From 30e90af2761354314681839ef19849ce8b2a6948 Mon Sep 17 00:00:00 2001 From: imterah Date: Fri, 27 Jun 2025 12:48:25 -0400 Subject: [PATCH 1/4] 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/4] 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/4] 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, From 102c83d5b94cc6e8580de56913cfe4b38edd0ea8 Mon Sep 17 00:00:00 2001 From: imterah Date: Sat, 28 Jun 2025 19:49:12 -0400 Subject: [PATCH 4/4] fix: Adds look vector correction code This is needed for the Xreal line of devices as the reverse engineered driver works, but has... issues. One of them being normal sensor drift, the other being ~2330 units in any direction for some reason. This is a hack but I can't think of a normal person that can spin their head around fast enough to trigger anything past 7 units/pull cycle, especially considering our already high pulling rate (basically as fast as we can) --- app/renderer/renderer.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/renderer/renderer.go b/app/renderer/renderer.go index 79cda44..6bac3a0 100644 --- a/app/renderer/renderer.go +++ b/app/renderer/renderer.go @@ -235,6 +235,24 @@ func EnterRenderLoop(config *libconfig.Config, displayMetadata *edidtools.Displa lookVector.Z = (currentRoll - previousRoll) * 6.5 } + // evil look hacks to not randomly explode + maxTrustedSize := float64(7) + + if math.Abs(float64(lookVector.X)) > maxTrustedSize { + log.Errorf("WOAH. Ignoring extreme camera movement for vector X: %.02f", lookVector.X) + lookVector.X = 0 + } + + if math.Abs(float64(lookVector.Y)) > maxTrustedSize { + log.Errorf("WOAH. Ignoring extreme camera movement for vector Y: %.02f", lookVector.Y) + lookVector.Y = 0 + } + + if !hasZVectorDisabledQuirk && math.Abs(float64(lookVector.Z)) > maxTrustedSize { + log.Errorf("WOAH. Ignoring extreme camera movement for vector Z: %.02f", lookVector.Z) + lookVector.Z = 0 + } + rl.UpdateCameraPro(&camera, movementVector, lookVector, 0) } } else {