├── .github └── workflows │ └── nuget.yml ├── .gitignore ├── LICENSE ├── OVRSharp.Graphics.DirectX ├── DirectXCompositor.cs └── OVRSharp.Graphics.DirectX.csproj ├── OVRSharp.Graphics.OpenGL ├── OVRSharp.Graphics.OpenGL.csproj └── OpenGLCompositor.cs ├── OVRSharp.sln ├── OVRSharp ├── Application.cs ├── Exceptions │ └── OpenVRSystemException.cs ├── Graphics │ └── ICompositorAPI.cs ├── Math │ ├── Matrix.cs │ └── Transforms.cs ├── OVRSharp.csproj ├── Overlay.cs ├── openvr_api.cs └── openvr_api.dll ├── README.md └── tests ├── OVRSharp.Benchmarks ├── Graphics │ └── CompositorBenchmarks.cs ├── OVRSharp.Benchmarks.csproj └── Program.cs ├── OVRSharp.Graphics.DirectX.Tests ├── CompositorTests.cs └── OVRSharp.Graphics.DirectX.Tests.csproj ├── OVRSharp.Graphics.OpenGL.Tests ├── CompositorTests.cs └── OVRSharp.Graphics.OpenGL.Tests.csproj └── OVRSharp.Tests ├── Graphics └── CompositorTests.cs └── OVRSharp.Tests.csproj /.github/workflows/nuget.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NuGet 2 | on: 3 | push: 4 | branches: [ master ] 5 | release: 6 | types: [ created ] 7 | 8 | jobs: 9 | build-publish: 10 | name: Build and Publish 11 | runs-on: windows-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Set up .NET 16 | uses: actions/setup-dotnet@v1 17 | with: 18 | dotnet-version: "5.0.x" 19 | 20 | - name: Pack .nupkg (Release) 21 | if: ${{ github.event_name == 'release' }} 22 | run: dotnet pack -c Release -o dist 23 | 24 | - name: Pack .nupkg (Pre-Release) 25 | if: ${{ github.event_name == 'push' }} 26 | run: dotnet pack -c Release -o dist --version-suffix "git-${{ github.sha }}" 27 | 28 | - name: Publish to NuGet 29 | run: dotnet nuget push dist\* --api-key ${{ secrets.NUGET_KEY }} --source https://api.nuget.org/v3/index.json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # globs 2 | Makefile.in 3 | *.userprefs 4 | *.usertasks 5 | config.make 6 | config.status 7 | aclocal.m4 8 | install-sh 9 | autom4te.cache/ 10 | *.tar.gz 11 | tarballs/ 12 | test-results/ 13 | 14 | # Mac bundle stuff 15 | *.dmg 16 | *.app 17 | 18 | # content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore 19 | # General 20 | .DS_Store 21 | .AppleDouble 22 | .LSOverride 23 | 24 | # Icon must end with two \r 25 | Icon 26 | 27 | 28 | # Thumbnails 29 | ._* 30 | 31 | # Files that might appear in the root of a volume 32 | .DocumentRevisions-V100 33 | .fseventsd 34 | .Spotlight-V100 35 | .TemporaryItems 36 | .Trashes 37 | .VolumeIcon.icns 38 | .com.apple.timemachine.donotpresent 39 | 40 | # Directories potentially created on remote AFP share 41 | .AppleDB 42 | .AppleDesktop 43 | Network Trash Folder 44 | Temporary Items 45 | .apdisk 46 | 47 | # content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore 48 | # Windows thumbnail cache files 49 | Thumbs.db 50 | ehthumbs.db 51 | ehthumbs_vista.db 52 | 53 | # Dump file 54 | *.stackdump 55 | 56 | # Folder config file 57 | [Dd]esktop.ini 58 | 59 | # Recycle Bin used on file shares 60 | $RECYCLE.BIN/ 61 | 62 | # Windows Installer files 63 | *.cab 64 | *.msi 65 | *.msix 66 | *.msm 67 | *.msp 68 | 69 | # Windows shortcuts 70 | *.lnk 71 | 72 | # content below from: https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 73 | ## Ignore Visual Studio temporary files, build results, and 74 | ## files generated by popular Visual Studio add-ons. 75 | ## 76 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 77 | 78 | # User-specific files 79 | *.suo 80 | *.user 81 | *.userosscache 82 | *.sln.docstates 83 | 84 | # User-specific files (MonoDevelop/Xamarin Studio) 85 | *.userprefs 86 | 87 | # Build results 88 | [Dd]ebug/ 89 | [Dd]ebugPublic/ 90 | [Rr]elease/ 91 | [Rr]eleases/ 92 | x64/ 93 | x86/ 94 | bld/ 95 | [Bb]in/ 96 | [Oo]bj/ 97 | [Ll]og/ 98 | 99 | # Visual Studio 2015/2017 cache/options directory 100 | .vs/ 101 | # Uncomment if you have tasks that create the project's static files in wwwroot 102 | #wwwroot/ 103 | 104 | # Visual Studio 2017 auto generated files 105 | Generated\ Files/ 106 | 107 | # MSTest test Results 108 | [Tt]est[Rr]esult*/ 109 | [Bb]uild[Ll]og.* 110 | 111 | # NUNIT 112 | *.VisualState.xml 113 | TestResult.xml 114 | 115 | # Build Results of an ATL Project 116 | [Dd]ebugPS/ 117 | [Rr]eleasePS/ 118 | dlldata.c 119 | 120 | # Benchmark Results 121 | BenchmarkDotNet.Artifacts/ 122 | 123 | # .NET Core 124 | project.lock.json 125 | project.fragment.lock.json 126 | artifacts/ 127 | 128 | # StyleCop 129 | StyleCopReport.xml 130 | 131 | # Files built by Visual Studio 132 | *_i.c 133 | *_p.c 134 | *_h.h 135 | *.ilk 136 | *.meta 137 | *.obj 138 | *.iobj 139 | *.pch 140 | *.pdb 141 | *.ipdb 142 | *.pgc 143 | *.pgd 144 | *.rsp 145 | *.sbr 146 | *.tlb 147 | *.tli 148 | *.tlh 149 | *.tmp 150 | *.tmp_proj 151 | *_wpftmp.csproj 152 | *.log 153 | *.vspscc 154 | *.vssscc 155 | .builds 156 | *.pidb 157 | *.svclog 158 | *.scc 159 | 160 | # Chutzpah Test files 161 | _Chutzpah* 162 | 163 | # Visual C++ cache files 164 | ipch/ 165 | *.aps 166 | *.ncb 167 | *.opendb 168 | *.opensdf 169 | *.sdf 170 | *.cachefile 171 | *.VC.db 172 | *.VC.VC.opendb 173 | 174 | # Visual Studio profiler 175 | *.psess 176 | *.vsp 177 | *.vspx 178 | *.sap 179 | 180 | # Visual Studio Trace Files 181 | *.e2e 182 | 183 | # TFS 2012 Local Workspace 184 | $tf/ 185 | 186 | # Guidance Automation Toolkit 187 | *.gpState 188 | 189 | # ReSharper is a .NET coding add-in 190 | _ReSharper*/ 191 | *.[Rr]e[Ss]harper 192 | *.DotSettings.user 193 | 194 | # JustCode is a .NET coding add-in 195 | .JustCode 196 | 197 | # TeamCity is a build add-in 198 | _TeamCity* 199 | 200 | # DotCover is a Code Coverage Tool 201 | *.dotCover 202 | 203 | # AxoCover is a Code Coverage Tool 204 | .axoCover/* 205 | !.axoCover/settings.json 206 | 207 | # Visual Studio code coverage results 208 | *.coverage 209 | *.coveragexml 210 | 211 | # NCrunch 212 | _NCrunch_* 213 | .*crunch*.local.xml 214 | nCrunchTemp_* 215 | 216 | # MightyMoose 217 | *.mm.* 218 | AutoTest.Net/ 219 | 220 | # Web workbench (sass) 221 | .sass-cache/ 222 | 223 | # Installshield output folder 224 | [Ee]xpress/ 225 | 226 | # DocProject is a documentation generator add-in 227 | DocProject/buildhelp/ 228 | DocProject/Help/*.HxT 229 | DocProject/Help/*.HxC 230 | DocProject/Help/*.hhc 231 | DocProject/Help/*.hhk 232 | DocProject/Help/*.hhp 233 | DocProject/Help/Html2 234 | DocProject/Help/html 235 | 236 | # Click-Once directory 237 | publish/ 238 | 239 | # Publish Web Output 240 | *.[Pp]ublish.xml 241 | *.azurePubxml 242 | # Note: Comment the next line if you want to checkin your web deploy settings, 243 | # but database connection strings (with potential passwords) will be unencrypted 244 | *.pubxml 245 | *.publishproj 246 | 247 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 248 | # checkin your Azure Web App publish settings, but sensitive information contained 249 | # in these scripts will be unencrypted 250 | PublishScripts/ 251 | 252 | # NuGet Packages 253 | *.nupkg 254 | # The packages folder can be ignored because of Package Restore 255 | **/[Pp]ackages/* 256 | # except build/, which is used as an MSBuild target. 257 | !**/[Pp]ackages/build/ 258 | # Uncomment if necessary however generally it will be regenerated when needed 259 | #!**/[Pp]ackages/repositories.config 260 | # NuGet v3's project.json files produces more ignorable files 261 | *.nuget.props 262 | *.nuget.targets 263 | 264 | # Microsoft Azure Build Output 265 | csx/ 266 | *.build.csdef 267 | 268 | # Microsoft Azure Emulator 269 | ecf/ 270 | rcf/ 271 | 272 | # Windows Store app package directories and files 273 | AppPackages/ 274 | BundleArtifacts/ 275 | Package.StoreAssociation.xml 276 | _pkginfo.txt 277 | *.appx 278 | 279 | # Visual Studio cache files 280 | # files ending in .cache can be ignored 281 | *.[Cc]ache 282 | # but keep track of directories ending in .cache 283 | !*.[Cc]ache/ 284 | 285 | # Others 286 | ClientBin/ 287 | ~$* 288 | *~ 289 | *.dbmdl 290 | *.dbproj.schemaview 291 | *.jfm 292 | *.pfx 293 | *.publishsettings 294 | orleans.codegen.cs 295 | 296 | # Including strong name files can present a security risk 297 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 298 | #*.snk 299 | 300 | # Since there are multiple workflows, uncomment next line to ignore bower_components 301 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 302 | #bower_components/ 303 | 304 | # RIA/Silverlight projects 305 | Generated_Code/ 306 | 307 | # Backup & report files from converting an old project file 308 | # to a newer Visual Studio version. Backup files are not needed, 309 | # because we have git ;-) 310 | _UpgradeReport_Files/ 311 | Backup*/ 312 | UpgradeLog*.XML 313 | UpgradeLog*.htm 314 | ServiceFabricBackup/ 315 | *.rptproj.bak 316 | 317 | # SQL Server files 318 | *.mdf 319 | *.ldf 320 | *.ndf 321 | 322 | # Business Intelligence projects 323 | *.rdl.data 324 | *.bim.layout 325 | *.bim_*.settings 326 | *.rptproj.rsuser 327 | 328 | # Microsoft Fakes 329 | FakesAssemblies/ 330 | 331 | # GhostDoc plugin setting file 332 | *.GhostDoc.xml 333 | 334 | # Node.js Tools for Visual Studio 335 | .ntvs_analysis.dat 336 | node_modules/ 337 | 338 | # Visual Studio 6 build log 339 | *.plg 340 | 341 | # Visual Studio 6 workspace options file 342 | *.opt 343 | 344 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 345 | *.vbw 346 | 347 | # Visual Studio LightSwitch build output 348 | **/*.HTMLClient/GeneratedArtifacts 349 | **/*.DesktopClient/GeneratedArtifacts 350 | **/*.DesktopClient/ModelManifest.xml 351 | **/*.Server/GeneratedArtifacts 352 | **/*.Server/ModelManifest.xml 353 | _Pvt_Extensions 354 | 355 | # Paket dependency manager 356 | .paket/paket.exe 357 | paket-files/ 358 | 359 | # FAKE - F# Make 360 | .fake/ 361 | 362 | # JetBrains Rider 363 | .idea/ 364 | *.sln.iml 365 | 366 | # CodeRush personal settings 367 | .cr/personal 368 | 369 | # Python Tools for Visual Studio (PTVS) 370 | __pycache__/ 371 | *.pyc 372 | 373 | # Cake - Uncomment if you are using it 374 | # tools/** 375 | # !tools/packages.config 376 | 377 | # Tabs Studio 378 | *.tss 379 | 380 | # Telerik's JustMock configuration file 381 | *.jmconfig 382 | 383 | # BizTalk build output 384 | *.btp.cs 385 | *.btm.cs 386 | *.odx.cs 387 | *.xsd.cs 388 | 389 | # OpenCover UI analysis results 390 | OpenCover/ 391 | 392 | # Azure Stream Analytics local run output 393 | ASALocalRun/ 394 | 395 | # MSBuild Binary and Structured Log 396 | *.binlog 397 | 398 | # NVidia Nsight GPU debugger configuration file 399 | *.nvuser 400 | 401 | # MFractors (Xamarin productivity tool) working folder 402 | .mfractor/ 403 | 404 | # Local History for Visual Studio 405 | .localhistory/ 406 | 407 | dist/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020-2021 TJ Horner 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /OVRSharp.Graphics.DirectX/DirectXCompositor.cs: -------------------------------------------------------------------------------- 1 | using OVRSharp.Exceptions; 2 | using SharpDX; 3 | using SharpDX.Direct3D11; 4 | using System; 5 | using System.Drawing; 6 | using System.Drawing.Imaging; 7 | using Valve.VR; 8 | 9 | namespace OVRSharp.Graphics.DirectX 10 | { 11 | public class DirectXCompositor : ICompositorAPI 12 | { 13 | /// 14 | /// Global, static instance of . 15 | /// 16 | public static DirectXCompositor Instance 17 | { 18 | get 19 | { 20 | if (_instance == null) 21 | _instance = new DirectXCompositor(); 22 | 23 | return _instance; 24 | } 25 | } 26 | 27 | private static DirectXCompositor _instance = null; 28 | 29 | private readonly Device device; 30 | 31 | /// 32 | /// A DirectX-based implementation of .

