Conversation
Extract a WPF-free, cross-platform core from LibDmd so it can be consumed on non-Windows targets and published as a NuGet package. - Split ImageUtil into a bitmap half (ImageUtil.cs, excluded from the core) and portable array-only methods (ImageUtil.Portable.cs) via a partial class. - Add LibDmd.Core project that compiles the portable sources with shims (DmdColor, DmdColors, Analytics, WpfNamespaceMarker) and a NativeLibraryLoader for resolving native dependencies. - Move shared types (ScalerMode, WrongFormatException, PluginConfig) into standalone files and guard WPF-only paths so they compile in both flavors. - Add LibDmd.Core.Harness console app for exercising the core. - Add native NuGet packaging (nuspecs and targets for win-x64, linux-x64, osx-x64, osx-arm64), build-nuget scripts, and a build-core-nuget GitHub Actions workflow.
Greptile SummaryThis PR adds a cross-platform LibDmd.Core package path. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (6): Last reviewed commit: "core: Vendor macOS libSDL2 dylibs for th..." | Re-trigger Greptile |
Introduce a Windows-specific project that renders DMD frames to a native window without WPF. NativeWindowDestination hosts a Win32 window on a dedicated STA thread and implements the Gray2/Gray4/ Gray8/Rgb24/Rgb565 destination interfaces, while NativeOpenGlRenderer uploads frames to an OpenGL texture with an optional dot-matrix shader. Wire the project into DmdExtensions.sln and LibDmd.Core.slnx, and align NLog to 4.7.15 across LibDmd.Core and the new project.
Replace the single-pass DMD shader with the full dmdext render pipeline using offscreen framebuffers. Two separable blur passes generate dot-glow and back-glow textures from the source, which the DMD fragment shader then composites with per-dot rounded boxes, brightness and gamma to match dmdext's virtual DMD appearance. - Add framebuffer/render-texture management and the GL entry points required for it (glGenFramebuffers, glActiveTexture, etc.). - Upload the source frame flipped into a reusable buffer so the offscreen passes and final quad share a consistent orientation. - Rename shader/texture members from the old single-program model to the multi-program pipeline and update related log messages.
Move the DMD glow rendering (shaders, framebuffers, blur passes and GL function bindings) out of NativeOpenGlRenderer into a new VirtualDmdOpenGlPipeline class. NativeOpenGlRenderer now owns only the window context and delegates frame rendering to the pipeline, making the glow pipeline reusable across renderers.
Move VirtualDmdOpenGlPipeline into LibDmd (linked back into LibDmd.Core.Windows) and make it public so the WPF VirtualDmdControl can reuse it. When a frame is available, the control now converts it to RGBA and draws via the shared pipeline, falling back to the legacy SharpGL path otherwise. Add RGBA conversion helpers for Gray2/4/8, ColoredGray6 and Rgb24 frames, and dispose the shared pipeline on teardown.
Map the WPF DmdStyle (dot size, rounding, sharpness, glow, brightness, gamma, glass) into a new VirtualDmdRenderStyle and feed it to the shared OpenGL pipeline. Build the DMD fragment shader dynamically from the active style, gating dot overlap, glow, brightness, unlit dots, glass and gamma behind #define blocks so each effect is only compiled when used. Recompile the program when the style changes and add glass texture and color uniforms (glUniform4f) to support glass lighting.
Upload the glass overlay as a dedicated RGBA texture bound to its own texture unit instead of reusing the source texture, and gate the GLASS shader define on the glass data actually being present. The WPF control converts the loaded bitmap to RGBA and forwards it to the pipeline, reapplying it whenever the pipeline is recreated.
The LogFrame diagnostics that logged the first non-empty and first empty frame per format were temporary debugging aids. Remove them along with the now-unused tracking fields.
Extend NativeWindowDestination with configurable window position, size, stay-on-top behavior, and render style. New constructor overloads and ConfigureWindow/ConfigureRenderStyle let callers position the window and update its style at runtime. Track window layout by handling WM_MOVE, WM_SIZE, and the enter/exit size-move messages, reading back the actual client rect so the reported placement stays in sync after user drag/resize. Thread the render style through NativeOpenGlRenderer so the shared OpenGL pipeline can be restyled without recreating the renderer.
Add render/configure pending flags so repeated paint and restyle requests collapse into a single posted window message instead of queuing duplicates. Move window configuration onto the UI thread via a WM_CONFIGURE_WINDOW message and skip rendering while the window is being moved or resized. Replace the per-message SyncRoot locks with a shared TryGetDestination helper.
Switch the native DMD window to a WS_POPUP style with a thick frame so it has no title bar or border chrome. Drag the window by its body via WM_NCHITTEST returning HTCAPTION, and resize from a bottom-right grip returning HTBOTTOMRIGHT. Strip the non-client area with WM_NCCALCSIZE and drop the AdjustWindowRect calls since client and window rects now coincide.
Refactor NativeWindowDestination from a single Win32-bound implementation into a thin facade over an INativeWindowBackend abstraction, selecting a Win32, SDL, or null backend at runtime. This lifts the native DMD window off Windows-only Win32/STA-thread plumbing so it can render on macOS/Linux through SDL. - Add INativeWindowBackend with Win32, SDL, and null backends - Move the Win32 message loop and the SDL window/event loop into their own backend classes - Make VirtualDmdOpenGlPipeline backend-agnostic by injecting a GL function loader, so SDL can supply its own GL entry points - Wire a --native-window mode into the harness and reference LibDmd.Core.Windows; copy per-RID native libs and allow AnyCPU for osx-arm64 - Fix Serum_Load null check to compare against IntPtr.Zero
Move ConfigureRenderStyle (and SDL window configuration) off the caller thread and onto the message/render loop. The SDL backend now sets pending flags applied in the loop, and the Win32 backend posts a WM_CONFIGURE_RENDER_STYLE message handled in the window procedure. This keeps all window mutations on the UI thread. In AbstractConverter, guard the last-frame fields with a lock so the clock Tick and Convert() callers no longer race. Also return the previously stored DMD frame to the pool on replace and on Dispose, fixing a frame pool leak.
Introduce a WPF/GL-free interface that the VPE bridge can talk to, along with neutral DmdWindowLayout/DmdWindowStyle types and a reflection-based NativeDmdWindow.TryCreate factory that resolves the platform implementation at runtime (returning null where no backend ships). Implement INativeDmdWindow on NativeWindowDestination and add a strongly-typed constructor so consumers never touch reflection or the GL-coupled style type. Self-driven backends report RequiresHostPump false and no-op Pump(); the upcoming host-pumped (macOS) backend will override both.
Introduce LibDmd.Core.Sdl, a host-pumped native DMD window for macOS/Linux (and mobile-capable) built on SDL2 + an OpenGL ES 2.0 port of the dmdext virtual-DMD renderer (dot SDF shader plus the two-stage gaussian glow) running through ANGLE. The backend constructs on the host main thread (SDL/window/GL context init), buffers frames on the worker thread, and renders on the host pump. Glass overlay is not yet ported. Wire NativeDmdWindow to select a backend per OS: prefer the Win32 window on Windows and fall back to SDL, use SDL elsewhere. Wrap construction in a try/catch so a present-but-unusable backend (missing SDL/ANGLE runtime) returns null instead of throwing.
…back. Make cross-thread state safe and fix GL resource leaks in the native DMD window backends, plus let the factory fall through to the next backend when one fails to initialize. - Mark _disposed (and Win32 _isMovingOrSizing) volatile, since render methods run on the worker/rotation thread while Dispose runs on the main/window thread. - Snapshot _color once per frame; SetColor may run on another thread during color rotation. - GlesDmdPipeline: delete the old shader program before rebuilding on style change, and free all GL programs/textures/buffers on dispose; reuse a letterbox rect array to avoid per-frame allocation. - Filter SDL mouse events by window id so a shared global event queue (Unity or another plugin) can't drive our borderless-drag. - TryCreate now attempts every candidate backend in preference order, logging and skipping any that throws during construction. - Remove the obsolete self-driven SdlNativeWindowDestination; macOS and Linux use the host-pumped GL-ES backend in LibDmd.Core.Sdl, so this assembly's backend is Windows-only. - Document the threading contract on INativeDmdWindow.
Add the build asset group to the native RID dependencies so the .targets files actually flow to consumers, and restrict the osx-x64 and osx-arm64 copy steps to their matching process architecture to avoid copying the wrong binaries on Apple Silicon vs Intel.
Split the PIN2DMD USB transport per build so LibDmd.Core can drive the device without the WPF-bound LibUsbDotNet 2.x dependency. - Make Pin2DmdBase partial and guard the LibUsbDotNet path with #if !LIBDMD_CORE (Init/RenderRaw/ReadConfig/Dispose and the _pin2DmdDevice field). - Add LibDmd.Core's libusb-1.0 counterpart in Pin2DmdBase.Usb3.cs: enumerate by VID/PID, match the product string, claim the interface, bulk-transfer frames and read config. - Add a thin libusb-1.0 P/Invoke binding (LibUsb.cs), netstandard2.1 and IL2CPP-safe, consistent with the libserum/libzedmd bindings. - Resolve libusb-1.0 per-OS in NativeLibraryLoader (it doesn't follow the "<name>64" naming convention). - Link the shared Pin2Dmd sources into LibDmd.Core.
Split the FTDI and USB transports per build so LibDmd.Core gets WPF-free, cross-platform paths while the legacy code stays intact: - PinDmd1: new ftd2xx (D2XX) P/Invoke binding and a partial-class Ftdi transport; the FTD2XX_NET path in PinDmd1.cs is now #if !LIBDMD_CORE. - PinDmd2: new libusb-1.0 USB transport (shares the PIN2DMD vendor id, told apart by the "pinDMD V2" product string); the LibUsbDotNet 2.x path in PinDmd2.cs is now #if !LIBDMD_CORE. - Link the shared PinDmd1/PinDmd2 sources into LibDmd.Core and register ftd2xx with NativeLibraryLoader. Vendor libusb-1.0 (all four RIDs) and win-x64 ftd2xx.dll under native/vendor/, staged by fetch-natives.sh with a warning when libusb-1.0 is missing. macOS/Linux libftd2xx must be dropped in manually (FTDI gates downloads); see native/vendor/README.md.
Unity's Mono runtime cannot load the real net6.0+ System.IO.Ports
("TypeLoadException: Invalid type System.IO.Ports.SerialPort"), which
breaks LibDmd's Pixelcade and PinDMD3 serial drivers.
Add a drop-in replacement assembly named System.IO.Ports (netstandard2.0,
AssemblyVersion 8.0.0.0) that exposes the minimal SerialPort surface those
drivers use, backed by the native libserialport already shipped with
dmd-extensions. LibDmd.Core compiles against the real reference assembly;
at runtime Unity loads only this shim from Plugins, so the drivers bind to
it transparently.
The osx-arm64/osx-x64 targets gated the native dylib copy solely on the build host's ProcessArchitecture. That pulled the wrong-arch dylibs during cross-RID publishes (e.g. `publish -r osx-x64` on an Apple Silicon host, or `-r osx-arm64` on Intel). Prefer the requested RuntimeIdentifier's architecture when a RID is set, and fall back to the host ProcessArchitecture only for non-RID builds.
Extract the dot/glow/glass fragment shaders and blur kernels into a shared DmdShaderSource so the desktop OpenGL pipeline (GLSL 1.20) and the SDL GL ES pipeline (GLSL ES 1.00) render identically and can't drift. Each pipeline now prepends only its own profile header and keeps its own vertex shader; bodies and the #define/const config block are shared verbatim. Link DmdShaderSource into the Core.Sdl, Core.Windows, and LibDmd projects, and rename the GL ES blur uniforms (uTex/uDir -> texture/ direction) to match the shared source. Add glass-overlay parity to the GL ES pipeline: SetGlassTexture/ ClearGlassTexture, the GLASS-path uniforms, and RGBA upload, mirroring the desktop renderer.
Switch the harness from the Win32-only NativeWindowDestination to the NativeDmdWindow factory, which resolves the Win32 window on Windows and the host-pumped SDL/GL-ES (ANGLE) window on macOS/Linux. Reference LibDmd.Core.Sdl so the backend ships next to the harness, drive Pump() once per frame when RequiresHostPump is set, and configure a realistic DMD layout/style so the dots-plus-glow shader is actually exercised. Add publish-macos.sh to produce a self-contained osx-arm64/osx-x64 app (bundling SDL2 + ANGLE dylibs from native/vendor when present) and README-macos-test.md documenting how to validate the GL-ES window on a Mac without the .NET SDK or Unity.
Stage the universal libSDL2 dylib (osx-arm64 + osx-x64, from the pysdl2-dll 2.30.1 wheel) needed by the macOS virtual-DMD window. Update native/vendor/README.md to document the new libSDL2 entry, the build-handled libserialport, and the outstanding ANGLE blocker for the macOS virtual window, and clarify the libftd2xx notes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PP adds a cross-platform
LibDmd.Coreextraction and NuGet packaging path for use by VPE and other non-WPF consumers.LibDmd.Coretargetingnetstandard2.1andnet8.0LibDmd.Core.Harnessto verify native interop and RenderGraph flowcore-v*tags