// Copyright(c) 2019-2025 pypy, Natsumi and individual contributors. // All rights reserved. // // This work is licensed under the terms of the MIT license. // For a copy, see . using CefSharp; using CefSharp.Enums; using CefSharp.OffScreen; using CefSharp.Structs; using SharpDX.Direct3D11; using System; using System.Threading; using NLog; using SharpDX.Direct3D; using SharpDX.Mathematics.Interop; using Range = CefSharp.Structs.Range; namespace VRCX { public class OffScreenBrowser : ChromiumWebBrowser, IRenderHandler { private Device _device; private Device1 _device1; private DeviceMultithread _deviceMultithread; private Query _query; private Texture2D _renderTarget; private static readonly Logger logger = LogManager.GetCurrentClassLogger(); public OffScreenBrowser(string address, int width, int height) : base(address, automaticallyCreateBrowser: false) { var windowInfo = new WindowInfo(); windowInfo.SetAsWindowless(IntPtr.Zero); windowInfo.WindowlessRenderingEnabled = true; windowInfo.SharedTextureEnabled = true; windowInfo.Width = width; windowInfo.Height = height; var browserSettings = new BrowserSettings() { DefaultEncoding = "UTF-8", WindowlessFrameRate = 60 }; CreateBrowser(windowInfo, browserSettings); Size = new System.Drawing.Size(width, height); RenderHandler = this; JavascriptBindings.ApplyVrJavascriptBindings(JavascriptObjectRepository); } public void UpdateRender(Device device, Texture2D renderTarget) { _device = device; _device1 = _device.QueryInterface(); _deviceMultithread?.Dispose(); _deviceMultithread = _device.QueryInterfaceOrNull(); _deviceMultithread?.SetMultithreadProtected(true); _renderTarget = renderTarget; _query?.Dispose(); _query = new Query(_device, new QueryDescription { Type = QueryType.Event, Flags = QueryFlags.None }); } public new void Dispose() { RenderHandler = null; base.Dispose(); } ScreenInfo? IRenderHandler.GetScreenInfo() { return new ScreenInfo { DeviceScaleFactor = 1.0F }; } bool IRenderHandler.GetScreenPoint(int viewX, int viewY, out int screenX, out int screenY) { screenX = viewX; screenY = viewY; return false; } Rect IRenderHandler.GetViewRect() { return new Rect(0, 0, Size.Width, Size.Height); } void IRenderHandler.OnAcceleratedPaint(PaintElementType type, Rect dirtyRect, AcceleratedPaintInfo paintInfo) { if (type != PaintElementType.View) return; if (_device == null) return; try { using Texture2D cefTexture = _device1.OpenSharedResource1(paintInfo.SharedTextureHandle); _device.ImmediateContext.CopyResource(cefTexture, _renderTarget); _device.ImmediateContext.End(_query); _device.ImmediateContext.Flush(); } catch (Exception e) { logger.Error(e); _device = null; return; } RawBool q = _device.ImmediateContext.GetData(_query, AsynchronousFlags.DoNotFlush); while (!q) { Thread.Yield(); q = _device.ImmediateContext.GetData(_query, AsynchronousFlags.DoNotFlush); } } void IRenderHandler.OnCursorChange(IntPtr cursor, CursorType type, CursorInfo customCursorInfo) { } void IRenderHandler.OnImeCompositionRangeChanged(Range selectedRange, Rect[] characterBounds) { } void IRenderHandler.OnPaint(PaintElementType type, Rect dirtyRect, IntPtr buffer, int width, int height) { } void IRenderHandler.OnPopupShow(bool show) { } void IRenderHandler.OnPopupSize(Rect rect) { } void IRenderHandler.OnVirtualKeyboardRequested(IBrowser browser, TextInputMode inputMode) { } bool IRenderHandler.StartDragging(IDragData dragData, DragOperationsMask mask, int x, int y) { return false; } void IRenderHandler.UpdateDragCursor(DragOperationsMask operation) { } } }