Multi-platform .NET bindings to the Ultralight project.
at master 16 kB view raw
1using Silk.NET.OpenGLES; 2using System; 3using System.Diagnostics; 4using System.Drawing; 5using System.IO; 6using System.Runtime.CompilerServices; 7using System.Runtime.ExceptionServices; 8using System.Runtime.InteropServices; 9using System.Text; 10using CommandLine; 11using ImpromptuNinjas.UltralightSharp.Safe; 12using InlineIL; 13using Nvidia.Nsight.Injection; 14using Silk.NET.Core; 15using Silk.NET.EGL; 16using Silk.NET.GLFW; 17using Silk.NET.Windowing.Common; 18using Ultz.SuperInvoke; 19using Ultz.SuperInvoke.Loader; 20using ErrorCode = Silk.NET.GLFW.ErrorCode; 21using Renderer = ImpromptuNinjas.UltralightSharp.Safe.Renderer; 22using Window = Silk.NET.Windowing.Window; 23 24partial class Program { 25 26 static Program() { 27 // ReSharper disable once InvertIf 28 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { 29 Windows.User32.SetProcessDPIAware(); 30 Windows.User32.SetProcessDpiAwarenessContext(Windows.User32.DpiAwarenessContext.PerMonitorAwareV2); 31 Windows.ShCore.SetProcessDpiAwareness(Windows.ShCore.ProcessDpiAwareness.PerMonitorDpiAware); 32 } 33 } 34 35 private static int _majOES = 3; 36 37 private static readonly string AsmPath = new Uri(typeof(Program).Assembly.CodeBase!).LocalPath; 38 39 private static readonly string AsmDir = Path.GetDirectoryName(AsmPath)!; 40 41 private static readonly string AssetsDir = Path.Combine(AsmDir, "assets"); 42 43 private static IView _snView = null!; 44 45 private static GL _gl = null!; 46 47 private static uint _qvb; 48 49 private static uint _qeb; 50 51 private static uint _qva; 52 53 private static uint _qpg; 54 55 private static Renderer _ulRenderer = null!; 56 57 private static Session _ulSession = null!; 58 59 private static View _ulView = null!; 60 61 private static Glfw _glfw = null!; 62 63 private static string _storagePath = null!; 64 65 private static bool _useOpenGl; 66 67 private static bool _automaticFallback = true; 68 69 private static bool _useEgl = true; 70 71 private static int _glMaj = 3; 72 73 private static int _glMin = 2; 74 75 private static unsafe void Main(string[] args) { 76 var parsedOpts = Parser.Default.ParseArguments<Options>(args); 77 78 parsedOpts.WithParsed(opts => { 79 if (opts.Context != null) { 80 _useEgl = opts.Context.Contains("egl"); 81 } 82 83 if (opts.GlApi != null) { 84 _automaticFallback = false; 85 _useOpenGl = !opts.GlApi.Contains("es"); 86 } 87 88 if (opts.GlMajorVersion != null) { 89 _automaticFallback = false; 90 _glMaj = opts.GlMajorVersion.Value; 91 } 92 93 if (opts.GlMinorVersion != null) { 94 _automaticFallback = false; 95 _glMin = opts.GlMinorVersion.Value; 96 } 97 }); 98 99 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { 100 Console.OutputEncoding = Encoding.UTF8; 101 Ansi.WindowsConsole.TryEnableVirtualTerminalProcessing(); 102 } 103 104 AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; 105 AppDomain.CurrentDomain.FirstChanceException += OnFirstChanceException; 106 107 //InjectNsight(); 108 109 //InjectRenderDoc(); 110 111 var options = WindowOptions.Default; 112 113 var size = new Size(1024, 576); 114 115 var title = "UltralightSharp - Silk.NET"; 116 117 options.Size = size; 118 options.Title = title; 119 options.VSync = VSyncMode.On; 120 options.TransparentFramebuffer = false; 121 options.PreferredDepthBufferBits = null; 122 //options.VSync = true; 123 124 /* 125 if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { 126 var asmDir = Path.GetDirectoryName(new Uri(typeof(Program).Assembly.CodeBase!).LocalPath)!; 127 var glfwPath = Path.Combine(asmDir, "libglfw.so.3"); 128 const string sysGlfwPath = "/lib/libglfw.so.3"; 129 if (File.Exists(sysGlfwPath)) { 130 var sb = new StringBuilder(1024); 131 var sbSize = (UIntPtr)sb.Capacity; 132 var used = (long)Libc.readlink(glfwPath, sb, sbSize); 133 if (used >= 0) { 134 var link = sb.ToString(0, (int)(used - 1)); 135 if (link != sysGlfwPath) { 136 File.Delete(glfwPath); 137 Libc.symlink(sysGlfwPath, glfwPath); 138 } 139 } 140 else { 141 // not a link 142 File.Delete(glfwPath); 143 Libc.symlink(sysGlfwPath, glfwPath); 144 Cleanup += () => { 145 File.Delete(glfwPath); 146 }; 147 } 148 } 149 } 150 */ 151 152 _glfw = Glfw.GetApi(); 153 Console.WriteLine($"GLFW v{_glfw.GetVersionString()}"); 154 155 if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { 156 _glfw.InitHint(InitHint.CocoaMenubar, false); 157 _glfw.InitHint(InitHint.CocoaChdirResources, false); 158 } 159 160 _glfw = GlfwProvider.GLFW.Value; 161 162 { 163 // setup logging 164 Ultralight.SetLogger(new Logger {LogMessage = LoggerCallback}); 165 166 var tempDir = Path.GetTempPath(); 167 // find a place to stash instance storage 168 do _storagePath = Path.Combine(tempDir, Guid.NewGuid().ToString()); 169 while (Directory.Exists(_storagePath) || File.Exists(_storagePath)); 170 171 AppCore.EnablePlatformFontLoader(); 172 173 AppCore.EnablePlatformFileSystem(AssetsDir); 174 } 175 176 /* afaik GLFW already does this, this was just to check 177 if (_useEgl || _automaticFallback) { 178 Console.WriteLine("Binding to LibEGL..."); 179 var eglLib = new UnmanagedLibrary( 180 new CustomEglLibNameContainer().GetLibraryName(), 181 LibraryLoader.GetPlatformDefaultLoader() 182 ); 183 var q = eglLib.LoadFunction("eglQueryAPI"); 184 IL.Push(q); 185 IL.Emit.Calli(StandAloneMethodSig.UnmanagedMethod(CallingConvention.Cdecl, typeof(EGLEnum))); 186 IL.Pop(out EGLEnum api); 187 Console.WriteLine($"EGL API Target: {api}"); 188 var b = eglLib.LoadFunction("eglBindAPI"); 189 if (_useOpenGL && api != EGLEnum.OpenglApi) { 190 IL.Push(EGLEnum.OpenglApi); 191 IL.Push(b); 192 IL.Emit.Calli(StandAloneMethodSig.UnmanagedMethod(CallingConvention.Cdecl, typeof(bool), 193 typeof(EGLEnum))); 194 IL.Pop(out bool success); 195 Console.Error.WriteLine(!success 196 ? "Couldn't bind EGL to OpenGL" 197 : "EGL now bound to OpenGL"); 198 } 199 else if (!_useOpenGL && api != EGLEnum.OpenglESApi){ 200 IL.Push(EGLEnum.OpenglESApi); 201 IL.Push(b); 202 IL.Emit.Calli(StandAloneMethodSig.UnmanagedMethod(CallingConvention.Cdecl, typeof(bool), 203 typeof(EGLEnum))); 204 IL.Pop(out bool success); 205 Console.Error.WriteLine(!success 206 ? "Couldn't bind EGL to OpenGL ES" 207 : "EGL now bound to OpenGL ES"); 208 } 209 } 210 */ 211 212 if (_automaticFallback) { 213 Console.WriteLine("Checking for supported context..."); 214 215 for (;;) { 216 SetGlfwWindowHints(); 217 218 Console.WriteLine( 219 _useOpenGl 220 ? "Attempting OpenGL v3.2 (Core)" 221 : $"Attempting OpenGL ES v{_majOES}.0"); 222 var wh = _glfw.CreateWindow(1024, 576, title, null, null); 223 if (wh != null) { 224 Console.WriteLine( 225 _useOpenGl 226 ? "Created Window with OpenGL v3.2 (Core)" 227 : $"Created Window with OpenGL ES v{_majOES}.0"); 228 _glfw.DestroyWindow(wh); 229 break; 230 } 231 232 var code = _glfw.GetError(out char* pDesc); 233 if (code == ErrorCode.NoError || pDesc == null) 234 throw new PlatformNotSupportedException("Can't create a window via GLFW. Unknown error."); 235 236 var strLen = new ReadOnlySpan<byte>((byte*) pDesc, 32768).IndexOf<byte>(0); 237 if (strLen == -1) strLen = 0; 238 var str = new string((sbyte*) pDesc, 0, strLen, Encoding.UTF8); 239 var errMsg = $"{code}: {str}"; 240 Console.Error.WriteLine(errMsg); 241 if (code != ErrorCode.VersionUnavailable) 242 throw new GlfwException(errMsg); 243 244 // attempt sequence: OpenGL ES 3.0, OpenGL 3.2, OpenGL ES 2.0 245 if (!_useOpenGl && _majOES == 3) 246 _useOpenGl = true; 247 else if (_majOES == 3 && _useOpenGl) { 248 _useOpenGl = false; 249 _majOES = 2; 250 } 251 else 252 throw new GlfwException(errMsg); 253 } 254 } 255 256 SetGlfwWindowHints(); 257 258 if (_useOpenGl) 259 options.API = new GraphicsAPI( 260 ContextAPI.OpenGL, 261 ContextProfile.Core, 262 ContextFlags.ForwardCompatible, 263 new APIVersion(_automaticFallback ? 3 : _glMaj, _automaticFallback ? 2 : _glMin) 264 ); 265 else 266 options.API = new GraphicsAPI( 267 ContextAPI.OpenGLES, 268 ContextProfile.Core, 269 ContextFlags.ForwardCompatible, 270 new APIVersion(_automaticFallback ? _majOES : _glMaj, _automaticFallback ? 0 : _glMin) 271 ); 272 options.IsVisible = true; 273 options.WindowBorder = WindowBorder.Resizable; 274 options.WindowState = WindowState.Normal; 275 276 Console.WriteLine("Creating window..."); 277 278 _snView = Window.Create(options); 279 280 _snView.Load += OnLoad; 281 _snView.Render += OnRender; 282 _snView.Update += OnUpdate; 283 _snView.Closing += OnClose; 284 _snView.Resize += OnResize; 285 286 var glCtx = _snView.GLContext; 287 288 if (!_useOpenGl) { 289 Console.WriteLine("Binding to LibGLES..."); 290 _gl = LibraryActivator.CreateInstance<GL> 291 ( 292 new CustomGlEsLibNameContainer().GetLibraryName(), 293 TemporarySuperInvokeClass.GetLoader(glCtx) 294 ); 295 } 296 297 Console.WriteLine("Initializing window..."); 298 299 _snView.Initialize(); 300 301 if (_snView.Handle == null) { 302 var code = _glfw.GetError(out char* pDesc); 303 if (code == ErrorCode.NoError || pDesc == null) 304 throw new PlatformNotSupportedException("Can't create a window via GLFW. Unknown error."); 305 306 var strLen = new ReadOnlySpan<byte>((byte*) pDesc, 32768).IndexOf<byte>(0); 307 if (strLen == -1) strLen = 0; 308 var str = new string((sbyte*) pDesc, 0, strLen, Encoding.UTF8); 309 throw new GlfwException($"{code}: {str}"); 310 } 311 312 Console.WriteLine("Starting main loop..."); 313 _snView.Run(); 314 } 315 316 private static void SetGlfwWindowHints() { 317 _glfw.DefaultWindowHints(); 318 if (_useOpenGl) { 319 _glfw.WindowHint(WindowHintContextApi.ContextCreationApi, 320 _automaticFallback || !_useEgl ? ContextApi.NativeContextApi : ContextApi.EglContextApi); 321 _glfw.WindowHint(WindowHintClientApi.ClientApi, ClientApi.OpenGL); 322 _glfw.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Core); 323 324 _glfw.WindowHint(WindowHintInt.ContextVersionMajor, 3); 325 _glfw.WindowHint(WindowHintInt.ContextVersionMinor, 2); 326 } 327 else { 328 _glfw.WindowHint(WindowHintContextApi.ContextCreationApi, 329 _automaticFallback || _useEgl ? ContextApi.EglContextApi : ContextApi.NativeContextApi); 330 _glfw.WindowHint(WindowHintClientApi.ClientApi, ClientApi.OpenGLES); 331 332 _glfw.WindowHint(WindowHintInt.ContextVersionMajor, _majOES); 333 _glfw.WindowHint(WindowHintInt.ContextVersionMinor, 0); 334 } 335 336 _glfw.WindowHint(WindowHintInt.RedBits, 8); 337 _glfw.WindowHint(WindowHintInt.GreenBits, 8); 338 _glfw.WindowHint(WindowHintInt.BlueBits, 8); 339 _glfw.WindowHint(WindowHintInt.DepthBits, 24); 340 _glfw.WindowHint(WindowHintInt.StencilBits, 8); 341 342 if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { 343 // osx graphics switching 344 _glfw.WindowHint((WindowHintBool) 0x00023003, true); 345 } 346 } 347 348 public static event Action? Cleanup; 349 350 private static unsafe void OnFirstChanceException(object? sender, FirstChanceExceptionEventArgs eventArgs) { 351 var sf = new StackFrame(1, true); 352 Console.Error.WriteLine("First-Chance Exception Stack Frame:"); 353 Console.Error.WriteLine(sf.ToString()); 354 Console.Error.WriteLine($"First-Chance Exception: {eventArgs.Exception.GetType().Name}: {eventArgs.Exception.Message}"); 355 } 356 357 private static unsafe void OnUnhandledException(object sender, UnhandledExceptionEventArgs eventArgs) { 358 var st = new StackTrace(1, true); 359 Console.Error.WriteLine("Unhandled Exception Entry Stack:"); 360 Console.Error.WriteLine(st.ToString()); 361 st = new StackTrace((Exception) eventArgs.ExceptionObject, 0, true); 362 Console.Error.WriteLine("Unhandled Exception:"); 363 Console.Error.WriteLine(st.ToString()); 364 if (eventArgs.IsTerminating) { 365 Cleanup?.Invoke(); 366 } 367 } 368 369 private static unsafe void OnResize(Size size) { 370 _ulView.Resize((uint) size.Width, (uint) size.Height); 371 } 372 373 private static void InjectRenderDoc() { 374 try { 375 Console.WriteLine("Injecting RenderDoc..."); 376 using var proc = Process.GetCurrentProcess(); 377 var ps = Process.Start(@"\\?\C:\Program Files\RenderDoc\renderdoccmd.exe", $"inject --PID={proc.Id} --opt-disallow-vsync --opt-disallow-fullscreen --opt-ref-all-resources"); 378 ps?.WaitForExit(250); 379 //Process.Start(@"\\?\C:\Program Files\RenderDoc\qrenderdoc.exe", $"inject --PID={proc.Id}"); 380 } 381 catch (Exception ex) { 382 Console.Error.WriteLine("Couldn't inject RenderDoc"); 383 Console.Error.WriteLine(ex.ToString()); 384 Console.Error.Flush(); 385 } 386 } 387 388 private static unsafe void InjectNsight() { 389 try { 390 Console.WriteLine("Injecting Nsight..."); 391 NativeLibrary.Load(@"C:\Program Files\NVIDIA Corporation\Nsight Graphics 2020.3.1\SDKs\NsightGraphicsSDK\0.7.0\lib\x64\NGFX_Injection.dll"); 392 var installCount = 0; 393 var r = Nsight.EnumerateInstallations((uint*) &installCount, null); 394 if (r != NsightInjectionResult.Ok) throw new Exception(r.ToString()); 395 396 var installs = stackalloc NsightInjectionInstallationInfo[installCount]; 397 r = Nsight.EnumerateInstallations((uint*) &installCount, installs); 398 if (r != NsightInjectionResult.Ok) throw new Exception(r.ToString()); 399 400 var injected = false; 401 for (var x = 0; x < installCount; ++x) { 402 var activityCount = 0; 403 var install = &installs[x]; 404 r = Nsight.EnumerateActivities(install, (uint*) &activityCount, null); 405 if (r != NsightInjectionResult.Ok) throw new Exception(r.ToString()); 406 407 // ReSharper disable once StackAllocInsideLoop 408 var activities = stackalloc NsightInjectionActivity[activityCount]; 409 r = Nsight.EnumerateActivities(install, (uint*) &activityCount, activities); 410 if (r != NsightInjectionResult.Ok) throw new Exception(r.ToString()); 411 412 for (var i = 0; i < activityCount; ++i) { 413 var activity = &activities[i]; 414 if (activity->Type != NsightInjectionActivityType.FrameDebugger) 415 continue; 416 417 r = Nsight.InjectToProcess(install, activity); 418 if (r != NsightInjectionResult.Ok) throw new Exception(r.ToString()); 419 420 injected = true; 421 break; 422 } 423 424 if (injected) 425 break; 426 } 427 428 if (!injected) 429 throw new Exception("Found no injectable FrameDebugger activity."); 430 } 431 catch (Exception ex) { 432 Console.Error.WriteLine("Couldn't inject Nsight"); 433 Console.Error.WriteLine(ex.ToString()); 434 Console.Error.Flush(); 435 } 436 } 437 438 private static void LabelObject(ObjectIdentifier objId, uint vao, string name) { 439 if (_dbg == null) 440 return; 441 442 _dbg.ObjectLabel(objId, vao, (uint) name.Length, name); 443 CheckGl(); 444 } 445 446 private static void OnUpdate(double obj) { 447 _ulRenderer.Update(); 448 } 449 450 private static void OnClose() { 451 //Remember to delete the buffers. 452 _ulView.Dispose(); 453 _ulRenderer.Dispose(); 454 _ulSession.Dispose(); 455 _gl.DeleteBuffer(_qvb); 456 _gl.DeleteBuffer(_qeb); 457 _gl.DeleteVertexArray(_qva); 458 _gl.DeleteProgram(_qpg); 459 } 460 461 private static void CheckGl([CallerLineNumber] int lineNumber = default) { 462#if DEBUG 463 var error = _gl.GetError(); 464 if (error == default) 465 return; 466 467 Console.Error.WriteLine($"Line {lineNumber}, GL Error: {error}"); 468 Console.Error.Flush(); 469 //Debugger.Break(); 470#endif 471 } 472 473}