feat(linux): gate graphical backends and capabilities
This commit is contained in:
parent
07bb1c5a74
commit
11501d52ca
13 changed files with 1924 additions and 32 deletions
|
|
@ -0,0 +1,261 @@
|
|||
using System.Text.Json;
|
||||
using AcDream.App.Platform;
|
||||
|
||||
namespace AcDream.App.Tests.Platform;
|
||||
|
||||
public sealed class GraphicalCapabilityRequirementsTests
|
||||
{
|
||||
[Fact]
|
||||
public void SupportedModernContextPasses()
|
||||
{
|
||||
GraphicalCapabilityRecord capabilities = CreateSupported();
|
||||
|
||||
Assert.Empty(GraphicalCapabilityRequirements.Evaluate(capabilities));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("bindless")]
|
||||
[InlineData("draw-parameters")]
|
||||
[InlineData("mdi")]
|
||||
[InlineData("ssbo")]
|
||||
[InlineData("timer")]
|
||||
[InlineData("depth")]
|
||||
[InlineData("stencil")]
|
||||
[InlineData("srgb")]
|
||||
[InlineData("keyboard")]
|
||||
[InlineData("mouse")]
|
||||
public void MissingMandatoryCapabilityIsRejected(string missing)
|
||||
{
|
||||
GraphicalCapabilityRecord capabilities = CreateSupported();
|
||||
capabilities = missing switch
|
||||
{
|
||||
"bindless" => capabilities with
|
||||
{
|
||||
HasBindlessTexture = false,
|
||||
},
|
||||
"draw-parameters" => capabilities with
|
||||
{
|
||||
HasShaderDrawParameters = false,
|
||||
},
|
||||
"mdi" => capabilities with
|
||||
{
|
||||
HasMultiDrawIndirect = false,
|
||||
},
|
||||
"ssbo" => capabilities with
|
||||
{
|
||||
HasShaderStorageBuffer = false,
|
||||
},
|
||||
"timer" => capabilities with
|
||||
{
|
||||
HasTimerQuery = false,
|
||||
},
|
||||
"depth" => capabilities with
|
||||
{
|
||||
Framebuffer = capabilities.Framebuffer with
|
||||
{
|
||||
DepthBits = 16,
|
||||
},
|
||||
},
|
||||
"stencil" => capabilities with
|
||||
{
|
||||
Framebuffer = capabilities.Framebuffer with
|
||||
{
|
||||
StencilBits = 0,
|
||||
},
|
||||
},
|
||||
"srgb" => capabilities with
|
||||
{
|
||||
Framebuffer = capabilities.Framebuffer with
|
||||
{
|
||||
FramebufferSrgbApi = false,
|
||||
},
|
||||
},
|
||||
"keyboard" => capabilities with
|
||||
{
|
||||
Input = capabilities.Input with
|
||||
{
|
||||
KeyboardCount = 0,
|
||||
},
|
||||
},
|
||||
"mouse" => capabilities with
|
||||
{
|
||||
Input = capabilities.Input with
|
||||
{
|
||||
MouseCount = 0,
|
||||
},
|
||||
},
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(missing)),
|
||||
};
|
||||
|
||||
Assert.NotEmpty(GraphicalCapabilityRequirements.Evaluate(capabilities));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdvertisedBufferStorageRequiresSuccessfulPersistentProbe()
|
||||
{
|
||||
GraphicalCapabilityRecord capabilities = CreateSupported() with
|
||||
{
|
||||
FunctionProbe = CreateSupported().FunctionProbe with
|
||||
{
|
||||
PersistentBufferStorage = false,
|
||||
},
|
||||
};
|
||||
|
||||
Assert.Contains(
|
||||
GraphicalCapabilityRequirements.Evaluate(capabilities),
|
||||
failure => failure.Contains(
|
||||
"persistent mapping",
|
||||
StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MissingOptionalBufferStorageDoesNotRequireProbe()
|
||||
{
|
||||
GraphicalCapabilityRecord capabilities = CreateSupported() with
|
||||
{
|
||||
HasBufferStorage = false,
|
||||
FunctionProbe = CreateSupported().FunctionProbe with
|
||||
{
|
||||
PersistentBufferStorage = null,
|
||||
},
|
||||
};
|
||||
|
||||
Assert.Empty(GraphicalCapabilityRequirements.Evaluate(capabilities));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnsupportedMessageNamesDriverProtocolFailureAndReport()
|
||||
{
|
||||
GraphicalCapabilityRecord capabilities = CreateSupported() with
|
||||
{
|
||||
SupportFailures = ["GL_ARB_bindless_texture is required."],
|
||||
};
|
||||
|
||||
string message = GraphicalCapabilityGuard.FormatUnsupportedMessage(
|
||||
capabilities,
|
||||
"capabilities.json");
|
||||
|
||||
Assert.Contains("Mesa", message);
|
||||
Assert.Contains("RadeonSI", message);
|
||||
Assert.Contains("Wayland", message);
|
||||
Assert.Contains("GL_ARB_bindless_texture", message);
|
||||
Assert.Contains(
|
||||
Path.GetFullPath("capabilities.json"),
|
||||
message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReportWriterAtomicallyOverwritesJson()
|
||||
{
|
||||
string directory = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
"acdream-capability-tests",
|
||||
Guid.NewGuid().ToString("N"));
|
||||
string path = Path.Combine(directory, "capabilities.json");
|
||||
try
|
||||
{
|
||||
GraphicalCapabilityReportWriter.Write(path, CreateSupported());
|
||||
GraphicalCapabilityReportWriter.Write(
|
||||
path,
|
||||
CreateSupported() with
|
||||
{
|
||||
GlRenderer = "second renderer",
|
||||
});
|
||||
|
||||
using JsonDocument report = JsonDocument.Parse(
|
||||
File.ReadAllText(path));
|
||||
Assert.Equal(
|
||||
"second renderer",
|
||||
report.RootElement
|
||||
.GetProperty(nameof(GraphicalCapabilityRecord.GlRenderer))
|
||||
.GetString());
|
||||
Assert.Equal(
|
||||
"Wayland",
|
||||
report.RootElement
|
||||
.GetProperty(nameof(
|
||||
GraphicalCapabilityRecord.ActiveDisplayProtocol))
|
||||
.GetString());
|
||||
Assert.False(File.Exists(path + ".tmp"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(directory))
|
||||
Directory.Delete(directory, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static GraphicalCapabilityRecord CreateSupported() => new(
|
||||
DateTimeOffset.UnixEpoch,
|
||||
"linux-x64",
|
||||
GraphicalHostOperatingSystem.Linux,
|
||||
GraphicalDisplayProtocol.Wayland,
|
||||
GraphicalDisplayProtocol.Wayland,
|
||||
"test",
|
||||
"Silk.NET.Windowing.Glfw",
|
||||
"3.4.0",
|
||||
"Mesa",
|
||||
"RadeonSI",
|
||||
"4.6",
|
||||
"4.60",
|
||||
4,
|
||||
6,
|
||||
1,
|
||||
1,
|
||||
HasBindlessTexture: true,
|
||||
HasShaderDrawParameters: true,
|
||||
HasMultiDrawIndirect: true,
|
||||
HasShaderStorageBuffer: true,
|
||||
HasBufferStorage: true,
|
||||
HasTimerQuery: true,
|
||||
MaximumShaderStorageBufferBindings: 16,
|
||||
MaximumUniformBufferBindings: 72,
|
||||
MaximumTextureSize: 16_384,
|
||||
MaximumArrayTextureLayers: 2_048,
|
||||
MaximumCombinedTextureImageUnits: 192,
|
||||
new GraphicalFramebufferCapabilities(
|
||||
8,
|
||||
8,
|
||||
8,
|
||||
8,
|
||||
24,
|
||||
8,
|
||||
1,
|
||||
4,
|
||||
FramebufferSrgbApi: true),
|
||||
new GraphicalInputCapabilities(
|
||||
KeyboardCount: 1,
|
||||
MouseCount: 1,
|
||||
GamepadCount: 0,
|
||||
JoystickCount: 0),
|
||||
new GraphicalWindowCapabilities(
|
||||
1280,
|
||||
720,
|
||||
2560,
|
||||
1440,
|
||||
"test monitor",
|
||||
144,
|
||||
VSync: false),
|
||||
new GraphicalAudioCapabilities(
|
||||
Requested: false,
|
||||
Available: false,
|
||||
PlaybackSubmitted: false,
|
||||
DisposalComplete: true,
|
||||
Backend: "not requested"),
|
||||
new GraphicalSmokeLifecycleCapabilities(
|
||||
OwnedWindowCount: 1,
|
||||
OwnedGlApiCount: 1,
|
||||
OwnedInputContextCount: 1,
|
||||
OwnedAudioEngineCount: 0,
|
||||
ShutdownComplete: false),
|
||||
[],
|
||||
new GraphicalFunctionProbeResult(
|
||||
BindlessTexture: true,
|
||||
ShaderDrawParameters: true,
|
||||
MultiDrawIndirect: true,
|
||||
ShaderStorageBuffer: true,
|
||||
TimerQuery: true,
|
||||
SrgbFramebuffer: true,
|
||||
PersistentBufferStorage: true,
|
||||
Failures: []),
|
||||
SupportFailures: []);
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
using AcDream.App.Platform;
|
||||
|
||||
namespace AcDream.App.Tests.Platform;
|
||||
|
||||
public sealed class GraphicalWindowBackendSelectionTests
|
||||
{
|
||||
[Fact]
|
||||
public void WindowsAlwaysSelectsWin32()
|
||||
{
|
||||
GraphicalWindowBackendSelection selection =
|
||||
GraphicalWindowBackendSelection.Resolve(
|
||||
GraphicalHostOperatingSystem.Windows,
|
||||
_ => "wayland");
|
||||
|
||||
Assert.Equal(
|
||||
GraphicalDisplayProtocol.Windows,
|
||||
selection.RequestedProtocol);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("auto", "Automatic")]
|
||||
[InlineData("x11", "X11")]
|
||||
[InlineData("wayland", "Wayland")]
|
||||
[InlineData(" WAYLAND ", "Wayland")]
|
||||
public void ExplicitLinuxProtocolWins(
|
||||
string configured,
|
||||
string expected)
|
||||
{
|
||||
var environment = Environment(
|
||||
(GraphicalWindowBackendSelection.EnvironmentVariable, configured),
|
||||
("XDG_SESSION_TYPE", "x11"),
|
||||
("DISPLAY", ":0"));
|
||||
|
||||
GraphicalWindowBackendSelection selection =
|
||||
GraphicalWindowBackendSelection.Resolve(
|
||||
GraphicalHostOperatingSystem.Linux,
|
||||
environment);
|
||||
|
||||
Assert.Equal(expected, selection.RequestedProtocol.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidExplicitLinuxProtocolIsActionable()
|
||||
{
|
||||
var error = Assert.Throws<InvalidOperationException>(
|
||||
() => GraphicalWindowBackendSelection.Resolve(
|
||||
GraphicalHostOperatingSystem.Linux,
|
||||
Environment(
|
||||
(GraphicalWindowBackendSelection.EnvironmentVariable,
|
||||
"mir"))));
|
||||
|
||||
Assert.Contains("auto, x11, or wayland", error.Message);
|
||||
Assert.Contains("mir", error.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AutomaticLinuxCases))]
|
||||
public void LinuxEnvironmentSelectsExpectedProtocol(
|
||||
(string Name, string? Value)[] variables,
|
||||
string expected)
|
||||
{
|
||||
GraphicalWindowBackendSelection selection =
|
||||
GraphicalWindowBackendSelection.Resolve(
|
||||
GraphicalHostOperatingSystem.Linux,
|
||||
Environment(variables));
|
||||
|
||||
Assert.Equal(expected, selection.RequestedProtocol.ToString());
|
||||
}
|
||||
|
||||
public static TheoryData<
|
||||
(string Name, string? Value)[],
|
||||
string> AutomaticLinuxCases => new()
|
||||
{
|
||||
{
|
||||
[
|
||||
("XDG_SESSION_TYPE", "wayland"),
|
||||
("WAYLAND_DISPLAY", "wayland-0"),
|
||||
("DISPLAY", ":0"),
|
||||
],
|
||||
"Wayland"
|
||||
},
|
||||
{
|
||||
[
|
||||
("XDG_SESSION_TYPE", "x11"),
|
||||
("WAYLAND_DISPLAY", "wayland-0"),
|
||||
("DISPLAY", ":0"),
|
||||
],
|
||||
"X11"
|
||||
},
|
||||
{
|
||||
[("WAYLAND_DISPLAY", "wayland-0")],
|
||||
"Wayland"
|
||||
},
|
||||
{
|
||||
[],
|
||||
"Automatic"
|
||||
},
|
||||
};
|
||||
|
||||
private static Func<string, string?> Environment(
|
||||
params (string Name, string? Value)[] variables)
|
||||
{
|
||||
var values = variables.ToDictionary(
|
||||
pair => pair.Name,
|
||||
pair => pair.Value,
|
||||
StringComparer.Ordinal);
|
||||
return name => values.GetValueOrDefault(name);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,24 @@ namespace AcDream.App.Tests.Studio;
|
|||
|
||||
public class StudioWindowTests
|
||||
{
|
||||
[Fact]
|
||||
public void FrameCloseGate_defersCloseUntilFrameCompletion()
|
||||
{
|
||||
var gate = new StudioFrameCloseGate();
|
||||
int closeCount = 0;
|
||||
|
||||
gate.Request();
|
||||
|
||||
Assert.True(gate.IsRequested);
|
||||
Assert.Equal(0, closeCount);
|
||||
|
||||
gate.CompleteFrame(() => closeCount++);
|
||||
gate.CompleteFrame(() => closeCount++);
|
||||
|
||||
Assert.False(gate.IsRequested);
|
||||
Assert.Equal(1, closeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseMockup_doesNotDefaultToSinglePanelLayout()
|
||||
{
|
||||
|
|
@ -16,6 +34,22 @@ public class StudioWindowTests
|
|||
Assert.Null(opts.MarkupPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseCapabilitySmokeOptions()
|
||||
{
|
||||
var opts = StudioOptions.Parse(
|
||||
[
|
||||
@"C:\fake-dats",
|
||||
"--mockup",
|
||||
"--capability-report",
|
||||
"report.json",
|
||||
"--audio-smoke",
|
||||
]);
|
||||
|
||||
Assert.Equal("report.json", opts.CapabilityReportPath);
|
||||
Assert.True(opts.AudioSmoke);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NormalizeSinglePanelRoot_movesDatPanelToCanvasOrigin()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue