Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"csharpier": {
"version": "1.3.0",
"commands": [
"csharpier"
],
"rollForward": false
}
}
}
26 changes: 26 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,16 @@ 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

- 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
Expand Down Expand Up @@ -104,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')
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 4 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="DotNet.ReproducibleBuilds" Version="2.0.5" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="11.0.0-rc.1.26425.128" />
<PackageVersion
Include="Microsoft.Extensions.Logging.Abstractions"
Version="11.0.0-rc.1.26425.128"
/>
<PackageVersion Include="MinVer" Version="8.0.0" />
<PackageVersion Include="MSTest.TestFramework" Version="4.4.1" />
<PackageVersion Include="MSTest.TestAdapter" Version="4.4.1" />
Expand Down
34 changes: 17 additions & 17 deletions Spout2.NET.slnx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
<Solution>
<Folder Name="/Solution Items/">
<File Path=".editorconfig" />
<File Path=".gitattributes" />
<File Path=".gitignore" />
<File Path="Directory.Build.props" />
<File Path="Directory.Packages.props" />
<File Path="global.json" />
<File Path="LICENSE" />
<File Path="README.md" />
<File Path="THIRD-PARTY-NOTICES.md" />
</Folder>
<Folder Name="/src/">
<Project Path="src/Spout2.NET/Spout2.NET.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/Spout2.NET.Tests/Spout2.NET.Tests.csproj" />
</Folder>
<Folder Name="/Solution Items/">
<File Path=".editorconfig" />
<File Path=".gitattributes" />
<File Path=".gitignore" />
<File Path="Directory.Build.props" />
<File Path="Directory.Packages.props" />
<File Path="global.json" />
<File Path="LICENSE" />
<File Path="README.md" />
<File Path="THIRD-PARTY-NOTICES.md" />
</Folder>
<Folder Name="/src/">
<Project Path="src/Spout2.NET/Spout2.NET.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/Spout2.NET.Tests/Spout2.NET.Tests.csproj" />
</Folder>
</Solution>
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "10.0.100",
"version": "11.0.100-rc.1.26425.128",
"rollForward": "latestMajor",
"allowPrerelease": true
},
Expand Down
12 changes: 8 additions & 4 deletions src/Spout2.NET/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
}
);
}
}
9 changes: 8 additions & 1 deletion src/Spout2.NET/Interop/SpoutNative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
12 changes: 7 additions & 5 deletions src/Spout2.NET/Spout2.NET.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@

<!-- Self-contained native shim (x64), built by native/build-native.ps1 on Windows. -->
<ItemGroup Condition="Exists('$(MSBuildThisFileDirectory)..\..\native\build\spout_shim.dll')">
<None Include="..\..\native\build\spout_shim.dll"
Pack="true"
PackagePath="runtimes/win-x64/native/"
CopyToOutputDirectory="PreserveNewest"
CopyToPublishDirectory="PreserveNewest">
<None
Include="..\..\native\build\spout_shim.dll"
Pack="true"
PackagePath="runtimes/win-x64/native/"
CopyToOutputDirectory="PreserveNewest"
CopyToPublishDirectory="PreserveNewest"
>
<Link>spout_shim.dll</Link>
</None>
</ItemGroup>
Expand Down
37 changes: 27 additions & 10 deletions src/Spout2.NET/SpoutReceiver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,29 @@ public sealed partial class SpoutReceiver : IDisposable
/// <param name="d3d11Device">An <c>ID3D11Device*</c>, or <see cref="nint.Zero"/> to let Spout create one.</param>
/// <param name="senderName">Sender to receive, or null/empty for the active sender.</param>
/// <param name="loggerFactory">Optional factory for Debug/Trace diagnostics; omit for none.</param>
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);
}

Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
16 changes: 12 additions & 4 deletions src/Spout2.NET/SpoutSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand Down Expand Up @@ -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();
}
}

/// <inheritdoc/>
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);
}
Expand Down
25 changes: 21 additions & 4 deletions src/Spout2.NET/SpoutSenders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ namespace Spout2.NET;
/// <param name="Height">Height in pixels.</param>
/// <param name="ShareHandle">The DXGI shared texture handle.</param>
/// <param name="Format">The texture format.</param>
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
);

/// <summary>
/// Enumerates the Spout senders currently advertised on the machine (a process-global registry).
Expand Down Expand Up @@ -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;
Expand All @@ -69,14 +84,16 @@ public bool TryGetInfo(string name, out SpoutSenderInfo info)
private static string Decode(byte[] buffer)
{
int end = Array.IndexOf<byte>(buffer, 0);
if (end < 0) end = buffer.Length;
if (end < 0)
end = buffer.Length;
return Encoding.UTF8.GetString(buffer, 0, end);
}

/// <inheritdoc/>
public void Dispose()
{
nint h = Interlocked.Exchange(ref _handle, 0);
if (h != 0) SpoutNative.sp_destroy(h);
if (h != 0)
SpoutNative.sp_destroy(h);
}
}
2 changes: 2 additions & 0 deletions tests/Spout2.NET.Tests/Spout2.NET.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0;net11.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
Expand Down
Loading
Loading