├── .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 |
--------------------------------------------------------------------------------