33 | /// 34 | /// Ideally, there should ever only be one instance of this class 35 | /// at once. You can provide to anything that depends 36 | /// on to ensure that this is the case. 37 | ///
38 | public DirectXCompositor() 39 | { 40 | device = new Device(SharpDX.Direct3D.DriverType.Hardware, DeviceCreationFlags.Debug); 41 | } 42 | 43 | /// 44 | ///

45 | /// 46 | /// Warning: this is a pretty slow method. 47 | /// It's fine to use this for one-off captures, but if you require 48 | /// something like a constant stream of the headset view, I recommend 49 | /// digging into a lower-level implementation. 50 | ///
51 | /// 52 | public Bitmap GetMirrorImage(EVREye eye = EVREye.Eye_Left) 53 | { 54 | var srvPtr = IntPtr.Zero; 55 | 56 | var result = OpenVR.Compositor.GetMirrorTextureD3D11(eye, device.NativePointer, ref srvPtr); 57 | if (result != EVRCompositorError.None) 58 | throw new OpenVRSystemException("Failed to get mirror texture from OpenVR", result); 59 | 60 | var srv = new ShaderResourceView(srvPtr); 61 | var tex = srv.Resource.QueryInterface(); 62 | var texDesc = tex.Description; 63 | 64 | var bitmap = new Bitmap(texDesc.Width, texDesc.Height); 65 | var boundsRect = new Rectangle(0, 0, texDesc.Width, texDesc.Height); 66 | 67 | using(var cpuTex = new Texture2D(device, new Texture2DDescription 68 | { 69 | CpuAccessFlags = CpuAccessFlags.Read, 70 | BindFlags = BindFlags.None, 71 | Format = texDesc.Format, 72 | Width = texDesc.Width, 73 | Height = texDesc.Height, 74 | OptionFlags = ResourceOptionFlags.None, 75 | MipLevels = 1, 76 | ArraySize = 1, 77 | SampleDescription = { Count = 1, Quality = 0 }, 78 | Usage = ResourceUsage.Staging 79 | })) 80 | { 81 | // Copy texture to RAM so CPU can read from it 82 | device.ImmediateContext.CopyResource(tex, cpuTex); 83 | OpenVR.Compositor.ReleaseMirrorTextureD3D11(srvPtr); 84 | 85 | var mapSource = device.ImmediateContext.MapSubresource(cpuTex, 0, MapMode.Read, MapFlags.None); 86 | var mapDest = bitmap.LockBits(boundsRect, ImageLockMode.WriteOnly, bitmap.PixelFormat); 87 | var sourcePtr = mapSource.DataPointer; 88 | var destPtr = mapDest.Scan0; 89 | 90 | for (int y = 0; y < texDesc.Height; y++) 91 | { 92 | Utilities.CopyMemory(destPtr, sourcePtr, texDesc.Width * 4); 93 | sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch); 94 | destPtr = IntPtr.Add(destPtr, mapDest.Stride); 95 | } 96 | 97 | bitmap.UnlockBits(mapDest); 98 | device.ImmediateContext.UnmapSubresource(cpuTex, 0); 99 | } 100 | 101 | FlipChannels(ref bitmap); 102 | return bitmap; 103 | } 104 | 105 | private static void FlipChannels(ref Bitmap bitmap) 106 | { 107 | var bytesPerPixel = Image.GetPixelFormatSize(bitmap.PixelFormat) / 8; 108 | var data = bitmap.LockBits( 109 | new Rectangle(0, 0, bitmap.Width, bitmap.Height), 110 | ImageLockMode.ReadWrite, 111 | bitmap.PixelFormat 112 | ); 113 | 114 | var length = System.Math.Abs(data.Stride) * bitmap.Height; 115 | 116 | unsafe 117 | { 118 | byte* rgbValues = (byte*)data.Scan0.ToPointer(); 119 | for (int i = 0; i < length; i += bytesPerPixel) 120 | { 121 | byte dummy = rgbValues[i]; 122 | rgbValues[i] = rgbValues[i + 2]; 123 | rgbValues[i + 2] = dummy; 124 | } 125 | } 126 | 127 | bitmap.UnlockBits(data); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /OVRSharp.Graphics.DirectX/OVRSharp.Graphics.DirectX.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | TJ Horner 6 | DirectX implementation of OVRSharp graphics APIs 7 | https://github.com/OVRTools/OVRSharp 8 | https://github.com/OVRTools/OVRSharp 9 | MIT 10 | git 11 | openvr 12 | 2020-2021 TJ Horner 13 | false 14 | true 15 | OVRSharp.Graphics.DirectX 16 | 1.2.0 17 | $(VersionSuffix) 18 | 19 | 20 | 21 | true 22 | 23 | 24 | 25 | true 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /OVRSharp.Graphics.OpenGL/OVRSharp.Graphics.OpenGL.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | TJ Horner 6 | OpenGL implementation of OVRSharp graphics APIs 7 | https://github.com/OVRTools/OVRSharp 8 | https://github.com/OVRTools/OVRSharp 9 | MIT 10 | git 11 | openvr 12 | 2020-2021 TJ Horner 13 | false 14 | true 15 | OVRSharp.Graphics.OpenGL 16 | 1.2.0 17 | $(VersionSuffix) 18 | false 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /OVRSharp.Graphics.OpenGL/OpenGLCompositor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Drawing.Imaging; 4 | using OpenTK.Graphics; 5 | using OpenTK.Graphics.OpenGLES3; 6 | using OpenTK.Windowing.Desktop; 7 | using OVRSharp.Exceptions; 8 | using Valve.VR; 9 | using PixelFormat = System.Drawing.Imaging.PixelFormat; 10 | 11 | namespace OVRSharp.Graphics.OpenGL 12 | { 13 | public class OpenGLCompositor : NativeWindow, ICompositorAPI 14 | { 15 | public OpenGLCompositor() : base( 16 | new NativeWindowSettings 17 | { 18 | StartVisible = false, 19 | Title = "OVRSharp Window", 20 | WindowState = OpenTK.Windowing.Common.WindowState.Minimized 21 | } 22 | ) { } 23 | 24 | public Bitmap GetMirrorImage(EVREye eye = EVREye.Eye_Left) 25 | { 26 | uint textureId = 0; 27 | var handle = new IntPtr(); 28 | 29 | var result = OpenVR.Compositor.GetMirrorTextureGL(eye, ref textureId, handle); 30 | if (result != EVRCompositorError.None) 31 | throw new OpenVRSystemException("Failed to get mirror texture from OpenVR", result); 32 | 33 | OpenVR.Compositor.LockGLSharedTextureForAccess(handle); 34 | 35 | GL.BindTexture(TextureTarget.Texture2d, new TextureHandle((int)textureId)); 36 | 37 | var height = 0; 38 | GL.GetTexParameteri(TextureTarget.Texture2d, GetTextureParameter.TextureHeight, ref height); 39 | 40 | var width = 0; 41 | GL.GetTexParameteri(TextureTarget.Texture2d, GetTextureParameter.TextureWidth, ref width); 42 | 43 | var bitmap = new Bitmap(width, height); 44 | var data = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, 45 | PixelFormat.Format24bppRgb); 46 | 47 | GL.Finish(); 48 | GL.ReadPixels(0, 0, width, height, OpenTK.Graphics.OpenGLES3.PixelFormat.Rgb, PixelType.UnsignedByte, data.Scan0); 49 | 50 | bitmap.UnlockBits(data); 51 | bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY); 52 | 53 | OpenVR.Compositor.UnlockGLSharedTextureForAccess(handle); 54 | OpenVR.Compositor.ReleaseSharedGLTexture(textureId, handle); 55 | 56 | return bitmap; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /OVRSharp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.808.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OVRSharp", "OVRSharp\OVRSharp.csproj", "{DA0D98B4-9F23-414E-9B9F-CD8F113992BE}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OVRSharp.Graphics.OpenGL", "OVRSharp.Graphics.OpenGL\OVRSharp.Graphics.OpenGL.csproj", "{ABEE1C23-AC02-4E6E-AC02-EAA0205BE84B}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OVRSharp.Graphics.OpenGL.Tests", "tests\OVRSharp.Graphics.OpenGL.Tests\OVRSharp.Graphics.OpenGL.Tests.csproj", "{C78CB0B3-9DCF-4083-A234-C5FB2661A9B3}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EDA24C40-CE58-429D-A903-19BDE6D024E6}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{AC203731-548C-4C7D-95BD-AAA6D7D288FC}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OVRSharp.Graphics.DirectX", "OVRSharp.Graphics.DirectX\OVRSharp.Graphics.DirectX.csproj", "{AF840E83-0D65-4558-8086-F19491B4532B}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OVRSharp.Graphics.DirectX.Tests", "tests\OVRSharp.Graphics.DirectX.Tests\OVRSharp.Graphics.DirectX.Tests.csproj", "{80860435-8D24-48BB-BA3A-25ECB6AA6E0A}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OVRSharp.Tests", "tests\OVRSharp.Tests\OVRSharp.Tests.csproj", "{875527A2-DDD3-4F4B-A0EF-4F7980D6AB73}" 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OVRSharp.Benchmarks", "tests\OVRSharp.Benchmarks\OVRSharp.Benchmarks.csproj", "{5CF4140D-F0E4-4F9D-8DEC-92F3DD82720F}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {DA0D98B4-9F23-414E-9B9F-CD8F113992BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {DA0D98B4-9F23-414E-9B9F-CD8F113992BE}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {DA0D98B4-9F23-414E-9B9F-CD8F113992BE}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {DA0D98B4-9F23-414E-9B9F-CD8F113992BE}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {ABEE1C23-AC02-4E6E-AC02-EAA0205BE84B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {ABEE1C23-AC02-4E6E-AC02-EAA0205BE84B}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {ABEE1C23-AC02-4E6E-AC02-EAA0205BE84B}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {ABEE1C23-AC02-4E6E-AC02-EAA0205BE84B}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {C78CB0B3-9DCF-4083-A234-C5FB2661A9B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {C78CB0B3-9DCF-4083-A234-C5FB2661A9B3}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {C78CB0B3-9DCF-4083-A234-C5FB2661A9B3}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {C78CB0B3-9DCF-4083-A234-C5FB2661A9B3}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {AF840E83-0D65-4558-8086-F19491B4532B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {AF840E83-0D65-4558-8086-F19491B4532B}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {AF840E83-0D65-4558-8086-F19491B4532B}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {AF840E83-0D65-4558-8086-F19491B4532B}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {80860435-8D24-48BB-BA3A-25ECB6AA6E0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {80860435-8D24-48BB-BA3A-25ECB6AA6E0A}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {80860435-8D24-48BB-BA3A-25ECB6AA6E0A}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {80860435-8D24-48BB-BA3A-25ECB6AA6E0A}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {875527A2-DDD3-4F4B-A0EF-4F7980D6AB73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {875527A2-DDD3-4F4B-A0EF-4F7980D6AB73}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {875527A2-DDD3-4F4B-A0EF-4F7980D6AB73}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {875527A2-DDD3-4F4B-A0EF-4F7980D6AB73}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {5CF4140D-F0E4-4F9D-8DEC-92F3DD82720F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {5CF4140D-F0E4-4F9D-8DEC-92F3DD82720F}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {5CF4140D-F0E4-4F9D-8DEC-92F3DD82720F}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {5CF4140D-F0E4-4F9D-8DEC-92F3DD82720F}.Release|Any CPU.Build.0 = Release|Any CPU 58 | EndGlobalSection 59 | GlobalSection(SolutionProperties) = preSolution 60 | HideSolutionNode = FALSE 61 | EndGlobalSection 62 | GlobalSection(NestedProjects) = preSolution 63 | {DA0D98B4-9F23-414E-9B9F-CD8F113992BE} = {EDA24C40-CE58-429D-A903-19BDE6D024E6} 64 | {ABEE1C23-AC02-4E6E-AC02-EAA0205BE84B} = {EDA24C40-CE58-429D-A903-19BDE6D024E6} 65 | {C78CB0B3-9DCF-4083-A234-C5FB2661A9B3} = {AC203731-548C-4C7D-95BD-AAA6D7D288FC} 66 | {AF840E83-0D65-4558-8086-F19491B4532B} = {EDA24C40-CE58-429D-A903-19BDE6D024E6} 67 | {80860435-8D24-48BB-BA3A-25ECB6AA6E0A} = {AC203731-548C-4C7D-95BD-AAA6D7D288FC} 68 | {875527A2-DDD3-4F4B-A0EF-4F7980D6AB73} = {AC203731-548C-4C7D-95BD-AAA6D7D288FC} 69 | {5CF4140D-F0E4-4F9D-8DEC-92F3DD82720F} = {AC203731-548C-4C7D-95BD-AAA6D7D288FC} 70 | EndGlobalSection 71 | GlobalSection(ExtensibilityGlobals) = postSolution 72 | SolutionGuid = {93984CAB-8CA9-430A-8166-5E2B339CA8A8} 73 | EndGlobalSection 74 | EndGlobal 75 | -------------------------------------------------------------------------------- /OVRSharp/Application.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OVRSharp.Exceptions; 3 | using Valve.VR; 4 | 5 | namespace OVRSharp 6 | { 7 | public class Application 8 | { 9 | public enum ApplicationType 10 | { 11 | /// 12 | /// A 3D application that will be drawing an environment. 13 | /// 14 | Scene = EVRApplicationType.VRApplication_Scene, 15 | 16 | /// 17 | /// An application that only interacts with overlays or the dashboard. 18 | /// 19 | Overlay = EVRApplicationType.VRApplication_Overlay, 20 | 21 | /// 22 | /// The application will not start SteamVR. If it is not already running 23 | /// the call with VR_Init will fail with . 24 | /// 25 | Background = EVRApplicationType.VRApplication_Background, 26 | 27 | /// 28 | /// The application will start up even if no hardware is present. Only the IVRSettings 29 | /// and IVRApplications interfaces are guaranteed to work. This application type is 30 | /// appropriate for things like installers. 31 | /// 32 | Utility = EVRApplicationType.VRApplication_Utility, 33 | 34 | Other = EVRApplicationType.VRApplication_Other 35 | } 36 | 37 | public readonly ApplicationType Type; 38 | public CVRSystem OVRSystem { get; private set; } 39 | 40 | /// 41 | /// Instantiate and initialize a new . 42 | /// Internally, this will initialize the OpenVR API with the specified 43 | /// and . 44 | /// 45 | /// 46 | /// 47 | /// 48 | public Application(ApplicationType type, string startupInfo = "") 49 | { 50 | Type = type; 51 | 52 | // Attempt to initialize a new OpenVR context 53 | var err = EVRInitError.None; 54 | OVRSystem = OpenVR.Init(ref err, (EVRApplicationType)type, startupInfo); 55 | 56 | if (err != EVRInitError.None) 57 | throw new OpenVRSystemException("An error occurred while initializing the OpenVR runtime.", err); 58 | } 59 | 60 | public void Shutdown() 61 | { 62 | OpenVR.Shutdown(); 63 | OVRSystem = null; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /OVRSharp/Exceptions/OpenVRSystemException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OVRSharp.Exceptions 4 | { 5 | public class OpenVRSystemException : Exception 6 | { 7 | public readonly TError Error; 8 | 9 | public OpenVRSystemException() : base() { } 10 | public OpenVRSystemException(string message) : base(message) { } 11 | public OpenVRSystemException(string message, Exception inner) : base(message, inner) { } 12 | 13 | public OpenVRSystemException(string message, TError error) : this($"{message} ({error})") 14 | { 15 | Error = error; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /OVRSharp/Graphics/ICompositorAPI.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using Valve.VR; 3 | 4 | namespace OVRSharp.Graphics 5 | { 6 | /// 7 | /// An interface for graphics-related compositor API methods.

