From d83e7f433767671ea5603cbce04b1b97426aea4b Mon Sep 17 00:00:00 2001 From: Slendi Date: Sun, 29 Jun 2025 17:34:45 +0300 Subject: [PATCH] Add helper scripts and wlroots integration Signed-off-by: Slendi --- src/compositor.cpp | 45 ++++++++++- src/compositor.h | 9 +++ src/wlr/openxr_gl.cpp | 183 +++++++++++++++++++++++++++++++++++++----- tools/clang-format.sh | 7 ++ tools/tokei.sh | 7 ++ 5 files changed, 232 insertions(+), 19 deletions(-) create mode 100755 tools/clang-format.sh create mode 100755 tools/tokei.sh diff --git a/src/compositor.cpp b/src/compositor.cpp index 7be2109..5f2f726 100644 --- a/src/compositor.cpp +++ b/src/compositor.cpp @@ -1,6 +1,13 @@ #include "compositor.h" #include "common.h" +#include +// wlroots is a C library; wrap headers in extern "C" +extern "C" { +#include +#include +} +#include "wlr/openxr_gl.h" #include #include @@ -193,10 +200,40 @@ void Compositor::create_session() ci.createFlags = 0; OPENXR_CHECK(xrCreateSession, m_xr_instance, &ci, &m_session); } + // --- wlroots integration: create Wayland display and OpenXR wlroots + // backend --- initialize wlroots logging + wlr_log_init(WLR_DEBUG, nullptr); + // create Wayland display + m_display = wl_display_create(); + if (!m_display) { + throw std::runtime_error("Failed to create Wayland display"); + } + // get event loop + m_event_loop = wl_display_get_event_loop(m_display); + if (!m_event_loop) { + wl_display_destroy(m_display); + throw std::runtime_error("Failed to get Wayland event loop"); + } + // create the OpenXR wlroots backend + m_backend = wlr_openxr_backend_create(m_display, m_event_loop); + if (!m_backend || !wlr_backend_is_openxr(m_backend)) { + wl_display_destroy(m_display); + throw std::runtime_error("Failed to create OpenXR wlroots backend"); + } } void Compositor::destroy_session() { + // destroy wlroots backend and Wayland display + if (m_backend) { + wlr_backend_destroy(m_backend); + m_backend = nullptr; + } + if (m_display) { + wl_display_destroy(m_display); + m_display = nullptr; + } + // destroy OpenXR session OPENXR_CHECK(xrDestroySession, m_session); } @@ -249,11 +286,17 @@ void Compositor::poll_events() void Compositor::run() { + // start the wlroots backend (this also begins XrSession) + if (!wlr_backend_start(m_backend)) { + throw std::runtime_error("Failed to start wlroots backend"); + } while (m_running) { this->poll_events(); + // dispatch Wayland events + wl_event_loop_dispatch(m_event_loop, 0); if (m_session_running) { - // TODO: Render frame + // TODO: Render frame (per-eye rendering via OpenXR swapchain) } } } diff --git a/src/compositor.h b/src/compositor.h index 20b7498..90569d9 100644 --- a/src/compositor.h +++ b/src/compositor.h @@ -2,6 +2,11 @@ #include +// Forward declarations for wlroots integration +struct wl_display; +struct wl_event_loop; +struct wlr_backend; + struct Compositor { Compositor() { @@ -34,4 +39,8 @@ private: bool m_session_running = false; XrSession m_session = {}; XrSessionState m_session_state = XR_SESSION_STATE_UNKNOWN; + // wlroots integration + wl_display* m_display = nullptr; + wl_event_loop* m_event_loop = nullptr; + wlr_backend* m_backend = nullptr; }; diff --git a/src/wlr/openxr_gl.cpp b/src/wlr/openxr_gl.cpp index de09067..d620393 100644 --- a/src/wlr/openxr_gl.cpp +++ b/src/wlr/openxr_gl.cpp @@ -10,10 +10,29 @@ extern "C" { #include #include +#define GL_GLEXT_PROTOTYPES +#include +#include #include #include #include #include +#include +// Internal backend definition +struct openxr_backend { + wlr_backend base; + wl_display* display; + wl_event_loop* event_loop; + XrInstance instance {}; + XrSession session {}; + XrSpace app_space { XR_NULL_HANDLE }; + XrSwapchain swapchain { XR_NULL_HANDLE }; + int32_t width = 0, height = 0; + std::vector swapchain_images; + std::vector framebuffers; + bool started = false; + struct wlr_output* output = nullptr; +}; static void output_destroy(struct wlr_output* wlr_output) { (void)wlr_output; } @@ -28,8 +47,77 @@ static bool output_test( static bool output_commit( struct wlr_output* wlr_output, const struct wlr_output_state* state) { - (void)wlr_output; + // Retrieve our backend + auto* xr = reinterpret_cast(wlr_output->backend); (void)state; + // Wait for frame + XrFrameState frameState { XR_TYPE_FRAME_STATE }; + xrWaitFrame(xr->session, nullptr, &frameState); + xrBeginFrame(xr->session, nullptr); + // Locate views + uint32_t viewCount = static_cast(xr->framebuffers.size()); + std::vector views(viewCount, { XR_TYPE_VIEW }); + XrViewLocateInfo viewLocInfo { XR_TYPE_VIEW_LOCATE_INFO }; + viewLocInfo.viewConfigurationType + = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + viewLocInfo.displayTime = frameState.predictedDisplayTime; + viewLocInfo.space = xr->app_space; + XrViewState viewState { XR_TYPE_VIEW_STATE }; + uint32_t viewCountOutput; + xrLocateViews(xr->session, &viewLocInfo, &viewState, viewCount, + &viewCountOutput, views.data()); + // Prepare projection views + std::vector projViews( + viewCountOutput, { XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW }); + for (uint32_t i = 0; i < viewCountOutput; ++i) { + // Acquire swapchain image + XrSwapchainImageAcquireInfo acqInfo { + XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO + }; + uint32_t imgIndex; + xrAcquireSwapchainImage(xr->swapchain, &acqInfo, &imgIndex); + XrSwapchainImageWaitInfo waitInfo { XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO }; + waitInfo.timeout = XR_INFINITE_DURATION; + xrWaitSwapchainImage(xr->swapchain, &waitInfo); + // Bind framebuffer and clear + glBindFramebuffer(GL_FRAMEBUFFER, xr->framebuffers[imgIndex]); + glViewport(0, 0, xr->width, xr->height); + glClearColor(0.1f, 0.1f, 0.1f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + // TODO: render scene (e.g., wlr_scene_render) + // Release swapchain image + { + XrSwapchainImageReleaseInfo relInfo { + XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO + }; + xrReleaseSwapchainImage(xr->swapchain, &relInfo); + } + // Setup projection view + projViews[i].pose = views[i].pose; + projViews[i].fov = views[i].fov; + projViews[i].subImage.swapchain = xr->swapchain; + projViews[i].subImage.imageArrayIndex = imgIndex; + // Set image rectangle offset and extent + projViews[i].subImage.imageRect.offset.x = 0; + projViews[i].subImage.imageRect.offset.y = 0; + projViews[i].subImage.imageRect.extent.width = xr->width; + projViews[i].subImage.imageRect.extent.height = xr->height; + } + // Unbind + glBindFramebuffer(GL_FRAMEBUFFER, 0); + // End frame + XrCompositionLayerProjection layer { XR_TYPE_COMPOSITION_LAYER_PROJECTION }; + layer.space = xr->app_space; + layer.viewCount = static_cast(projViews.size()); + layer.views = projViews.data(); + XrCompositionLayerBaseHeader const* layers[] + = { reinterpret_cast(&layer) }; + XrFrameEndInfo frameEndInfo { XR_TYPE_FRAME_END_INFO }; + frameEndInfo.displayTime = frameState.predictedDisplayTime; + frameEndInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; + frameEndInfo.layerCount = 1; + frameEndInfo.layers = layers; + xrEndFrame(xr->session, &frameEndInfo); return true; } @@ -48,23 +136,6 @@ static const struct wlr_output_impl output_impl = { .get_primary_formats = output_get_primary_formats, }; -#include -#include -#include -#include -#include -#include - -struct openxr_backend { - wlr_backend base; - wl_display* display; - wl_event_loop* event_loop; - XrInstance instance {}; - XrSession session {}; - bool started = false; - struct wlr_output* output = nullptr; -}; - static bool backend_start(wlr_backend* backend) { auto* xr = reinterpret_cast(backend); @@ -156,6 +227,68 @@ static bool backend_start(wlr_backend* backend) wlr_log(WLR_ERROR, "xrCreateSession failed"); return false; } + // Create reference space + XrReferenceSpaceCreateInfo spaceInfo { + XR_TYPE_REFERENCE_SPACE_CREATE_INFO + }; + spaceInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL; + spaceInfo.poseInReferenceSpace = { { 0, 0, 0, 1 }, { 0, 0, 0 } }; + if (xrCreateReferenceSpace(xr->session, &spaceInfo, &xr->app_space) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "xrCreateReferenceSpace failed"); + return false; + } + // Get recommended view configuration + uint32_t viewCount; + xrEnumerateViewConfigurationViews(xr->instance, system_id, + XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &viewCount, nullptr); + std::vector viewConfigs( + viewCount, { XR_TYPE_VIEW_CONFIGURATION_VIEW }); + xrEnumerateViewConfigurationViews(xr->instance, system_id, + XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, viewCount, &viewCount, + viewConfigs.data()); + xr->width = viewConfigs[0].recommendedImageRectWidth; + xr->height = viewConfigs[0].recommendedImageRectHeight; + // Create swapchain + uint32_t formatCount; + xrEnumerateSwapchainFormats(xr->session, 0, &formatCount, nullptr); + std::vector formats(formatCount); + xrEnumerateSwapchainFormats( + xr->session, formatCount, &formatCount, formats.data()); + int64_t swapFormat = formats.empty() ? 0 : formats[0]; + XrSwapchainCreateInfo swapchain_ci { XR_TYPE_SWAPCHAIN_CREATE_INFO }; + swapchain_ci.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT; + swapchain_ci.format = swapFormat; + swapchain_ci.sampleCount = 1; + swapchain_ci.width = xr->width; + swapchain_ci.height = xr->height; + swapchain_ci.faceCount = 1; + swapchain_ci.arraySize = viewCount; + swapchain_ci.mipCount = 1; + if (xrCreateSwapchain(xr->session, &swapchain_ci, &xr->swapchain) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "xrCreateSwapchain failed"); + return false; + } + // Enumerate swapchain images + uint32_t imageCount; + xrEnumerateSwapchainImages(xr->swapchain, 0, &imageCount, nullptr); + xr->swapchain_images.resize( + imageCount, { XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR }); + xrEnumerateSwapchainImages(xr->swapchain, imageCount, &imageCount, + reinterpret_cast( + xr->swapchain_images.data())); + // Create framebuffers for each image + xr->framebuffers.resize(imageCount); + for (uint32_t i = 0; i < imageCount; ++i) { + GLuint fbo; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, xr->swapchain_images[i].image, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + xr->framebuffers[i] = fbo; + } xr->output = static_cast(calloc(1, sizeof(wlr_output))); if (xr->output) { @@ -173,6 +306,20 @@ static bool backend_start(wlr_backend* backend) static void backend_destroy(wlr_backend* backend) { auto* xr = reinterpret_cast(backend); + // destroy swapchain and framebuffers + if (xr->swapchain) { + xrDestroySwapchain(xr->swapchain); + xr->swapchain = XR_NULL_HANDLE; + } + for (auto fbo : xr->framebuffers) { + glDeleteFramebuffers(1, &fbo); + } + // destroy reference space + if (xr->app_space) { + xrDestroySpace(xr->app_space); + xr->app_space = XR_NULL_HANDLE; + } + // destroy XR session and instance if (xr->session) { xrDestroySession(xr->session); } diff --git a/tools/clang-format.sh b/tools/clang-format.sh new file mode 100755 index 0000000..33e87d3 --- /dev/null +++ b/tools/clang-format.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd $(dirname $0)/.. || exit 1 +find src '(' -name '*.c' -o -name '*.h' -o -name '*.cpp' -o -name '*.hpp' ')' -print0 | xargs -0 clang-format -i + +echo 'Formatted source files in src/' \ No newline at end of file diff --git a/tools/tokei.sh b/tools/tokei.sh new file mode 100755 index 0000000..e845393 --- /dev/null +++ b/tools/tokei.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd $(dirname $0)/.. || exit 1 +tokei src + +exit 0 \ No newline at end of file