From 6ae8070cea75b9db57581dd3ab64bc00bd458962 Mon Sep 17 00:00:00 2001 From: Agash Date: Wed, 23 Sep 2026 11:04:51 +0200 Subject: [PATCH 1/3] style: format with csharpier --- .config/dotnet-tools.json | 13 ++++++++ .editorconfig | 26 ++++++++++++++++ .github/workflows/build.yml | 5 +++ Directory.Packages.props | 5 ++- Spout2.NET.slnx | 34 ++++++++++---------- src/Spout2.NET/AssemblyInfo.cs | 12 ++++--- src/Spout2.NET/Interop/SpoutNative.cs | 9 +++++- src/Spout2.NET/Spout2.NET.csproj | 12 ++++--- src/Spout2.NET/SpoutReceiver.cs | 37 ++++++++++++++++------ src/Spout2.NET/SpoutSender.cs | 16 +++++++--- src/Spout2.NET/SpoutSenders.cs | 25 ++++++++++++--- tests/Spout2.NET.Tests/SpoutTests.cs | 45 +++++++++++++++++++++------ 12 files changed, 184 insertions(+), 55 deletions(-) create mode 100644 .config/dotnet-tools.json diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..9e028ef --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "csharpier": { + "version": "1.3.0", + "commands": [ + "csharpier" + ], + "rollForward": false + } + } +} diff --git a/.editorconfig b/.editorconfig index a9b4846..6433b7f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -80,6 +80,32 @@ csharp_space_after_cast = false csharp_space_after_keywords_in_control_flow_statements = true csharp_space_around_binary_operators = before_and_after +# Formatting follows CSharpier; `dotnet csharpier check` enforces it in CI. These keys keep the IDE +# formatter consistent with it, and IDE0055 is off because some layouts, such as a multi-line +# constructor initializer before an empty body, cannot be expressed to it. +dotnet_diagnostic.IDE0055.severity = none +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + [*.{csproj,vbproj,proj,nativeproj,locproj}] charset = utf-8 indent_size = 2 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d68bf67..62798e3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,6 +44,11 @@ jobs: - name: Restore run: dotnet restore Spout2.NET.slnx + - name: Check formatting + run: | + dotnet tool restore + dotnet csharpier check . + - name: Build run: dotnet build Spout2.NET.slnx --no-restore -c Release diff --git a/Directory.Packages.props b/Directory.Packages.props index 376440b..c008497 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,10 @@ - + diff --git a/Spout2.NET.slnx b/Spout2.NET.slnx index 6d76d2e..097982f 100644 --- a/Spout2.NET.slnx +++ b/Spout2.NET.slnx @@ -1,19 +1,19 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/src/Spout2.NET/AssemblyInfo.cs b/src/Spout2.NET/AssemblyInfo.cs index 2b57b44..093e1d7 100644 --- a/src/Spout2.NET/AssemblyInfo.cs +++ b/src/Spout2.NET/AssemblyInfo.cs @@ -19,12 +19,16 @@ internal static void Initialize() typeof(AssemblyInitializer).Assembly, static (name, asm, path) => { - if (name is not Interop.SpoutNative.Lib) return 0; + if (name is not Interop.SpoutNative.Lib) + return 0; // Packaged as runtimes/win-x64/native/spout_shim.dll (resolved by the runtime), // or copied next to the assembly for local builds and tests. - if (NativeLibrary.TryLoad("spout_shim.dll", asm, path, out nint h)) return h; - if (NativeLibrary.TryLoad("spout_shim", asm, path, out h)) return h; + if (NativeLibrary.TryLoad("spout_shim.dll", asm, path, out nint h)) + return h; + if (NativeLibrary.TryLoad("spout_shim", asm, path, out h)) + return h; return 0; - }); + } + ); } } diff --git a/src/Spout2.NET/Interop/SpoutNative.cs b/src/Spout2.NET/Interop/SpoutNative.cs index 975183c..9d42b24 100644 --- a/src/Spout2.NET/Interop/SpoutNative.cs +++ b/src/Spout2.NET/Interop/SpoutNative.cs @@ -71,5 +71,12 @@ internal static partial class SpoutNative internal static partial int sp_get_sender(nint s, int index, byte[] name, int maxSize); [LibraryImport(Lib, StringMarshalling = StringMarshalling.Utf8)] - internal static partial int sp_get_sender_info(nint s, string name, out uint width, out uint height, out nint shareHandle, out uint format); + internal static partial int sp_get_sender_info( + nint s, + string name, + out uint width, + out uint height, + out nint shareHandle, + out uint format + ); } diff --git a/src/Spout2.NET/Spout2.NET.csproj b/src/Spout2.NET/Spout2.NET.csproj index a5445e2..6bddbcf 100644 --- a/src/Spout2.NET/Spout2.NET.csproj +++ b/src/Spout2.NET/Spout2.NET.csproj @@ -32,11 +32,13 @@ - + spout_shim.dll diff --git a/src/Spout2.NET/SpoutReceiver.cs b/src/Spout2.NET/SpoutReceiver.cs index c614cf2..404c393 100644 --- a/src/Spout2.NET/SpoutReceiver.cs +++ b/src/Spout2.NET/SpoutReceiver.cs @@ -25,19 +25,29 @@ public sealed partial class SpoutReceiver : IDisposable /// An ID3D11Device*, or to let Spout create one. /// Sender to receive, or null/empty for the active sender. /// Optional factory for Debug/Trace diagnostics; omit for none. - public SpoutReceiver(nint d3d11Device, string? senderName = null, ILoggerFactory? loggerFactory = null) + public SpoutReceiver( + nint d3d11Device, + string? senderName = null, + ILoggerFactory? loggerFactory = null + ) { - _logger = (loggerFactory ?? NullLoggerFactory.Instance) - .CreateLogger($"Spout2.NET.Receiver.{(string.IsNullOrEmpty(senderName) ? "active" : senderName)}"); + _logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger( + $"Spout2.NET.Receiver.{(string.IsNullOrEmpty(senderName) ? "active" : senderName)}" + ); _handle = SpoutNative.sp_create(); if (_handle == 0) throw new InvalidOperationException("Failed to create the Spout receiver."); if (SpoutNative.sp_open_directx11(_handle, d3d11Device) == 0) { Dispose(); - throw new InvalidOperationException("Failed to open DirectX 11 for the Spout receiver."); + throw new InvalidOperationException( + "Failed to open DirectX 11 for the Spout receiver." + ); } - SpoutNative.sp_set_receiver_name(_handle, string.IsNullOrEmpty(senderName) ? null : senderName); + SpoutNative.sp_set_receiver_name( + _handle, + string.IsNullOrEmpty(senderName) ? null : senderName + ); LogCreated(string.IsNullOrEmpty(senderName) ? "(active sender)" : senderName); } @@ -62,9 +72,12 @@ public bool Receive() // Log connection transitions and size/format changes (not every frame) so --verbose shows when a // sender appears/disappears and when the texture geometry is renegotiated. bool connected = IsConnected; - if (connected && !_wasConnected) LogConnected(SenderWidth, SenderHeight); - else if (!connected && _wasConnected) LogDisconnected(); - else if (connected && IsUpdated) LogUpdated(SenderWidth, SenderHeight); + if (connected && !_wasConnected) + LogConnected(SenderWidth, SenderHeight); + else if (!connected && _wasConnected) + LogDisconnected(); + else if (connected && IsUpdated) + LogUpdated(SenderWidth, SenderHeight); _wasConnected = connected; return ok; @@ -92,7 +105,8 @@ public bool Receive() public void Dispose() { nint h = Interlocked.Exchange(ref _handle, 0); - if (h == 0) return; + if (h == 0) + return; SpoutNative.sp_release_receiver(h); SpoutNative.sp_destroy(h); } @@ -106,6 +120,9 @@ public void Dispose() [LoggerMessage(Level = LogLevel.Debug, Message = "sender disconnected")] private partial void LogDisconnected(); - [LoggerMessage(Level = LogLevel.Debug, Message = "sender geometry/format updated ({Width}x{Height})")] + [LoggerMessage( + Level = LogLevel.Debug, + Message = "sender geometry/format updated ({Width}x{Height})" + )] private partial void LogUpdated(int width, int height); } diff --git a/src/Spout2.NET/SpoutSender.cs b/src/Spout2.NET/SpoutSender.cs index 854b003..b455b93 100644 --- a/src/Spout2.NET/SpoutSender.cs +++ b/src/Spout2.NET/SpoutSender.cs @@ -26,7 +26,9 @@ public sealed partial class SpoutSender : IDisposable public SpoutSender(string name, nint d3d11Device, ILoggerFactory? loggerFactory = null) { ArgumentException.ThrowIfNullOrEmpty(name); - _logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger($"Spout2.NET.Sender.{name}"); + _logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger( + $"Spout2.NET.Sender.{name}" + ); _handle = SpoutNative.sp_create(); if (_handle == 0) throw new InvalidOperationException("Failed to create the Spout sender."); @@ -69,17 +71,23 @@ public void SetFormat(DxgiFormat format) public void Send(nint d3d11Texture) { ObjectDisposedException.ThrowIf(_handle == 0, this); - if (d3d11Texture == 0) throw new ArgumentException("Texture pointer is null.", nameof(d3d11Texture)); + if (d3d11Texture == 0) + throw new ArgumentException("Texture pointer is null.", nameof(d3d11Texture)); if (SpoutNative.sp_send_texture(_handle, d3d11Texture) == 0) throw new InvalidOperationException("Failed to send the texture."); - if (!_firstSendLogged) { _firstSendLogged = true; LogFirstFrame(); } + if (!_firstSendLogged) + { + _firstSendLogged = true; + LogFirstFrame(); + } } /// public void Dispose() { nint h = Interlocked.Exchange(ref _handle, 0); - if (h == 0) return; + if (h == 0) + return; SpoutNative.sp_release_sender(h); SpoutNative.sp_destroy(h); } diff --git a/src/Spout2.NET/SpoutSenders.cs b/src/Spout2.NET/SpoutSenders.cs index 701d057..37db7aa 100644 --- a/src/Spout2.NET/SpoutSenders.cs +++ b/src/Spout2.NET/SpoutSenders.cs @@ -9,7 +9,13 @@ namespace Spout2.NET; /// Height in pixels. /// The DXGI shared texture handle. /// The texture format. -public readonly record struct SpoutSenderInfo(string Name, int Width, int Height, nint ShareHandle, DxgiFormat Format); +public readonly record struct SpoutSenderInfo( + string Name, + int Width, + int Height, + nint ShareHandle, + DxgiFormat Format +); /// /// Enumerates the Spout senders currently advertised on the machine (a process-global registry). @@ -57,7 +63,16 @@ public bool TryGetInfo(string name, out SpoutSenderInfo info) { ObjectDisposedException.ThrowIf(_handle == 0, this); ArgumentException.ThrowIfNullOrEmpty(name); - if (SpoutNative.sp_get_sender_info(_handle, name, out uint w, out uint h, out nint handle, out uint format) != 0) + if ( + SpoutNative.sp_get_sender_info( + _handle, + name, + out uint w, + out uint h, + out nint handle, + out uint format + ) != 0 + ) { info = new SpoutSenderInfo(name, (int)w, (int)h, handle, (DxgiFormat)format); return true; @@ -69,7 +84,8 @@ public bool TryGetInfo(string name, out SpoutSenderInfo info) private static string Decode(byte[] buffer) { int end = Array.IndexOf(buffer, 0); - if (end < 0) end = buffer.Length; + if (end < 0) + end = buffer.Length; return Encoding.UTF8.GetString(buffer, 0, end); } @@ -77,6 +93,7 @@ private static string Decode(byte[] buffer) public void Dispose() { nint h = Interlocked.Exchange(ref _handle, 0); - if (h != 0) SpoutNative.sp_destroy(h); + if (h != 0) + SpoutNative.sp_destroy(h); } } diff --git a/tests/Spout2.NET.Tests/SpoutTests.cs b/tests/Spout2.NET.Tests/SpoutTests.cs index e169e3d..9b5a291 100644 --- a/tests/Spout2.NET.Tests/SpoutTests.cs +++ b/tests/Spout2.NET.Tests/SpoutTests.cs @@ -44,7 +44,8 @@ public void Bgra_64x64_RoundTripsByteExact() using (recvDevice) using (recvContext) { - const int w = 64, h = 64; + const int w = 64, + h = 64; byte[] source = Pattern(w, h); using ID3D11Texture2D sourceTexture = CreateTexture(sendDevice!, w, h, source); @@ -61,13 +62,17 @@ public void Bgra_64x64_RoundTripsByteExact() if (receiver.Receive() && receiver.Texture != 0) { received = receiver.Texture; - if (++frames >= 3) break; + if (++frames >= 3) + break; } Thread.Sleep(16); } using (var senders = new SpoutSenders()) - Assert.IsTrue(senders.Names().Contains("Spout.NET Test"), "the sender should appear in the registry"); + Assert.IsTrue( + senders.Names().Contains("Spout.NET Test"), + "the sender should appear in the registry" + ); Assert.AreNotEqual(0, received, "the receiver should connect and deliver a texture"); Assert.AreEqual(w, receiver.SenderWidth); @@ -80,13 +85,24 @@ public void Bgra_64x64_RoundTripsByteExact() private static bool TryCreateDevice(out ID3D11Device? device, out ID3D11DeviceContext? context) { - FeatureLevel[] levels = [FeatureLevel.Level_11_1, FeatureLevel.Level_11_0, FeatureLevel.Level_10_0]; + FeatureLevel[] levels = + [ + FeatureLevel.Level_11_1, + FeatureLevel.Level_11_0, + FeatureLevel.Level_10_0, + ]; foreach (DriverType driver in new[] { DriverType.Hardware, DriverType.Warp }) { Result hr = D3D11.D3D11CreateDevice( - null, driver, DeviceCreationFlags.BgraSupport, levels, - out device, out context); - if (hr.Success && device is not null && context is not null) return true; + null, + driver, + DeviceCreationFlags.BgraSupport, + levels, + out device, + out context + ); + if (hr.Success && device is not null && context is not null) + return true; device?.Dispose(); context?.Dispose(); } @@ -118,7 +134,13 @@ private static ID3D11Texture2D CreateTexture(ID3D11Device device, int w, int h, } } - private static byte[] ReadBack(ID3D11Device device, ID3D11DeviceContext context, nint texturePtr, int w, int h) + private static byte[] ReadBack( + ID3D11Device device, + ID3D11DeviceContext context, + nint texturePtr, + int w, + int h + ) { var stagingDesc = new Texture2DDescription { @@ -139,7 +161,12 @@ private static byte[] ReadBack(ID3D11Device device, ID3D11DeviceContext context, context.CopyResource(staging, received); received.Dispose(); - MappedSubresource map = context.Map(staging, 0, MapMode.Read, Vortice.Direct3D11.MapFlags.None); + MappedSubresource map = context.Map( + staging, + 0, + MapMode.Read, + Vortice.Direct3D11.MapFlags.None + ); byte[] result = new byte[w * h * 4]; unsafe { From ce2599ed0bb4110f8c0371bbe68c539aacbad3e5 Mon Sep 17 00:00:00 2001 From: Agash Date: Wed, 23 Sep 2026 11:05:31 +0200 Subject: [PATCH 2/3] build: move to the .NET 11 RC1 SDK --- CONTRIBUTING.md | 4 ++-- global.json | 2 +- tests/Spout2.NET.Tests/Spout2.NET.Tests.csproj | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c49edc..41c0693 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,8 +12,8 @@ dotnet build Spout2.NET.slnx dotnet test --filter "TestCategory!=RequiresGpu" ``` -The build treats warnings as errors and targets .NET 10 (and .NET 11 preview). If you do not have -the .NET 11 preview SDK installed, build the `net10.0` target only. +The build treats warnings as errors and targets .NET 10 and .NET 11. If you do not have +the .NET 11 SDK installed, build the `net10.0` target only. ## Native shim diff --git a/global.json b/global.json index 67b0c9d..b100162 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.100", + "version": "11.0.100-rc.1.26425.128", "rollForward": "latestMajor", "allowPrerelease": true }, diff --git a/tests/Spout2.NET.Tests/Spout2.NET.Tests.csproj b/tests/Spout2.NET.Tests/Spout2.NET.Tests.csproj index f1d675f..d2f3b26 100644 --- a/tests/Spout2.NET.Tests/Spout2.NET.Tests.csproj +++ b/tests/Spout2.NET.Tests/Spout2.NET.Tests.csproj @@ -1,6 +1,8 @@ net10.0;net11.0 + true + $(NoWarn);CS1591 true x64 From 6f498a6d542eb633e6af8341ab161dbe6f75f167 Mon Sep 17 00:00:00 2001 From: Agash Date: Wed, 23 Sep 2026 11:27:09 +0200 Subject: [PATCH 3/3] ci: pass test targets to dotnet test explicitly --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 62798e3..9b1f69c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,7 +53,7 @@ jobs: run: dotnet build Spout2.NET.slnx --no-restore -c Release - name: Test (value-type, no GPU required) - run: dotnet test Spout2.NET.slnx --no-build -c Release --filter "TestCategory!=RequiresGpu" -- --coverage --coverage-settings coverage.settings.xml --coverage-output-format cobertura --coverage-output coverage.cobertura.xml + run: dotnet test --solution Spout2.NET.slnx --no-build -c Release --filter "TestCategory!=RequiresGpu" -- --coverage --coverage-settings coverage.settings.xml --coverage-output-format cobertura --coverage-output coverage.cobertura.xml - name: Summarize coverage # Renders the cobertura totals into the run summary. Uploading the raw XML alone means nobody @@ -109,7 +109,7 @@ jobs: # Non-gating: GitHub Windows runners use WARP (software D3D11), where cross-device shared # texture sharing may not work. The local GPU run is the authoritative end-to-end check. continue-on-error: true - run: dotnet test Spout2.NET.slnx --no-build -c Release --filter "TestCategory=RequiresGpu" + run: dotnet test --solution Spout2.NET.slnx --no-build -c Release --filter "TestCategory=RequiresGpu" - name: Pack if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v')