8 | /// 9 | /// You can find implementations for different graphics APIs on NuGet as 10 | /// OVRSharp.Graphics.DirectX and OVRSharp.Graphics.OpenGL. Anyone else is also 11 | /// free to implement their own version of this for other graphics APIs and 12 | /// publish them. 13 | ///
14 | public interface ICompositorAPI 15 | { 16 | /// 17 | /// Capture a screenshot of the headset view. 18 | /// 19 | /// The eye to capture. 20 | Bitmap GetMirrorImage(EVREye eye = EVREye.Eye_Left); 21 | } 22 | } -------------------------------------------------------------------------------- /OVRSharp/Math/Matrix.cs: -------------------------------------------------------------------------------- 1 | using Valve.VR; 2 | using System.Numerics; 3 | 4 | namespace OVRSharp.Math 5 | { 6 | public static class MatrixExtension 7 | { 8 | /// 9 | /// Converts a to a . 10 | ///
11 | ///
12 | /// From:
13 | /// 11 12 13 14
14 | /// 21 22 23 24
15 | /// 31 32 33 34
16 | /// 41 42 43 44 17 | ///

18 | /// To:
19 | /// 11 12 13 41
20 | /// 21 22 23 42
21 | /// 31 32 33 43 22 | ///
23 | public static HmdMatrix34_t ToHmdMatrix34_t(this Matrix4x4 matrix) 24 | { 25 | return new HmdMatrix34_t() 26 | { 27 | m0 = matrix.M11, 28 | m1 = matrix.M12, 29 | m2 = matrix.M13, 30 | m3 = matrix.M41, 31 | m4 = matrix.M21, 32 | m5 = matrix.M22, 33 | m6 = matrix.M23, 34 | m7 = matrix.M42, 35 | m8 = matrix.M31, 36 | m9 = matrix.M32, 37 | m10 = matrix.M33, 38 | m11 = matrix.M43, 39 | }; 40 | } 41 | 42 | /// 43 | /// Converts a to a . 44 | ///
45 | ///
46 | /// From:
47 | /// 11 12 13 14
48 | /// 21 22 23 24
49 | /// 31 32 33 34 50 | ///

51 | /// To:
52 | /// 11 12 13 XX
53 | /// 21 22 23 XX
54 | /// 31 32 33 XX
55 | /// 14 24 34 XX 56 | ///
57 | public static Matrix4x4 ToMatrix4x4(this HmdMatrix34_t matrix) 58 | { 59 | return new Matrix4x4( 60 | matrix.m0, matrix.m1, matrix.m2, 0, 61 | matrix.m4, matrix.m5, matrix.m6, 0, 62 | matrix.m8, matrix.m9, matrix.m10, 0, 63 | matrix.m3, matrix.m7, matrix.m11, 1 64 | ); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /OVRSharp/Math/Transforms.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OVRSharp.Math 4 | { 5 | public class Transforms 6 | { 7 | public Transforms() 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /OVRSharp/OVRSharp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | TJ Horner 6 | High-level idiomatic C# interface for working with the OpenVR API 7 | https://github.com/OVRTools/OVRSharp 8 | https://github.com/OVRTools/OVRSharp 9 | MIT 10 | git 11 | openvr 12 | 2020-2021 TJ Horner 13 | false 14 | true 15 | OVRSharp 16 | 1.2.0 17 | $(VersionSuffix) 18 | 19 | 20 | 21 | 1591 22 | 23 | 24 | 25 | 1591 26 | 27 | 28 | 29 | 30 | true 31 | PreserveNewest 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | true 42 | \ 43 | 44 | 45 | 46 | true 47 | \ 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /OVRSharp/Overlay.cs: -------------------------------------------------------------------------------- 1 | using OVRSharp.Exceptions; 2 | using System; 3 | using System.Threading; 4 | using Valve.VR; 5 | 6 | namespace OVRSharp 7 | { 8 | public class Overlay 9 | { 10 | public enum TrackedDeviceRole 11 | { 12 | None, 13 | Hmd, 14 | RightHand = ETrackedControllerRole.RightHand, 15 | LeftHand = ETrackedControllerRole.LeftHand 16 | } 17 | 18 | /// 19 | /// This event is fired when mouse movement is detected. 20 | /// 21 | public event EventHandler OnMouseMove; 22 | public event EventHandler OnMouseDown; 23 | public event EventHandler OnMouseUp; 24 | public event EventHandler OnUnknown; 25 | 26 | public readonly string Key; 27 | public readonly string Name; 28 | public readonly bool IsDashboardOverlay; 29 | 30 | /// 31 | /// The rate at which to poll for overlay events, in milliseconds. 32 | /// 33 | public int PollingRate = 20; 34 | 35 | /// 36 | /// The pointer to the underlying OpenVR object for this overlay. 37 | /// 38 | public ulong Handle => _overlayHandle; 39 | 40 | /// 41 | /// The tracked device that the overlay is currently attached to. 42 | /// If set to , the overlay 43 | /// will switch back to absolute transform mode. 44 | /// 45 | public TrackedDeviceRole TrackedDevice 46 | { 47 | get => _trackedDevice; 48 | 49 | set 50 | { 51 | if (IsDashboardOverlay) return; 52 | 53 | _trackedDevice = value; 54 | 55 | EVROverlayError err; 56 | 57 | if (value == TrackedDeviceRole.None) 58 | { 59 | err = OpenVR.Overlay.SetOverlayTransformAbsolute(_overlayHandle, ETrackingUniverseOrigin.TrackingUniverseStanding, ref _transform); 60 | if (err != EVROverlayError.None) throw new Exception($"Could not set transform absolute: {err}"); 61 | return; 62 | } 63 | 64 | uint index = value == TrackedDeviceRole.Hmd 65 | ? OpenVR.k_unTrackedDeviceIndex_Hmd 66 | : OpenVR.System.GetTrackedDeviceIndexForControllerRole((ETrackedControllerRole)value); 67 | 68 | err = OpenVR.Overlay.SetOverlayTransformTrackedDeviceRelative(_overlayHandle, index, ref _transform); 69 | if (err != EVROverlayError.None) throw new Exception($"Could not set transform relative: {err}"); 70 | 71 | _trackedDeviceIndex = index; 72 | } 73 | } 74 | 75 | /// 76 | /// Sets the part of the texture to use for the overlay. UV Min 77 | /// is the upper left corner and UV Max is the lower right corner. 78 | /// By default overlays use the entire texture. 79 | /// 80 | public VRTextureBounds_t TextureBounds 81 | { 82 | set => AssertNoError(OpenVR.Overlay.SetOverlayTextureBounds(_overlayHandle, ref value)); 83 | 84 | get 85 | { 86 | var bounds = new VRTextureBounds_t(); 87 | AssertNoError(OpenVR.Overlay.GetOverlayTextureBounds(_overlayHandle, ref bounds)); 88 | return bounds; 89 | } 90 | } 91 | 92 | /// 93 | /// Sets/gets the mouse scaling factor that is used for mouse events. 94 | /// The actual texture may be a different size, but this is typically 95 | /// the size of the underlying UI in pixels. 96 | /// 97 | public HmdVector2_t MouseScale 98 | { 99 | set => AssertNoError(OpenVR.Overlay.SetOverlayMouseScale(_overlayHandle, ref value)); 100 | 101 | get 102 | { 103 | var scale = new HmdVector2_t(); 104 | AssertNoError(OpenVR.Overlay.GetOverlayMouseScale(_overlayHandle, ref scale)); 105 | return scale; 106 | } 107 | } 108 | 109 | /// 110 | /// Dashboard overlays are always . Other 111 | /// overlays default to , but can be set to 112 | /// use automatic mouse interaction using this function. 113 | /// 114 | public VROverlayInputMethod InputMethod 115 | { 116 | set 117 | { 118 | AssertNoError(OpenVR.Overlay.SetOverlayInputMethod(_overlayHandle, value)); 119 | } 120 | 121 | get 122 | { 123 | VROverlayInputMethod method = VROverlayInputMethod.None; 124 | AssertNoError(OpenVR.Overlay.GetOverlayInputMethod(_overlayHandle, ref method)); 125 | return method; 126 | } 127 | } 128 | 129 | /// 130 | /// Sets/gets the width of the overlay quad in meters. By default overlays are rendered 131 | /// on a quad that is 1 meter across. 132 | ///

133 | /// Overlays are rendered at the aspect ratio of their underlying texture and texture 134 | /// bounds. Height is a function of width and that aspect ratio. An overlay that is 1.6 135 | /// meters wide and has a 1920x1080 texture would be 0.9 meters tall. 136 | ///
137 | public float WidthInMeters 138 | { 139 | set => AssertNoError(OpenVR.Overlay.SetOverlayWidthInMeters(_overlayHandle, value)); 140 | 141 | get 142 | { 143 | var width = 0.0f; 144 | AssertNoError(OpenVR.Overlay.GetOverlayWidthInMeters(_overlayHandle, ref width)); 145 | return width; 146 | } 147 | } 148 | 149 | 150 | /// 151 | /// Use to draw overlay as a curved surface. Curvature is a percentage from (0..1] where 152 | /// 1 is a fully closed cylinder. 153 | ///
154 | /// For a specific radius, curvature can be computed as: 155 | /// overlay.width / (2 PI r) 156 | ///
157 | public float Curvature 158 | { 159 | set => AssertNoError(OpenVR.Overlay.SetOverlayCurvature(_overlayHandle, value)); 160 | 161 | get 162 | { 163 | var curvature = 0.0f; 164 | AssertNoError(OpenVR.Overlay.GetOverlayCurvature(_overlayHandle, ref curvature)); 165 | return curvature; 166 | } 167 | } 168 | 169 | /// 170 | /// Sets/gets the alpha of the overlay quad. Use 1.0 for 100 percent opacity to 0.0 for 0 171 | /// percent opacity. 172 | ///
173 | /// By default, overlays are rendering at 100 percent alpha (1.0). 174 | ///
175 | public float Alpha 176 | { 177 | set => AssertNoError(OpenVR.Overlay.SetOverlayAlpha(_overlayHandle, value)); 178 | 179 | get 180 | { 181 | var alpha = 0.0f; 182 | AssertNoError(OpenVR.Overlay.GetOverlayAlpha(_overlayHandle, ref alpha)); 183 | return alpha; 184 | } 185 | } 186 | 187 | private HmdMatrix34_t _transform; 188 | public HmdMatrix34_t Transform { 189 | set 190 | { 191 | if (IsDashboardOverlay) return; 192 | 193 | _transform = value; 194 | 195 | // absolute transform (no tracked devices) 196 | if(TrackedDevice == TrackedDeviceRole.None) 197 | { 198 | AssertNoError(OpenVR.Overlay.SetOverlayTransformAbsolute(_overlayHandle, ETrackingUniverseOrigin.TrackingUniverseStanding, ref value)); 199 | return; 200 | } 201 | 202 | // relative transform (oops, we are assuming things) 203 | AssertNoError(OpenVR.Overlay.SetOverlayTransformTrackedDeviceRelative(_overlayHandle, _trackedDeviceIndex, ref value)); 204 | } 205 | 206 | get => _transform; 207 | } 208 | 209 | private TrackedDeviceRole _trackedDevice = TrackedDeviceRole.None; 210 | private uint _trackedDeviceIndex = 0; 211 | 212 | private readonly ulong _overlayHandle; 213 | private readonly ulong _thumbnailHandle; 214 | private Thread _pollThread; 215 | 216 | /// 217 | /// Instantiate a new OpenVR overlay with the specified key 218 | /// and name. 219 | /// 220 | /// 221 | /// 222 | /// The key to create the overlay with. Keys must 223 | /// be globally unique and may not be longer than 224 | /// k_unVROverlayMaxKeyLength including the terminating 225 | /// null. 226 | /// 227 | /// 228 | /// 229 | /// The friendly, user-visible name of the overlay. 230 | /// When appropriate the name should be provided in 231 | /// UTF-8 in the current system language. This name 232 | /// may not be longer than k_unVROverlayMaxNameLength 233 | /// including the terminating null. 234 | /// 235 | public Overlay(string key, string name, bool dashboardOverlay = false) 236 | { 237 | if (OpenVR.Overlay == null) 238 | throw new NullReferenceException("OpenVR has not been initialized. Please initialize it by instantiating a new Application."); 239 | 240 | EVROverlayError err; 241 | 242 | if (dashboardOverlay) 243 | err = OpenVR.Overlay.CreateDashboardOverlay(key, name, ref _overlayHandle, ref _thumbnailHandle); 244 | else 245 | err = OpenVR.Overlay.CreateOverlay(key, name, ref _overlayHandle); 246 | 247 | if (err != EVROverlayError.None) 248 | throw new OpenVRSystemException($"Could not initialize overlay: {err}", err); 249 | 250 | Key = key; 251 | Name = name; 252 | IsDashboardOverlay = dashboardOverlay; 253 | } 254 | 255 | /// 256 | /// Wrap an existing OpenVR overlay with its handle. 257 | /// 258 | /// 259 | /// 260 | /// The handle of the overlay to wrap. 261 | /// 262 | public Overlay(ulong overlayHandle) 263 | { 264 | _overlayHandle = overlayHandle; 265 | 266 | // TODO: set Key and Name accordingly 267 | //StringBuilder keyVal = new StringBuilder((int)OpenVR.k_unVROverlayMaxKeyLength); 268 | //OpenVR.Overlay.GetOverlayKey(overlayHandle, keyVal, keyVal.Length) 269 | } 270 | 271 | /// 272 | /// Start polling for overlay events. While polling, the 273 | /// events , , etc. 274 | /// will be fired as they are received. If this overlay is already polling, 275 | /// this method is a no-op. 276 | /// 277 | ///

278 | /// 279 | /// Use to stop polling for events. 280 | ///
281 | public void StartPolling() 282 | { 283 | if (_pollThread != null) return; 284 | 285 | _pollThread = new Thread(Poll); 286 | _pollThread.Start(); 287 | } 288 | 289 | /// 290 | /// Stop polling for overlay events. If the overlay is 291 | /// not polling, this method is a no-op. 292 | /// 293 | ///

294 | /// 295 | /// Use to begin polling for events. 296 | ///
297 | public void StopPolling() 298 | { 299 | if (_pollThread == null) return; 300 | } 301 | 302 | /// 303 | /// Request that the OpenVR runtime displays the overlay 304 | /// in the scene. For VR Dashboard overlays only the dashboard 305 | /// manager is allowed to call this. 306 | /// 307 | ///

308 | /// 309 | /// Use to hide the overlay. 310 | ///
311 | public void Show() 312 | { 313 | if (IsDashboardOverlay) return; 314 | AssertNoError(OpenVR.Overlay.ShowOverlay(_overlayHandle)); 315 | } 316 | 317 | /// 318 | /// Request that the OpenVR runtime hides the overlay 319 | /// in the scene. For VR Dashboard overlays only the dashboard 320 | /// manager is allowed to call this. 321 | /// 322 | ///

323 | /// 324 | /// Use to show the overlay. 325 | ///
326 | public void Hide() 327 | { 328 | if (IsDashboardOverlay) return; 329 | AssertNoError(OpenVR.Overlay.HideOverlay(_overlayHandle)); 330 | } 331 | 332 | /// 333 | /// Destroy the overlay. When an application calls VR_Shutdown 334 | /// all overlays created by that app are automatically destroyed. 335 | /// 336 | public void Destroy() 337 | { 338 | StopPolling(); 339 | AssertNoError(OpenVR.Overlay.DestroyOverlay(_overlayHandle)); 340 | } 341 | 342 | /// 343 | /// Loads the specified file and sets that texture as the contents 344 | /// of the overlay. Textures can be up to 1920x1080 in size. 345 | /// PNG, JPG, and TGA files are supported in 24 or 32 bits. 346 | /// 347 | /// 348 | /// 349 | /// Path to the file. If a relative path is provided it is assumed 350 | /// to be relative to the resource directory in the OpenVR runtime. 351 | /// 352 | public void SetTextureFromFile(string path) 353 | { 354 | AssertNoError(OpenVR.Overlay.SetOverlayFromFile(_overlayHandle, path)); 355 | } 356 | 357 | /// 358 | /// Sets an existing application-created graphics resource as the 359 | /// texture for the overlay. The type of the pTexture depends on 360 | /// the eTextureType parameter. 361 | /// 362 | /// 363 | /// 364 | /// struct describing the texture 365 | /// 366 | public void SetTexture(Texture_t texture) 367 | { 368 | AssertNoError(OpenVR.Overlay.SetOverlayTexture(_overlayHandle, ref texture)); 369 | } 370 | 371 | /// 372 | /// Loads the specified file and sets that texture as the contents 373 | /// of the overlay's thumbnail. Textures can be up to 1920x1080 in size. 374 | /// PNG, JPG, and TGA files are supported in 24 or 32 bits. 375 | /// 376 | /// 377 | /// 378 | /// Path to the file. If a relative path is provided it is assumed 379 | /// to be relative to the resource directory in the OpenVR runtime. 380 | /// 381 | public void SetThumbnailTextureFromFile(string path) 382 | { 383 | // TODO: these IsDashboardOverlay checks should probably error, not silently return 384 | if (!IsDashboardOverlay) return; 385 | AssertNoError(OpenVR.Overlay.SetOverlayFromFile(_thumbnailHandle, path)); 386 | } 387 | 388 | /// 389 | /// Sets an existing application-created graphics resource as the 390 | /// texture for the overlay's thumbnail. The type of the pTexture depends on 391 | /// the eTextureType parameter. 392 | /// 393 | /// 394 | /// 395 | /// struct describing the texture 396 | /// 397 | public void SetThumbnailTexture(Texture_t texture) 398 | { 399 | if (!IsDashboardOverlay) return; 400 | AssertNoError(OpenVR.Overlay.SetOverlayTexture(_thumbnailHandle, ref texture)); 401 | } 402 | 403 | /// 404 | /// Sets or gets the specified flag for the specified overlay. 405 | /// 406 | /// 407 | /// Flag to set 408 | /// Flag value to set 409 | public void SetFlag(VROverlayFlags flag, bool value) 410 | { 411 | AssertNoError(OpenVR.Overlay.SetOverlayFlag(_overlayHandle, flag, value)); 412 | } 413 | 414 | private static void AssertNoError(EVROverlayError error) 415 | { 416 | if (error == EVROverlayError.None) return; 417 | throw new OpenVRSystemException($"An error occurred within an Overlay. {error}", error); 418 | } 419 | 420 | private void Poll() 421 | { 422 | while (true) 423 | { 424 | var evt = new VREvent_t(); 425 | var size = (uint)System.Runtime.InteropServices.Marshal.SizeOf(typeof(VREvent_t)); 426 | 427 | if (!OpenVR.Overlay.PollNextOverlayEvent(_overlayHandle, ref evt, size)) 428 | { 429 | Thread.Sleep(PollingRate); 430 | continue; 431 | }; 432 | 433 | switch ((EVREventType)evt.eventType) 434 | { 435 | case EVREventType.VREvent_MouseMove: 436 | OnMouseMove?.Invoke(this, evt); 437 | break; 438 | case EVREventType.VREvent_MouseButtonDown: 439 | OnMouseDown?.Invoke(this, evt); 440 | break; 441 | case EVREventType.VREvent_MouseButtonUp: 442 | OnMouseUp?.Invoke(this, evt); 443 | break; 444 | default: 445 | OnUnknown?.Invoke(this, evt); 446 | break; 447 | } 448 | } 449 | } 450 | } 451 | } 452 | -------------------------------------------------------------------------------- /OVRSharp/openvr_api.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OVRTools/OVRSharp/1026f9168a033c7a8100bd13f0775da8e98918d4/OVRSharp/openvr_api.dll -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OVRSharp 2 | 3 | OVRSharp is a high-level idiomatic C# interface for working with the OpenVR API. 4 | 5 | ## Installation 6 | 7 | > **Note:** OVRSharp comes with its own version of `openvr_api.dll` and will automatically copy it to your project's output directory. 8 | 9 | OVRSharp is available on [NuGet](https://www.nuget.org/packages/OVRSharp), so you can install it just like any other NuGet package: 10 | 11 | ```shell 12 | # .NET CLI 13 | dotnet add package OVRSharp 14 | 15 | # VS PowerShell 16 | Install-Package OVRSharp 17 | ``` 18 | 19 | ## Usage 20 | 21 | Check out the [Getting Started](https://github.com/OVRTools/OVRSharp/wiki/Getting-Started) guide to learn how to build your first OpenVR overlay with OVRSharp. 22 | 23 | Right now, our main goal with this is to make working with the overlay API easier and more C#-like, as well as being entirely platform-agnostic. So you could use this all on its own in your own .NET Core project, or throw it into Unity and work with it there. 24 | 25 | Later on, we plan on supporting basically the entirety of the OpenVR API, so you don't need to deal with pointers and stuff. Just write C# as you would expect. 26 | 27 | So you can do this: 28 | 29 | ```csharp 30 | Application app; 31 | 32 | try { 33 | app = new Application(Application.ApplicationType.Overlay); 34 | } catch(OpenVRSystemException e) { 35 | // Errors are exceptions! 36 | } 37 | 38 | var overlay = new Overlay("cool_overlay", "Cool Overlay", true) { 39 | WidthInMeters = 3.8f 40 | }; 41 | 42 | overlay.SetTextureFromFile(@"C:\path\to\file.png"); 43 | overlay.SetThumbnailTextureFromFile(@"C:\path\to\thumb.png"); 44 | ``` 45 | 46 | Instead of this: 47 | 48 | ```csharp 49 | var err = EVRInitError.None; 50 | var vrSystem = OpenVR.Init(ref err, EVRApplicationType.VRApplication_Overlay); 51 | 52 | if (err != EVRInitError.None) 53 | { 54 | // whatever error handling 55 | } 56 | 57 | // Create a dashboard overlay 58 | var overlayErr = EVROverlayError.None; 59 | 60 | ulong overlayHandle; 61 | ulong thumbnailHandle; 62 | 63 | overlayErr = OpenVR.Overlay.CreateDashboardOverlay("cool_overlay", "Cool Overlay", ref overlayHandle, ref thumbnailHandle); 64 | 65 | if (overlayErr != EVROverlayError.None) 66 | { 67 | // whatever error handling 68 | } 69 | 70 | overlayErr = OpenVR.Overlay.SetOverlayWidthInMeters(overlayHandle, 3.8f); 71 | 72 | if (overlayErr != EVROverlayError.None) 73 | { 74 | // whatever error handling 75 | } 76 | 77 | // Set the dashboard overlay up. First, the main overlay's texture 78 | overlayErr = OpenVR.Overlay.SetOverlayFromFile(overlayHandle, @"C:\path\to\file.png"); 79 | 80 | if (overlayErr != EVROverlayError.None) 81 | { 82 | // whatever error handling 83 | } 84 | 85 | // Then the thumbnail. 86 | overlayErr = OpenVR.Overlay.SetOverlayFromFile(thumbnailHandle, @"C:\path\to\thumb.png"); 87 | 88 | if (overlayErr != EVROverlayError.None) 89 | { 90 | // whatever error handling 91 | } 92 | ``` 93 | 94 | If you encounter anything that is not natively supported by OVRSharp, you can always use the internal `Valve.VR` APIs. An instance of `CVRSystem` is accessible through `Application`, as `OVRSystem`. 95 | 96 | If you run into something you would really like to see supported, feel free to open an issue describing your use case! That will help us prioritize what functionality to add next. 97 | 98 | ### Overlay transformations 99 | 100 | Matrix manipulation is not something easy, OVRSharp provides extension methods to convert the [`HmdMatrix34_t`](https://github.com/ValveSoftware/openvr/blob/4c85abcb7f7f1f02adaf3812018c99fc593bc341/headers/openvr.h#L32-L40) structure to and from the [`Matrix4x4`](https://docs.microsoft.com/en-us/dotnet/api/system.numerics.matrix4x4?view=net-5.0) structure. 101 | 102 | The purpose is to ease manipulations, the `Matrix4x4` struct contains [many static methods to apply translations, rotations, etc.](https://docs.microsoft.com/en-us/dotnet/api/system.numerics.matrix4x4?view=net-5.0#methods) That way you can transform your `Matrix4x4` with a high level of abstraction and then convert it to a `HmdMatrix34_t` before passing it to OpenVR. Here's an example: 103 | 104 | ```csharp 105 | using OVRSharp; 106 | using OVRSharp.Math; // Use "Math" to be able call the "ToHmdMatrix34_t()" and "ToMatrix4x4()" methods. 107 | 108 | float radians = (float)((Math.PI / 180) * 90); // 90 degrees to radians 109 | var rotation = Matrix4x4.CreateRotationX(radians); // Lay the overlay flat by rotating it by 90 degrees 110 | var translation = Matrix4x4.CreateTranslation(0, 1, 0); // Raise the overlay one meter above the ground 111 | var transform = Matrix4x4.Multiply(rotation, translation); // Combine the transformations in one matrix 112 | 113 | Overlay overlay = new("key", "name") 114 | { 115 | Transform = transform.ToHmdMatrix34_t(), // Convert the Matrix4x4 to a HmdMatrix34_t and pass it to OpenVR 116 | }; 117 | ``` 118 | 119 | ## Examples 120 | 121 | OVRSharp is currently being used in [WhereIsForward](https://github.com/OVRTools/WhereIsForward), and in some unreleased projects. 122 | 123 | ## License 124 | 125 | MIT 126 | -------------------------------------------------------------------------------- /tests/OVRSharp.Benchmarks/Graphics/CompositorBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using NBench; 2 | using OVRSharp.Graphics.DirectX; 3 | 4 | namespace OVRSharp.Benchmarks.Graphics 5 | { 6 | public class CompositorBenchmarks 7 | { 8 | [PerfSetup] 9 | public void Setup() 10 | { 11 | var app = new Application(Application.ApplicationType.Background); 12 | } 13 | 14 | [PerfBenchmark( 15 | RunMode = RunMode.Iterations, 16 | NumberOfIterations = 100, 17 | TestMode = TestMode.Measurement 18 | )] 19 | [TimingMeasurement()] 20 | [MemoryMeasurement(MemoryMetric.TotalBytesAllocated)] 21 | public void GetMirrorImageBenchmark() 22 | { 23 | DirectXCompositor.Instance.GetMirrorImage(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/OVRSharp.Benchmarks/OVRSharp.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/OVRSharp.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using NBench; 2 | 3 | namespace OVRSharp.Benchmarks 4 | { 5 | class Program 6 | { 7 | static int Main() 8 | { 9 | return NBenchRunner.Run(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/OVRSharp.Graphics.DirectX.Tests/CompositorTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using OVRSharp.Tests.Graphics; 3 | 4 | namespace OVRSharp.Graphics.DirectX.Tests 5 | { 6 | [TestFixture] 7 | public class DirectXCompositorTests : CompositorTests 8 | { 9 | protected override DirectXCompositor InstantiateCompositorAPI() => DirectXCompositor.Instance; 10 | } 11 | } -------------------------------------------------------------------------------- /tests/OVRSharp.Graphics.DirectX.Tests/OVRSharp.Graphics.DirectX.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/OVRSharp.Graphics.OpenGL.Tests/CompositorTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using OVRSharp.Tests.Graphics; 3 | 4 | namespace OVRSharp.Graphics.OpenGL.Tests 5 | { 6 | [TestFixture] 7 | public class OpenGLCompositorTests : CompositorTests 8 | { 9 | protected override OpenGLCompositor InstantiateCompositorAPI() 10 | { 11 | return new OpenGLCompositor(); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /tests/OVRSharp.Graphics.OpenGL.Tests/OVRSharp.Graphics.OpenGL.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/OVRSharp.Tests/Graphics/CompositorTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | using OVRSharp.Graphics; 4 | using OVRSharp.Exceptions; 5 | using Valve.VR; 6 | 7 | namespace OVRSharp.Tests.Graphics 8 | { 9 | /// 10 | /// Tests to run against implementations. 11 | /// 12 | /// Your implementation of to test. 13 | public abstract class CompositorTests where T : ICompositorAPI 14 | { 15 | private Application app; 16 | private ICompositorAPI compositor; 17 | 18 | /// 19 | /// Instantiates the instance of your 20 | /// that will be used for testing. 21 | /// 22 | protected abstract T InstantiateCompositorAPI(); 23 | 24 | [OneTimeSetUp] 25 | public void Setup() 26 | { 27 | try 28 | { 29 | app = new Application(Application.ApplicationType.Background); 30 | } 31 | catch(OpenVRSystemException e) 32 | { 33 | switch(e.Error) 34 | { 35 | case EVRInitError.Init_InstallationNotFound: 36 | case EVRInitError.Init_VRClientDLLNotFound: 37 | Assert.Ignore("OpenVR runtime not found; skipping integration tests."); 38 | break; 39 | case EVRInitError.Init_NoServerForBackgroundApp: 40 | Assert.Ignore("OpenVR runtime not running; skipping integration tests."); 41 | break; 42 | default: 43 | throw; 44 | } 45 | } 46 | 47 | compositor = InstantiateCompositorAPI(); 48 | } 49 | 50 | [Test] 51 | [TestCase(EVREye.Eye_Left)] 52 | [TestCase(EVREye.Eye_Right)] 53 | public void ShouldGetMirrorTextureSuccessfully(EVREye eye) 54 | { 55 | var bitmap = compositor.GetMirrorImage(eye); 56 | bitmap.Height.Should().BeGreaterThan(0); 57 | bitmap.Width.Should().BeGreaterThan(0); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/OVRSharp.Tests/OVRSharp.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | --------------------------------------------------------------------------------