From 30e90af2761354314681839ef19849ce8b2a6948 Mon Sep 17 00:00:00 2001 From: imterah Date: Fri, 27 Jun 2025 12:48:25 -0400 Subject: [PATCH 1/3] feature: Attempt to decrease load by decreasing max frame rate of window We don't really need to run at twice the speed of the display's refresh rate as Raylib manages the target time between frames well. --- app/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/main.go b/app/main.go index 239c49e..f84df7a 100644 --- a/app/main.go +++ b/app/main.go @@ -118,7 +118,7 @@ func mainEntrypoint(context.Context, *cli.Command) error { bufio.NewReader(os.Stdin).ReadBytes('\n') // Wait for Enter key press before continuing log.Info("Initializing XR headset") - rl.SetTargetFPS(int32(displayMetadata.MaxRefreshRate * 2)) + rl.SetTargetFPS(int32(displayMetadata.MaxRefreshRate)) rl.InitWindow(int32(displayMetadata.MaxWidth), int32(displayMetadata.MaxHeight), "UnrealXR") atexit.Register(func() { From 6911afb6e0845bc28b312c1180339d6e5c20813a Mon Sep 17 00:00:00 2001 From: imterah Date: Fri, 27 Jun 2025 12:50:29 -0400 Subject: [PATCH 2/3] fix: Adds missing script headers on unrealxr startup script --- unrealxr | 1 + 1 file changed, 1 insertion(+) diff --git a/unrealxr b/unrealxr index 512cfa2..5c13589 100755 --- a/unrealxr +++ b/unrealxr @@ -1 +1,2 @@ +#!/usr/bin/env bash sudo UNREALXR_LOG_LEVEL="$UNREALXR_LOG_LEVEL" LD_LIBRARY_PATH="$LD_LIBRARY_PATH" UNREALXR_CONFIG_PATH="$UNREALXR_CONFIG_PATH" UNREALXR_DATA_PATH="$UNREALXR_DATA_PATH" WAYLAND_DISPLAY="$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" XDG_RUNTIME_DIR="/user/run/0" ./uxr From 0efaf19b40ec6b1560f63f5376e2961311c89bb0 Mon Sep 17 00:00:00 2001 From: imterah Date: Fri, 27 Jun 2025 22:08:51 -0400 Subject: [PATCH 3/3] feature: Implement display rotation --- app/config/config.go | 20 ++++++----- app/config/default_config.yml | 4 ++- app/renderer/renderer.go | 68 ++++++++++++++++++++++++++++++----- 3 files changed, 75 insertions(+), 17 deletions(-) diff --git a/app/config/config.go b/app/config/config.go index 1222abf..5938650 100644 --- a/app/config/config.go +++ b/app/config/config.go @@ -6,10 +6,12 @@ import _ "embed" var InitialConfig []byte type DisplayConfig struct { - Angle *int `yaml:"angle"` - FOV *int `yaml:"fov"` - Spacing *float32 `yaml:"spacing"` - Count *int `yaml:"count"` + Angle *int `yaml:"angle"` + FOV *int `yaml:"fov"` + Spacing *float32 `yaml:"spacing"` + RadiusMultiplier *float32 `yaml:"circle_radius_multiplier"` + UseCircularSpacing *bool `yaml:"use_circular_spacing"` + Count *int `yaml:"count"` } type AppOverrides struct { @@ -38,10 +40,12 @@ func getPtrToBool(bool bool) *bool { var DefaultConfig = &Config{ DisplayConfig: DisplayConfig{ - Angle: getPtrToInt(45), - FOV: getPtrToInt(45), - Spacing: getPtrToFloat32(0.5), - Count: getPtrToInt(3), + Angle: getPtrToInt(45), + FOV: getPtrToInt(45), + Spacing: getPtrToFloat32(0.5), + RadiusMultiplier: getPtrToFloat32(2), + UseCircularSpacing: getPtrToBool(true), + Count: getPtrToInt(3), }, Overrides: AppOverrides{ AllowUnsupportedDevices: getPtrToBool(false), diff --git a/app/config/default_config.yml b/app/config/default_config.yml index 4938843..3daeee3 100644 --- a/app/config/default_config.yml +++ b/app/config/default_config.yml @@ -9,7 +9,9 @@ display: angle: 45 # Angle of the virtual displays fov: 45 # FOV of the 3D camera - spacing: 0.5 # Spacing between virtual displays + spacing: 0.5 # Raw spacing between virtual displays. Does not use circles in the layout. Purely flat plane. + circle_radius_multiplier: 2 # Multiplier for the radius of the circle used to calculate the spacing between virtual displays. "Rounded" plane of sorts. + use_circular_spacing: true # If true, uses a circular layout for the virtual displays. count: 3 # Count of virtual displays overrides: allow_unsupported_devices: false # If true, allows unsupported devices to be used as long as they're a compatible vendor (Xreal) diff --git a/app/renderer/renderer.go b/app/renderer/renderer.go index 8e2f3e3..79cda44 100644 --- a/app/renderer/renderer.go +++ b/app/renderer/renderer.go @@ -35,6 +35,15 @@ func findOptimalHorizontalRes(verticalDisplayRes float32, horizontalDisplayRes f return horizontalSize } +func findHfovFromVfov(vfovDeg, w, h float64) float64 { + vfovRad := vfovDeg * math.Pi / 180 + + ar := w / h + hfovRad := 2 * math.Atan(math.Tan(vfovRad/2)*ar) + + return hfovRad * 180 / math.Pi +} + func EnterRenderLoop(config *libconfig.Config, displayMetadata *edidtools.DisplayMetadata, evdiCards []*EvdiDisplayMetadata) { log.Info("Initializing AR driver") headset, err := ardriver.GetDevice() @@ -99,7 +108,9 @@ func EnterRenderLoop(config *libconfig.Config, displayMetadata *edidtools.Displa headset.RegisterEventListeners(arEventListner) - fovY := float32(45.0) + fovY := float32(*config.DisplayConfig.FOV) + fovX := findHfovFromVfov(float64(fovY), float64(displayMetadata.MaxWidth), float64(displayMetadata.MaxHeight)) + verticalSize := findMaxVerticalSize(fovY, 5.0) camera := rl.NewCamera3D( @@ -125,6 +136,21 @@ func EnterRenderLoop(config *libconfig.Config, displayMetadata *edidtools.Displa horizontalSize := findOptimalHorizontalRes(float32(displayMetadata.MaxHeight), float32(displayMetadata.MaxWidth), verticalSize) coreMesh := rl.GenMeshPlane(horizontalSize, verticalSize, 1, 1) + var radius float32 + + if *config.DisplayConfig.UseCircularSpacing == true { + radiusX := (horizontalSize / 2) / float32(math.Tan((float64(fovX)*math.Pi/180.0)/2)) + radiusY := (verticalSize / 2) / float32(math.Tan((float64(fovY)*math.Pi/180.0)/2)) + + if radiusY > radiusX { + radius = radiusY + } else { + radius = radiusX + } + + radius *= *config.DisplayConfig.RadiusMultiplier + } + movementVector := rl.Vector3{ X: 0.0, Y: 0.0, @@ -171,6 +197,17 @@ func EnterRenderLoop(config *libconfig.Config, displayMetadata *edidtools.Displa texture := rl.LoadTextureFromImage(image) model := rl.LoadModelFromMesh(coreMesh) + // spin up/down + pitchRad := float32(-90 * rl.Deg2rad) + // spin left/right + yawRad := currentAngle * rl.Deg2rad + + rotX := rl.MatrixRotateX(pitchRad) + rotY := rl.MatrixRotateY(yawRad) + + transform := rl.MatrixMultiply(rotX, rotY) + model.Transform = transform + rl.SetMaterialTexture(model.Materials, rl.MapAlbedo, texture) rects[i] = &TextureModelPair{ @@ -235,20 +272,35 @@ func EnterRenderLoop(config *libconfig.Config, displayMetadata *edidtools.Displa card.EvdiNode.RequestUpdate(card.Buffer) } + worldPos := rl.Vector3{ + X: 0, + Y: verticalSize / 2, + Z: 0, + } + + if *config.DisplayConfig.UseCircularSpacing == true { + yawRad := float32(rl.Deg2rad * rect.CurrentAngle) + + // WTF? + posX := float32(math.Sin(float64(yawRad))) * radius + posZ := -float32(math.Cos(float64(yawRad))) * radius + + worldPos.X = posX + worldPos.Z = posZ + radius + } else { + worldPos.X = rect.CurrentDisplaySpacing + } + rl.DrawModelEx( rect.Model, - rl.Vector3{ - X: rect.CurrentDisplaySpacing, - Y: verticalSize / 2, - Z: 0, - }, + worldPos, // rotate around X to make it vertical rl.Vector3{ - X: 1, + X: 0, Y: 0, Z: 0, }, - 90, + 0, rl.Vector3{ X: 1, Y: 1,