mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
273 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56b7957878 | ||
|
|
209eb5fea0 | ||
|
|
417954e66c | ||
|
|
e4fb860985 | ||
|
|
d70481aedd | ||
|
|
52eb581b56 | ||
|
|
918fa8e3b9 | ||
|
|
1c7f19bf67 | ||
|
|
0cbff8dee1 | ||
|
|
670ff54ef0 | ||
|
|
8f519c52b6 | ||
|
|
fbd29afbd8 | ||
|
|
2bbdb23716 | ||
|
|
e2dda67eef | ||
|
|
699615df97 | ||
|
|
e804994a5b | ||
|
|
24f7ecccc0 | ||
|
|
21d43350f0 | ||
|
|
63eb7847a1 | ||
|
|
4a4f07a10c | ||
|
|
1c816941ec | ||
|
|
2143c9abc3 | ||
|
|
4b6ceed586 | ||
|
|
49c6c0c9d8 | ||
|
|
54f6143be3 | ||
|
|
b83e09c214 | ||
|
|
caa9b442d5 | ||
|
|
69921da034 | ||
|
|
552893f1b7 | ||
|
|
c5c10caaf1 | ||
|
|
98de046977 | ||
|
|
6328bddb52 | ||
|
|
003c910c66 | ||
|
|
1d7f4b4501 | ||
|
|
2f6fb22473 | ||
|
|
96d66c57c1 | ||
|
|
35741524a3 | ||
|
|
d5855fa805 | ||
|
|
36a9d06381 | ||
|
|
3bfb3a5ef7 | ||
|
|
83b031cab4 | ||
|
|
6197352fca | ||
|
|
0fd210481a | ||
|
|
74eea847e2 | ||
|
|
6938787863 | ||
|
|
4ba5654253 | ||
|
|
15ff120b97 | ||
|
|
942a550687 | ||
|
|
acff35f4bf | ||
|
|
4e7039e09b | ||
|
|
4a0b3793e4 | ||
|
|
928ae19249 | ||
|
|
a7f85b53f8 | ||
|
|
5fac83c697 | ||
|
|
577b2b0f62 | ||
|
|
cd3a7ef91e | ||
|
|
051d47f4ff | ||
|
|
b982b94c83 | ||
|
|
84b7742656 | ||
|
|
1585cd111f | ||
|
|
766f6dc93d | ||
|
|
59af9451be | ||
|
|
80e4f69f29 | ||
|
|
98060f2056 | ||
|
|
f0bfb6a69a | ||
|
|
ac38748280 | ||
|
|
18fe34c041 | ||
|
|
6a0cb6a5af | ||
|
|
310aebaee2 | ||
|
|
f25d67da55 | ||
|
|
eac36e917f | ||
|
|
c64fa8f2cc | ||
|
|
63c1707581 | ||
|
|
914a7fb130 | ||
|
|
41d3495e29 | ||
|
|
2ae426b8e5 | ||
|
|
eb45e16739 | ||
|
|
79c952a871 | ||
|
|
827c4b32fd | ||
|
|
033e20cbf1 | ||
|
|
3ff46d40fa | ||
|
|
5ab6aba20b | ||
|
|
e6be04cb83 | ||
|
|
627b2f4d0d | ||
|
|
ffff42cc95 | ||
|
|
ce420ac8ab | ||
|
|
d3cdfbe3cb | ||
|
|
726bfd58f6 | ||
|
|
05827d01d0 | ||
|
|
6cb7641c07 | ||
|
|
d72185933a | ||
|
|
80a9a82278 | ||
|
|
99c8ce2cc8 | ||
|
|
f137a6e3a5 | ||
|
|
73e62414a7 | ||
|
|
ef7ef3ca9f | ||
|
|
da26e86f5e | ||
|
|
d2a1815d7b | ||
|
|
4894888339 | ||
|
|
3fd55733bb | ||
|
|
bebd638412 | ||
|
|
328b3cc715 | ||
|
|
b523cfddb7 | ||
|
|
4fea277541 | ||
|
|
3417f00650 | ||
|
|
db4b190498 | ||
|
|
7d2bbc2ca7 | ||
|
|
553278fdb3 | ||
|
|
e5013d9e3f | ||
|
|
679f8f24c9 | ||
|
|
1a547b946c | ||
|
|
5594bd7203 | ||
|
|
8cbe48c94f | ||
|
|
b04b9fc9cd | ||
|
|
820d988e0a | ||
|
|
1ad5122b5d | ||
|
|
456ac03870 | ||
|
|
f647d00dc4 | ||
|
|
2fd3bbf58b | ||
|
|
5ed49d51d3 | ||
|
|
758d5eedef | ||
|
|
96d15929e6 | ||
|
|
2a062dbf53 | ||
|
|
6d66bc66e4 | ||
|
|
5cc056100b | ||
|
|
5eee22b034 | ||
|
|
7f5beab259 | ||
|
|
46aead639b | ||
|
|
488d060595 | ||
|
|
b1bab1f38e | ||
|
|
feba2a23ad | ||
|
|
e1ff29744e | ||
|
|
f6216e63b0 | ||
|
|
0b3ecf2168 | ||
|
|
9f29bf6349 | ||
|
|
d12a238ecc | ||
|
|
d62a64029d | ||
|
|
ac46a7845d | ||
|
|
17662aaad9 | ||
|
|
60b526f653 | ||
|
|
d58e380dd9 | ||
|
|
96a098a0c2 | ||
|
|
c0d4e34089 | ||
|
|
e16e0f4bd0 | ||
|
|
d31ffd2794 | ||
|
|
04d94f87fc | ||
|
|
f809375389 | ||
|
|
530ea5f4e6 | ||
|
|
590a23d540 | ||
|
|
52351e8b11 | ||
|
|
f75a764ff3 | ||
|
|
d14f4f0ce3 | ||
|
|
5367570c7f | ||
|
|
e523a733c8 | ||
|
|
06af61106c | ||
|
|
2239c30924 | ||
|
|
f9f55b6862 | ||
|
|
83a5560850 | ||
|
|
9a8f139fb9 | ||
|
|
20dae60fd4 | ||
|
|
b4098668bb | ||
|
|
9397cc4a6b | ||
|
|
1601e75879 | ||
|
|
a7b9c87926 | ||
|
|
8fea42ff9a | ||
|
|
86d20a0ef1 | ||
|
|
09012ea4ff | ||
|
|
e68297eb93 | ||
|
|
47af668cda | ||
|
|
c1a2e23ce2 | ||
|
|
f208f6bfa9 | ||
|
|
ae526e2e10 | ||
|
|
25549869b1 | ||
|
|
f71e81d204 | ||
|
|
757143be84 | ||
|
|
e67812fdb4 | ||
|
|
aa44b1cb8a | ||
|
|
8ec75be244 | ||
|
|
48746b7bd3 | ||
|
|
a9791d2033 | ||
|
|
709f1f4284 | ||
|
|
907094a5c8 | ||
|
|
a35a5e1645 | ||
|
|
ad8a59a72f | ||
|
|
e93c0f76a9 | ||
|
|
7bac32d18e | ||
|
|
b6b1d46892 | ||
|
|
30fcc6b729 | ||
|
|
6f0bc3822e | ||
|
|
b7c8452285 | ||
|
|
49e2d567cd | ||
|
|
8c1e075c91 | ||
|
|
b340e40c99 | ||
|
|
c4b124f48d | ||
|
|
7efae8fbc1 | ||
|
|
7feeeb2f6f | ||
|
|
f90462cf82 | ||
|
|
b19ae9e69e | ||
|
|
9bf69db0ef | ||
|
|
2132d6cbae | ||
|
|
d2d6f9d08e | ||
|
|
4b58fcbff2 | ||
|
|
f83f6a8cd6 | ||
|
|
dfd7711506 | ||
|
|
78f9d92c07 | ||
|
|
3a86c827ea | ||
|
|
325f25c547 | ||
|
|
be57b5d20b | ||
|
|
7124d86f94 | ||
|
|
229380a71d | ||
|
|
e9eb536df5 | ||
|
|
22297ef6d8 | ||
|
|
7f2e433087 | ||
|
|
18c32a0258 | ||
|
|
72314a102d | ||
|
|
719ea26a31 | ||
|
|
5cb8fe1897 | ||
|
|
f35a52fc24 | ||
|
|
6bdb0cef47 | ||
|
|
fe3c9fe28f | ||
|
|
6085671f22 | ||
|
|
a2398da324 | ||
|
|
b27304cc58 | ||
|
|
3bf851a6cf | ||
|
|
cef92efd0f | ||
|
|
c5961a5ab1 | ||
|
|
8ddd92993d | ||
|
|
da253a5f34 | ||
|
|
ca9400a1ff | ||
|
|
f232195ceb | ||
|
|
b54a803519 | ||
|
|
a0d3d2108f | ||
|
|
977e4a017b | ||
|
|
2d8b159016 | ||
|
|
9caa0dde4b | ||
|
|
7a5a8c5eb1 | ||
|
|
95ba58f0a4 | ||
|
|
f780f04784 | ||
|
|
695b4ce8f2 | ||
|
|
85782bda92 | ||
|
|
14a01df5b1 | ||
|
|
644da60bfc | ||
|
|
8c83999ad2 | ||
|
|
24b9fc9eec | ||
|
|
ba40185179 | ||
|
|
8b013cb424 | ||
|
|
d7ecc0883f | ||
|
|
44b17edff6 | ||
|
|
6366c2383a | ||
|
|
ddbbe22bda | ||
|
|
6880af0d8b | ||
|
|
6f258b1822 | ||
|
|
2cf3ed8a70 | ||
|
|
c6f10a1321 | ||
|
|
41ab9b106f | ||
|
|
0d97569576 | ||
|
|
0a21d402b2 | ||
|
|
c6827fe8ae | ||
|
|
0c94956be1 | ||
|
|
1e7a193d5e | ||
|
|
eb4ea8aa63 | ||
|
|
af1a3d965d | ||
|
|
26fed40ad4 | ||
|
|
c6c6309eff | ||
|
|
7188eeb0c8 | ||
|
|
d74b22a4e8 | ||
|
|
75de717b4e | ||
|
|
a00fc75b61 | ||
|
|
dcbb6608df | ||
|
|
e4d0e50000 | ||
|
|
e5a2ab284b | ||
|
|
3ac1506f17 | ||
|
|
c85bb81606 |
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -16,3 +16,6 @@
|
||||
[submodule "Linguini"]
|
||||
path = Linguini
|
||||
url = https://github.com/space-wizards/Linguini
|
||||
[submodule "cefglue"]
|
||||
path = cefglue
|
||||
url = https://gitlab.com/xiliumhq/chromiumembedded/cefglue/
|
||||
|
||||
Submodule Lidgren.Network/Lidgren.Network updated: 5fc11c2b2b...4b36b36f39
2
Linguini
2
Linguini
Submodule Linguini updated: 62b0e75b91...b3c05c2f31
Submodule NetSerializer updated: 2d4f8b5611...ab1b651292
@@ -129,5 +129,7 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
/// for controlling sRGB rendering and a created OpenGL ES context will always have sRGB rendering enabled.
|
||||
/// </summary>
|
||||
SrgbCapable = 0x0002100E,
|
||||
|
||||
ScaleToMonitor = 0x0002200C,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5551,5 +5551,15 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
{
|
||||
return glfwGetX11Window(window);
|
||||
}
|
||||
|
||||
public static unsafe IntPtr GetX11Display(Window* window)
|
||||
{
|
||||
return glfwGetX11Display(window);
|
||||
}
|
||||
|
||||
public static unsafe IntPtr GetWin32Window(Window* window)
|
||||
{
|
||||
return glfwGetWin32Window(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,5 +406,11 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern uint glfwGetX11Window(Window* window);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern IntPtr glfwGetX11Display(Window* window);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern IntPtr glfwGetWin32Window(Window* window);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
- name: Box2D
|
||||
license:
|
||||
license: |
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Erin Catto
|
||||
@@ -33,7 +33,7 @@
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
@@ -48,7 +48,7 @@
|
||||
SOFTWARE.
|
||||
|
||||
- name: Bullet Physics SDK
|
||||
license:
|
||||
license: |
|
||||
The files in this repository are licensed under the zlib license, except for the files under 'Extras' and examples/ThirdPartyLibs.
|
||||
|
||||
Bullet Continuous Collision Detection and Physics Library
|
||||
@@ -60,10 +60,10 @@
|
||||
including commercial applications, and to alter it and redistribute it freely,
|
||||
subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
|
||||
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
|
||||
If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
- name: Castle Core
|
||||
license: |
|
||||
@@ -360,7 +360,7 @@
|
||||
limitations under the License.
|
||||
|
||||
- name: Farseer Physics Engine
|
||||
license:
|
||||
license: |
|
||||
Microsoft Permissive License (Ms-PL)
|
||||
|
||||
This license governs use of the accompanying software.
|
||||
@@ -374,26 +374,26 @@
|
||||
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
|
||||
|
||||
2. Grant of Rights
|
||||
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
||||
each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution,
|
||||
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
||||
each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution,
|
||||
prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
|
||||
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
||||
each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to
|
||||
make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or
|
||||
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
||||
each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to
|
||||
make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or
|
||||
derivative works of the contribution in the software.
|
||||
|
||||
3. Conditions and Limitations
|
||||
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
|
||||
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software,
|
||||
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software,
|
||||
your patent license from such contributor to the software ends automatically.
|
||||
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark,
|
||||
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark,
|
||||
and attribution notices that are present in the software.
|
||||
(D) If you distribute any portion of the software in source code form, you may do so only under this license by
|
||||
including a complete copy of this license with your distribution. If you distribute any portion of the software in
|
||||
(D) If you distribute any portion of the software in source code form, you may do so only under this license by
|
||||
including a complete copy of this license with your distribution. If you distribute any portion of the software in
|
||||
compiled or object code form, you may only do so under a license that complies with this license.
|
||||
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties,
|
||||
guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change.
|
||||
To the extent permitted under your local laws, the contributors exclude the implied warranties of
|
||||
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties,
|
||||
guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change.
|
||||
To the extent permitted under your local laws, the contributors exclude the implied warranties of
|
||||
merchantability, fitness for a particular purpose and non-infringement.
|
||||
|
||||
- name: Mono.Cecil
|
||||
|
||||
107
Robust.Analyzers/FriendAnalyzer.cs
Normal file
107
Robust.Analyzers/FriendAnalyzer.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
{
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public class FriendAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
const string FriendAttribute = "Robust.Shared.Analyzers.FriendAttribute";
|
||||
|
||||
public const string DiagnosticId = "RA0002";
|
||||
|
||||
private const string Title = "Tried to access friend-only member";
|
||||
private const string MessageFormat = "Tried to access member \"{0}\" in class \"{1}\" which can only be accessed by friend classes";
|
||||
private const string Description = "Make sure to specify the accessing class in the friends attribute.";
|
||||
private const string Category = "Usage";
|
||||
|
||||
[SuppressMessage("ReSharper", "RS2008")]
|
||||
private static readonly DiagnosticDescriptor Rule = new (DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, true, Description);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterSyntaxNodeAction(CheckFriendship, SyntaxKind.SimpleMemberAccessExpression);
|
||||
}
|
||||
|
||||
private void CheckFriendship(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
if (context.Node is not MemberAccessExpressionSyntax memberAccess)
|
||||
return;
|
||||
|
||||
// We only do something if our parent is one of a few types.
|
||||
switch (context.Node.Parent)
|
||||
{
|
||||
// If we're being assigned...
|
||||
case AssignmentExpressionSyntax assignParent:
|
||||
{
|
||||
if (assignParent.Left != memberAccess)
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
// If we're being invoked...
|
||||
case InvocationExpressionSyntax:
|
||||
break;
|
||||
|
||||
// Otherwise, do nothing.
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the friend attribute
|
||||
var friendAttr = context.Compilation.GetTypeByMetadataName(FriendAttribute);
|
||||
|
||||
// Get the type that is containing this expression, or, the class where this is happening.
|
||||
if (context.ContainingSymbol?.ContainingType is not { } containingType)
|
||||
return;
|
||||
|
||||
// We check all of our children and get only the identifiers.
|
||||
foreach (var identifier in memberAccess.ChildNodes().Select(node => node as IdentifierNameSyntax))
|
||||
{
|
||||
if (identifier == null) continue;
|
||||
|
||||
// Get the type info of the identifier, so we can check the attributes...
|
||||
if (context.SemanticModel.GetTypeInfo(identifier).ConvertedType is not { } type)
|
||||
continue;
|
||||
|
||||
// Same-type access is always fine.
|
||||
if (SymbolEqualityComparer.Default.Equals(type, containingType))
|
||||
continue;
|
||||
|
||||
// Finally, get all attributes of the type, to check if we have any friend classes.
|
||||
foreach (var attribute in type.GetAttributes())
|
||||
{
|
||||
// If the attribute isn't the friend attribute, continue.
|
||||
if (!SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, friendAttr))
|
||||
continue;
|
||||
|
||||
// Check all types allowed in the friend attribute. (We assume there's only one constructor arg.)
|
||||
foreach (var constant in attribute.ConstructorArguments[0].Values)
|
||||
{
|
||||
// Check if the value is a type...
|
||||
if (constant.Value is not INamedTypeSymbol t)
|
||||
continue;
|
||||
|
||||
// If we find that the containing class is specified in the attribute, return! All is good.
|
||||
if (SymbolEqualityComparer.Default.Equals(containingType, t))
|
||||
return;
|
||||
}
|
||||
|
||||
// Not in a friend class! Report an error.
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(Rule, context.Node.GetLocation(),
|
||||
$"{context.Node.ToString().Split('.').LastOrDefault()}", $"{type.Name}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>9</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
28
Robust.Benchmarks/NumericsHelpers/AddBenchmark.cs
Normal file
28
Robust.Benchmarks/NumericsHelpers/AddBenchmark.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Robust.Benchmarks.NumericsHelpers
|
||||
{
|
||||
public class AddBenchmark
|
||||
{
|
||||
[Params(32, 128)]
|
||||
public int N { get; set; }
|
||||
|
||||
private float[] _inputA = default!;
|
||||
private float[] _inputB = default!;
|
||||
private float[] _output = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_inputA = new float[N];
|
||||
_inputB = new float[N];
|
||||
_output = new float[N];
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Bench()
|
||||
{
|
||||
Shared.Maths.NumericsHelpers.Add(_inputA, _inputB, _output);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,11 @@ namespace Robust.Benchmarks
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
// --allCategories=ctg1,ctg2
|
||||
// --anyCategories=ctg1,ctg2
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run();
|
||||
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
40
Robust.Benchmarks/Serialization/BenchmarkIntSerializer.cs
Normal file
40
Robust.Benchmarks/Serialization/BenchmarkIntSerializer.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Globalization;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Result;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization
|
||||
{
|
||||
public class BenchmarkIntSerializer : ITypeSerializer<int, ValueDataNode>
|
||||
{
|
||||
public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node,
|
||||
IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||
{
|
||||
return int.TryParse(node.Value, out _)
|
||||
? new ValidatedValueNode(node)
|
||||
: new ErrorNode(node, $"Failed parsing int value: {node.Value}");
|
||||
}
|
||||
|
||||
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
|
||||
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
|
||||
{
|
||||
return new DeserializedValue<int>(int.Parse(node.Value, CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
public DataNode Write(ISerializationManager serializationManager, int value, bool alwaysWrite = false,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
return new ValueDataNode(value.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
public int Copy(ISerializationManager serializationManager, int source, int target, bool skipHook,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
return source;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,13 @@ using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Copy
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class SerializationCopyBenchmark : SerializationBenchmark
|
||||
{
|
||||
public SerializationCopyBenchmark()
|
||||
@@ -35,6 +37,10 @@ namespace Robust.Benchmarks.Serialization.Copy
|
||||
|
||||
private SeedDataDefinition Seed { get; }
|
||||
|
||||
private BenchmarkFlagsEnum FlagZero = BenchmarkFlagsEnum.Zero;
|
||||
|
||||
private BenchmarkFlagsEnum FlagThirtyOne = BenchmarkFlagsEnum.ThirtyOne;
|
||||
|
||||
[Benchmark]
|
||||
public string? CreateCopyString()
|
||||
{
|
||||
@@ -111,5 +117,35 @@ namespace Robust.Benchmarks.Serialization.Copy
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public object? CopyFlagZero()
|
||||
{
|
||||
return SerializationManager.CopyWithTypeSerializer(
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
(int) FlagZero,
|
||||
(int) FlagZero);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public object? CopyFlagThirtyOne()
|
||||
{
|
||||
return SerializationManager.CopyWithTypeSerializer(
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
(int) FlagThirtyOne,
|
||||
(int) FlagThirtyOne);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("customTypeSerializer")]
|
||||
public object? CopyIntegerCustomSerializer()
|
||||
{
|
||||
return SerializationManager.CopyWithTypeSerializer(
|
||||
typeof(BenchmarkIntSerializer),
|
||||
Integer,
|
||||
Integer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Definitions
|
||||
{
|
||||
public class BenchmarkFlags
|
||||
{
|
||||
public const int Zero = 1 << 0;
|
||||
public const int ThirtyOne = 1 << 31;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
[FlagsFor(typeof(BenchmarkFlags))]
|
||||
public enum BenchmarkFlagsEnum
|
||||
{
|
||||
Zero = BenchmarkFlags.Zero,
|
||||
ThirtyOne = BenchmarkFlags.ThirtyOne
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace Robust.Benchmarks.Serialization.Definitions
|
||||
[DataDefinition]
|
||||
public class DataDefinitionWithString
|
||||
{
|
||||
[field: DataField("string")]
|
||||
[DataField("string")]
|
||||
public string StringField { get; init; } = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Definitions
|
||||
{
|
||||
[DataDefinition]
|
||||
public sealed class SealedDataDefinitionWithString
|
||||
{
|
||||
[DataField("string")]
|
||||
public string StringField { get; init; } = default!;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using Robust.Shared.Serialization.Manager;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Initialize
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class SerializationInitializeBenchmark : SerializationBenchmark
|
||||
{
|
||||
[IterationCleanup]
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
using System.IO;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Benchmarks.Serialization.Definitions;
|
||||
using Robust.Shared.Serialization.Manager.Result;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Read
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class SerializationReadBenchmark : SerializationBenchmark
|
||||
{
|
||||
public SerializationReadBenchmark()
|
||||
@@ -32,6 +35,10 @@ namespace Robust.Benchmarks.Serialization.Read
|
||||
|
||||
private MappingDataNode SeedNode { get; }
|
||||
|
||||
private ValueDataNode FlagZero { get; } = new("Zero");
|
||||
|
||||
private ValueDataNode FlagThirtyOne { get; } = new("ThirtyOne");
|
||||
|
||||
[Benchmark]
|
||||
public string? ReadString()
|
||||
{
|
||||
@@ -55,5 +62,35 @@ namespace Robust.Benchmarks.Serialization.Read
|
||||
{
|
||||
return SerializationManager.ReadValue<SeedDataDefinition>(SeedNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public DeserializationResult ReadFlagZero()
|
||||
{
|
||||
return SerializationManager.ReadWithTypeSerializer(
|
||||
typeof(int),
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
FlagZero);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public DeserializationResult ReadThirtyOne()
|
||||
{
|
||||
return SerializationManager.ReadWithTypeSerializer(
|
||||
typeof(int),
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
FlagThirtyOne);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("customTypeSerializer")]
|
||||
public DeserializationResult ReadIntegerCustomSerializer()
|
||||
{
|
||||
return SerializationManager.ReadWithTypeSerializer(
|
||||
typeof(int),
|
||||
typeof(BenchmarkIntSerializer),
|
||||
IntNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
127
Robust.Benchmarks/Serialization/SerializationArrayBenchmark.cs
Normal file
127
Robust.Benchmarks/Serialization/SerializationArrayBenchmark.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Benchmarks.Serialization.Definitions;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class SerializationArrayBenchmark : SerializationBenchmark
|
||||
{
|
||||
public SerializationArrayBenchmark()
|
||||
{
|
||||
InitializeSerialization();
|
||||
|
||||
OneStringDefNode = new SequenceDataNode();
|
||||
OneStringDefNode.Add(new MappingDataNode
|
||||
{
|
||||
["string"] = new ValueDataNode("ABC")
|
||||
});
|
||||
|
||||
TenStringDefsNode = new SequenceDataNode();
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
TenStringDefsNode.Add(new MappingDataNode
|
||||
{
|
||||
["string"] = new ValueDataNode("ABC")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private SequenceDataNode EmptyNode { get; } = new();
|
||||
|
||||
private SequenceDataNode OneIntNode { get; } = new("1");
|
||||
|
||||
private SequenceDataNode TenIntsNode { get; } = new("1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
|
||||
|
||||
private SequenceDataNode OneStringDefNode { get; }
|
||||
|
||||
private SequenceDataNode TenStringDefsNode { get; }
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public string[]? ReadEmptyString()
|
||||
{
|
||||
return SerializationManager.ReadValue<string[]>(EmptyNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public string[]? ReadOneString()
|
||||
{
|
||||
return SerializationManager.ReadValue<string[]>(OneIntNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public string[]? ReadTenStrings()
|
||||
{
|
||||
return SerializationManager.ReadValue<string[]>(TenIntsNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public int[]? ReadEmptyInt()
|
||||
{
|
||||
return SerializationManager.ReadValue<int[]>(EmptyNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public int[]? ReadOneInt()
|
||||
{
|
||||
return SerializationManager.ReadValue<int[]>(OneIntNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public int[]? ReadTenInts()
|
||||
{
|
||||
return SerializationManager.ReadValue<int[]>(TenIntsNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public DataDefinitionWithString[]? ReadEmptyStringDataDef()
|
||||
{
|
||||
return SerializationManager.ReadValue<DataDefinitionWithString[]>(EmptyNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public DataDefinitionWithString[]? ReadOneStringDataDef()
|
||||
{
|
||||
return SerializationManager.ReadValue<DataDefinitionWithString[]>(OneStringDefNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public DataDefinitionWithString[]? ReadTenStringDataDefs()
|
||||
{
|
||||
return SerializationManager.ReadValue<DataDefinitionWithString[]>(TenStringDefsNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public SealedDataDefinitionWithString[]? ReadEmptySealedStringDataDef()
|
||||
{
|
||||
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(EmptyNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public SealedDataDefinitionWithString[]? ReadOneSealedStringDataDef()
|
||||
{
|
||||
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(OneStringDefNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public SealedDataDefinitionWithString[]? ReadTenSealedStringDataDefs()
|
||||
{
|
||||
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(TenStringDefsNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,12 @@ using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Write
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class SerializationWriteBenchmark : SerializationBenchmark
|
||||
{
|
||||
public SerializationWriteBenchmark()
|
||||
@@ -35,6 +37,10 @@ namespace Robust.Benchmarks.Serialization.Write
|
||||
|
||||
private SeedDataDefinition Seed { get; }
|
||||
|
||||
private BenchmarkFlagsEnum FlagZero = BenchmarkFlagsEnum.Zero;
|
||||
|
||||
private BenchmarkFlagsEnum FlagThirtyOne = BenchmarkFlagsEnum.ThirtyOne;
|
||||
|
||||
[Benchmark]
|
||||
public DataNode WriteString()
|
||||
{
|
||||
@@ -94,5 +100,35 @@ namespace Robust.Benchmarks.Serialization.Write
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public DataNode WriteFlagZero()
|
||||
{
|
||||
return SerializationManager.WriteWithTypeSerializer(
|
||||
typeof(int),
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
FlagZero);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public DataNode WriteThirtyOne()
|
||||
{
|
||||
return SerializationManager.WriteWithTypeSerializer(
|
||||
typeof(int),
|
||||
typeof(FlagSerializer<BenchmarkFlags>),
|
||||
FlagThirtyOne);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("customTypeSerializer")]
|
||||
public DataNode WriteIntegerCustomSerializer()
|
||||
{
|
||||
return SerializationManager.WriteWithTypeSerializer(
|
||||
typeof(int),
|
||||
typeof(BenchmarkIntSerializer),
|
||||
Integer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
32
Robust.Client.CEF/BeforeBrowseContext.cs
Normal file
32
Robust.Client.CEF/BeforeBrowseContext.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public sealed class BeforeBrowseContext
|
||||
{
|
||||
internal readonly CefRequest CefRequest;
|
||||
|
||||
public string Url => CefRequest.Url;
|
||||
public string Method => CefRequest.Method;
|
||||
|
||||
public bool IsRedirect { get; }
|
||||
public bool UserGesture { get; }
|
||||
|
||||
public bool IsCancelled { get; private set; }
|
||||
|
||||
internal BeforeBrowseContext(
|
||||
bool isRedirect,
|
||||
bool userGesture,
|
||||
CefRequest cefRequest)
|
||||
{
|
||||
CefRequest = cefRequest;
|
||||
IsRedirect = isRedirect;
|
||||
UserGesture = userGesture;
|
||||
}
|
||||
|
||||
public void DoCancel()
|
||||
{
|
||||
IsCancelled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
593
Robust.Client.CEF/BrowserControl.cs
Normal file
593
Robust.Client.CEF/BrowserControl.cs
Normal file
@@ -0,0 +1,593 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Xilium.CefGlue;
|
||||
using static Robust.Client.CEF.CefKeyCodes;
|
||||
using static Robust.Client.CEF.CefKeyCodes.ChromiumKeyboardCode;
|
||||
using static Robust.Client.Input.Keyboard;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
// Funny browser control to integrate in UI.
|
||||
public class BrowserControl : Control, IBrowserControl, IRawInputControl
|
||||
{
|
||||
private const int ScrollSpeed = 50;
|
||||
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IInputManager _inputMgr = default!;
|
||||
[Dependency] private readonly CefManager _cef = default!;
|
||||
|
||||
private RobustRequestHandler _requestHandler = new RobustRequestHandler(Logger.GetSawmill("root"));
|
||||
private LiveData? _data;
|
||||
private string _startUrl = "about:blank";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Url
|
||||
{
|
||||
get => _data == null ? _startUrl : _data.Browser.GetMainFrame().Url;
|
||||
set
|
||||
{
|
||||
if (_data == null)
|
||||
_startUrl = value;
|
||||
else
|
||||
_data.Browser.GetMainFrame().LoadUrl(value);
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables] public bool IsLoading => _data?.Browser.IsLoading ?? false;
|
||||
|
||||
private readonly Dictionary<Key, ChromiumKeyboardCode> _keyMap = new()
|
||||
{
|
||||
[Key.A] = VKEY_A,
|
||||
[Key.B] = VKEY_B,
|
||||
[Key.C] = VKEY_C,
|
||||
[Key.D] = VKEY_D,
|
||||
[Key.E] = VKEY_E,
|
||||
[Key.F] = VKEY_F,
|
||||
[Key.G] = VKEY_G,
|
||||
[Key.H] = VKEY_H,
|
||||
[Key.I] = VKEY_I,
|
||||
[Key.J] = VKEY_J,
|
||||
[Key.K] = VKEY_K,
|
||||
[Key.L] = VKEY_L,
|
||||
[Key.M] = VKEY_M,
|
||||
[Key.N] = VKEY_N,
|
||||
[Key.O] = VKEY_O,
|
||||
[Key.P] = VKEY_P,
|
||||
[Key.Q] = VKEY_Q,
|
||||
[Key.R] = VKEY_R,
|
||||
[Key.S] = VKEY_S,
|
||||
[Key.T] = VKEY_T,
|
||||
[Key.U] = VKEY_U,
|
||||
[Key.V] = VKEY_V,
|
||||
[Key.W] = VKEY_W,
|
||||
[Key.X] = VKEY_X,
|
||||
[Key.Y] = VKEY_Y,
|
||||
[Key.Z] = VKEY_Z,
|
||||
[Key.Num0] = VKEY_0,
|
||||
[Key.Num1] = VKEY_1,
|
||||
[Key.Num2] = VKEY_2,
|
||||
[Key.Num3] = VKEY_3,
|
||||
[Key.Num4] = VKEY_4,
|
||||
[Key.Num5] = VKEY_5,
|
||||
[Key.Num6] = VKEY_6,
|
||||
[Key.Num7] = VKEY_7,
|
||||
[Key.Num8] = VKEY_8,
|
||||
[Key.Num9] = VKEY_9,
|
||||
[Key.NumpadNum0] = VKEY_NUMPAD0,
|
||||
[Key.NumpadNum1] = VKEY_NUMPAD1,
|
||||
[Key.NumpadNum2] = VKEY_NUMPAD2,
|
||||
[Key.NumpadNum3] = VKEY_NUMPAD3,
|
||||
[Key.NumpadNum4] = VKEY_NUMPAD4,
|
||||
[Key.NumpadNum5] = VKEY_NUMPAD5,
|
||||
[Key.NumpadNum6] = VKEY_NUMPAD6,
|
||||
[Key.NumpadNum7] = VKEY_NUMPAD7,
|
||||
[Key.NumpadNum8] = VKEY_NUMPAD8,
|
||||
[Key.NumpadNum9] = VKEY_NUMPAD9,
|
||||
[Key.Escape] = VKEY_ESCAPE,
|
||||
[Key.Control] = VKEY_CONTROL,
|
||||
[Key.Shift] = VKEY_SHIFT,
|
||||
[Key.Alt] = VKEY_MENU,
|
||||
[Key.LSystem] = VKEY_LWIN,
|
||||
[Key.RSystem] = VKEY_RWIN,
|
||||
[Key.LBracket] = VKEY_OEM_4,
|
||||
[Key.RBracket] = VKEY_OEM_6,
|
||||
[Key.SemiColon] = VKEY_OEM_1,
|
||||
[Key.Comma] = VKEY_OEM_COMMA,
|
||||
[Key.Period] = VKEY_OEM_PERIOD,
|
||||
[Key.Apostrophe] = VKEY_OEM_7,
|
||||
[Key.Slash] = VKEY_OEM_2,
|
||||
[Key.BackSlash] = VKEY_OEM_5,
|
||||
[Key.Tilde] = VKEY_OEM_3,
|
||||
[Key.Equal] = VKEY_OEM_PLUS,
|
||||
[Key.Space] = VKEY_SPACE,
|
||||
[Key.Return] = VKEY_RETURN,
|
||||
[Key.BackSpace] = VKEY_BACK,
|
||||
[Key.Tab] = VKEY_TAB,
|
||||
[Key.PageUp] = VKEY_PRIOR,
|
||||
[Key.PageDown] = VKEY_NEXT,
|
||||
[Key.End] = VKEY_END,
|
||||
[Key.Home] = VKEY_HOME,
|
||||
[Key.Insert] = VKEY_INSERT,
|
||||
[Key.Delete] = VKEY_DELETE,
|
||||
[Key.Minus] = VKEY_OEM_MINUS,
|
||||
[Key.NumpadAdd] = VKEY_ADD,
|
||||
[Key.NumpadSubtract] = VKEY_SUBTRACT,
|
||||
[Key.NumpadDivide] = VKEY_DIVIDE,
|
||||
[Key.NumpadMultiply] = VKEY_MULTIPLY,
|
||||
[Key.NumpadDecimal] = VKEY_DECIMAL,
|
||||
[Key.Left] = VKEY_LEFT,
|
||||
[Key.Right] = VKEY_RIGHT,
|
||||
[Key.Up] = VKEY_UP,
|
||||
[Key.Down] = VKEY_DOWN,
|
||||
[Key.F1] = VKEY_F1,
|
||||
[Key.F2] = VKEY_F2,
|
||||
[Key.F3] = VKEY_F3,
|
||||
[Key.F4] = VKEY_F4,
|
||||
[Key.F5] = VKEY_F5,
|
||||
[Key.F6] = VKEY_F6,
|
||||
[Key.F7] = VKEY_F7,
|
||||
[Key.F8] = VKEY_F8,
|
||||
[Key.F9] = VKEY_F9,
|
||||
[Key.F10] = VKEY_F10,
|
||||
[Key.F11] = VKEY_F11,
|
||||
[Key.F12] = VKEY_F12,
|
||||
[Key.F13] = VKEY_F13,
|
||||
[Key.F14] = VKEY_F14,
|
||||
[Key.F15] = VKEY_F15,
|
||||
[Key.Pause] = VKEY_PAUSE,
|
||||
};
|
||||
|
||||
public BrowserControl()
|
||||
{
|
||||
CanKeyboardFocus = true;
|
||||
KeyboardFocusOnClick = true;
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void EnteredTree()
|
||||
{
|
||||
base.EnteredTree();
|
||||
|
||||
_cef.CheckInitialized();
|
||||
|
||||
DebugTools.AssertNull(_data);
|
||||
|
||||
// A funny render handler that will allow us to render to the control.
|
||||
var renderer = new ControlRenderHandler(this);
|
||||
|
||||
// A funny web cef client. This can actually be shared by multiple browsers, but I'm not sure how the
|
||||
// rendering would work in that case? TODO CEF: Investigate a way to share the web client?
|
||||
var client = new RobustCefClient(renderer, _requestHandler, new RobustLoadHandler());
|
||||
|
||||
var info = CefWindowInfo.Create();
|
||||
|
||||
// FUNFACT: If you DO NOT set these below and set info.Width/info.Height instead, you get an external window
|
||||
// Good to know, huh? Setup is the same, except you can pass a dummy render handler to the CEF client.
|
||||
info.SetAsWindowless(IntPtr.Zero, false); // TODO CEF: Pass parent handle?
|
||||
info.WindowlessRenderingEnabled = true;
|
||||
|
||||
var settings = new CefBrowserSettings()
|
||||
{
|
||||
WindowlessFrameRate = 60
|
||||
};
|
||||
|
||||
// Create the web browser! And by default, we go to about:blank.
|
||||
var browser = CefBrowserHost.CreateBrowserSync(info, client, settings, _startUrl);
|
||||
|
||||
var texture = _clyde.CreateBlankTexture<Bgra32>(Vector2i.One);
|
||||
|
||||
_data = new LiveData(texture, client, browser, renderer);
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
|
||||
DebugTools.AssertNotNull(_data);
|
||||
|
||||
_data!.Texture.Dispose();
|
||||
_data.Browser.GetHost().CloseBrowser(true);
|
||||
_data = null;
|
||||
}
|
||||
|
||||
protected internal override void MouseMove(GUIMouseMoveEventArgs args)
|
||||
{
|
||||
base.MouseMove(args);
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
// Logger.Debug();
|
||||
var modifiers = CalcMouseModifiers();
|
||||
var mouseEvent = new CefMouseEvent(
|
||||
(int) args.RelativePosition.X, (int) args.RelativePosition.Y,
|
||||
modifiers);
|
||||
|
||||
_data.Browser.GetHost().SendMouseMoveEvent(mouseEvent, false);
|
||||
}
|
||||
|
||||
protected internal override void MouseExited()
|
||||
{
|
||||
base.MouseExited();
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var modifiers = CalcMouseModifiers();
|
||||
|
||||
_data.Browser.GetHost().SendMouseMoveEvent(new CefMouseEvent(0, 0, modifiers), true);
|
||||
}
|
||||
|
||||
protected internal override void MouseWheel(GUIMouseWheelEventArgs args)
|
||||
{
|
||||
base.MouseWheel(args);
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var modifiers = CalcMouseModifiers();
|
||||
var mouseEvent = new CefMouseEvent(
|
||||
(int) args.RelativePosition.X, (int) args.RelativePosition.Y,
|
||||
modifiers);
|
||||
|
||||
_data.Browser.GetHost().SendMouseWheelEvent(
|
||||
mouseEvent,
|
||||
(int) args.Delta.X * ScrollSpeed,
|
||||
(int) args.Delta.Y * ScrollSpeed);
|
||||
}
|
||||
|
||||
bool IRawInputControl.RawKeyEvent(in GuiRawKeyEvent guiRawEvent)
|
||||
{
|
||||
if (_data == null)
|
||||
return false;
|
||||
|
||||
var host = _data.Browser.GetHost();
|
||||
|
||||
if (guiRawEvent.Key is Key.MouseLeft or Key.MouseMiddle or Key.MouseRight)
|
||||
{
|
||||
var key = guiRawEvent.Key switch
|
||||
{
|
||||
Key.MouseLeft => CefMouseButtonType.Left,
|
||||
Key.MouseMiddle => CefMouseButtonType.Middle,
|
||||
Key.MouseRight => CefMouseButtonType.Right,
|
||||
_ => default // not possible
|
||||
};
|
||||
|
||||
var mouseEvent = new CefMouseEvent(
|
||||
guiRawEvent.MouseRelative.X, guiRawEvent.MouseRelative.Y,
|
||||
CefEventFlags.None);
|
||||
|
||||
// Logger.Debug($"MOUSE: {guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {key}");
|
||||
|
||||
// TODO: double click support?
|
||||
host.SendMouseClickEvent(mouseEvent, key, guiRawEvent.Action == RawKeyAction.Up, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Handle left/right modifier keys??
|
||||
if (!_keyMap.TryGetValue(guiRawEvent.Key, out var vkKey))
|
||||
vkKey = default;
|
||||
|
||||
// Logger.Debug($"{guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {vkKey}");
|
||||
|
||||
var lParam = 0;
|
||||
lParam |= (guiRawEvent.ScanCode & 0xFF) << 16;
|
||||
if (guiRawEvent.Action != RawKeyAction.Down)
|
||||
lParam |= 1 << 30;
|
||||
|
||||
if (guiRawEvent.Action == RawKeyAction.Up)
|
||||
lParam |= 1 << 31;
|
||||
|
||||
var modifiers = CalcModifiers(guiRawEvent.Key);
|
||||
|
||||
host.SendKeyEvent(new CefKeyEvent
|
||||
{
|
||||
// Repeats are sent as key downs, I guess?
|
||||
EventType = guiRawEvent.Action == RawKeyAction.Up
|
||||
? CefKeyEventType.KeyUp
|
||||
: CefKeyEventType.RawKeyDown,
|
||||
NativeKeyCode = lParam,
|
||||
// NativeKeyCode = guiRawEvent.ScanCode,
|
||||
WindowsKeyCode = (int) vkKey,
|
||||
IsSystemKey = false, // TODO
|
||||
Modifiers = modifiers
|
||||
});
|
||||
|
||||
if (guiRawEvent.Action != RawKeyAction.Up && guiRawEvent.Key == Key.Return)
|
||||
{
|
||||
host.SendKeyEvent(new CefKeyEvent
|
||||
{
|
||||
EventType = CefKeyEventType.Char,
|
||||
WindowsKeyCode = '\r',
|
||||
NativeKeyCode = lParam,
|
||||
Modifiers = modifiers
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private CefEventFlags CalcModifiers(Key key)
|
||||
{
|
||||
CefEventFlags modifiers = default;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Control))
|
||||
modifiers |= CefEventFlags.ControlDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Alt))
|
||||
modifiers |= CefEventFlags.AltDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
private CefEventFlags CalcMouseModifiers()
|
||||
{
|
||||
CefEventFlags modifiers = default;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Control))
|
||||
modifiers |= CefEventFlags.ControlDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Alt))
|
||||
modifiers |= CefEventFlags.AltDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.MouseLeft))
|
||||
modifiers |= CefEventFlags.LeftMouseButton;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.MouseMiddle))
|
||||
modifiers |= CefEventFlags.MiddleMouseButton;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.MouseRight))
|
||||
modifiers |= CefEventFlags.RightMouseButton;
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
protected internal override void TextEntered(GUITextEventArgs args)
|
||||
{
|
||||
base.TextEntered(args);
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var host = _data.Browser.GetHost();
|
||||
|
||||
Span<char> buf = stackalloc char[2];
|
||||
var written = args.AsRune.EncodeToUtf16(buf);
|
||||
|
||||
for (var i = 0; i < written; i++)
|
||||
{
|
||||
host.SendKeyEvent(new CefKeyEvent
|
||||
{
|
||||
EventType = CefKeyEventType.Char,
|
||||
WindowsKeyCode = buf[i],
|
||||
Character = buf[i],
|
||||
UnmodifiedCharacter = buf[i]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Resized()
|
||||
{
|
||||
base.Resized();
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
_data.Browser.GetHost().NotifyMoveOrResizeStarted();
|
||||
_data.Browser.GetHost().WasResized();
|
||||
_data.Texture.Dispose();
|
||||
_data.Texture = _clyde.CreateBlankTexture<Bgra32>((PixelWidth, PixelHeight));
|
||||
}
|
||||
|
||||
protected internal override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var bufImg = _data.Renderer.Buffer.Buffer;
|
||||
|
||||
_data.Texture.SetSubImage(
|
||||
Vector2i.Zero,
|
||||
bufImg,
|
||||
new UIBox2i(
|
||||
0, 0,
|
||||
Math.Min(PixelWidth, bufImg.Width),
|
||||
Math.Min(PixelHeight, bufImg.Height)));
|
||||
|
||||
handle.DrawTexture(_data.Texture, Vector2.Zero);
|
||||
}
|
||||
|
||||
public void StopLoad()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
_data.Browser.StopLoad();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
_data.Browser.Reload();
|
||||
}
|
||||
|
||||
public bool GoBack()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
if (!_data.Browser.CanGoBack)
|
||||
return false;
|
||||
|
||||
_data.Browser.GoBack();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool GoForward()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
if (!_data.Browser.CanGoForward)
|
||||
return false;
|
||||
|
||||
_data.Browser.GoForward();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ExecuteJavaScript(string code)
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
// TODO: this should not run until the browser is done loading seriously does this even work?
|
||||
_data.Browser.GetMainFrame().ExecuteJavaScript(code, string.Empty, 1);
|
||||
}
|
||||
|
||||
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
_requestHandler.AddResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
_requestHandler.RemoveResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void AddBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
|
||||
{
|
||||
_requestHandler.AddBeforeBrowseHandler(handler);
|
||||
}
|
||||
|
||||
public void RemoveBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
|
||||
{
|
||||
_requestHandler.RemoveBeforeBrowseHandler(handler);
|
||||
}
|
||||
|
||||
private sealed class LiveData
|
||||
{
|
||||
public OwnedTexture Texture;
|
||||
public readonly RobustCefClient Client;
|
||||
public readonly CefBrowser Browser;
|
||||
public readonly ControlRenderHandler Renderer;
|
||||
|
||||
public LiveData(
|
||||
OwnedTexture texture,
|
||||
RobustCefClient client,
|
||||
CefBrowser browser,
|
||||
ControlRenderHandler renderer)
|
||||
{
|
||||
Texture = texture;
|
||||
Client = client;
|
||||
Browser = browser;
|
||||
Renderer = renderer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ControlRenderHandler : CefRenderHandler
|
||||
{
|
||||
public ImageBuffer Buffer { get; }
|
||||
private Control _control;
|
||||
|
||||
internal ControlRenderHandler(Control control)
|
||||
{
|
||||
Buffer = new ImageBuffer(control);
|
||||
_control = control;
|
||||
}
|
||||
|
||||
protected override CefAccessibilityHandler? GetAccessibilityHandler() => null;
|
||||
|
||||
protected override void GetViewRect(CefBrowser browser, out CefRectangle rect)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
{
|
||||
rect = new CefRectangle();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO CEF: Do we need to pass real screen coords? Cause what we do already works...
|
||||
//var screenCoords = _control.ScreenCoordinates;
|
||||
//rect = new CefRectangle((int) screenCoords.X, (int) screenCoords.Y, (int)Math.Max(_control.Size.X, 1), (int)Math.Max(_control.Size.Y, 1));
|
||||
|
||||
// We do the max between size and 1 because it will LITERALLY CRASH WITHOUT AN ERROR otherwise.
|
||||
rect = new CefRectangle(0, 0, (int) Math.Max(_control.Size.X, 1), (int) Math.Max(_control.Size.Y, 1));
|
||||
}
|
||||
|
||||
protected override bool GetScreenInfo(CefBrowser browser, CefScreenInfo screenInfo)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return false;
|
||||
|
||||
// TODO CEF: Get actual scale factor?
|
||||
screenInfo.DeviceScaleFactor = 1.0f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnPopupSize(CefBrowser browser, CefRectangle rect)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return;
|
||||
}
|
||||
|
||||
protected override void OnPaint(CefBrowser browser, CefPaintElementType type, CefRectangle[] dirtyRects,
|
||||
IntPtr buffer, int width, int height)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return;
|
||||
|
||||
foreach (var dirtyRect in dirtyRects)
|
||||
{
|
||||
Buffer.UpdateBuffer(width, height, buffer, dirtyRect);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAcceleratedPaint(CefBrowser browser, CefPaintElementType type,
|
||||
CefRectangle[] dirtyRects, IntPtr sharedHandle)
|
||||
{
|
||||
// Unused, but we're forced to implement it so.. NOOP.
|
||||
}
|
||||
|
||||
protected override void OnScrollOffsetChanged(CefBrowser browser, double x, double y)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return;
|
||||
}
|
||||
|
||||
protected override void OnImeCompositionRangeChanged(CefBrowser browser, CefRange selectedRange,
|
||||
CefRectangle[] characterBounds)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Robust.Client.CEF/BrowserWindowCreateParameters.cs
Normal file
16
Robust.Client.CEF/BrowserWindowCreateParameters.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public sealed class BrowserWindowCreateParameters
|
||||
{
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
|
||||
public string Url { get; set; } = "about:blank";
|
||||
|
||||
public BrowserWindowCreateParameters(int width, int height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
225
Robust.Client.CEF/CefKeyCodes.cs
Normal file
225
Robust.Client.CEF/CefKeyCodes.cs
Normal file
@@ -0,0 +1,225 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo")]
|
||||
[SuppressMessage("ReSharper", "CA1069")]
|
||||
[SuppressMessage("ReSharper", "CommentTypo")]
|
||||
internal static class CefKeyCodes
|
||||
{
|
||||
// Taken from https://chromium.googlesource.com/chromium/src/+/refs/heads/main/ui/events/keycodes/keyboard_codes_posix.h
|
||||
// See also https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
||||
public enum ChromiumKeyboardCode
|
||||
{
|
||||
VKEY_CANCEL = 0x03,
|
||||
VKEY_BACK = 0x08,
|
||||
VKEY_TAB = 0x09,
|
||||
VKEY_BACKTAB = 0x0A,
|
||||
VKEY_CLEAR = 0x0C,
|
||||
VKEY_RETURN = 0x0D,
|
||||
VKEY_SHIFT = 0x10,
|
||||
VKEY_CONTROL = 0x11,
|
||||
VKEY_MENU = 0x12,
|
||||
VKEY_PAUSE = 0x13,
|
||||
VKEY_CAPITAL = 0x14,
|
||||
VKEY_KANA = 0x15,
|
||||
VKEY_HANGUL = 0x15,
|
||||
VKEY_PASTE = 0x16,
|
||||
VKEY_JUNJA = 0x17,
|
||||
VKEY_FINAL = 0x18,
|
||||
VKEY_HANJA = 0x19,
|
||||
VKEY_KANJI = 0x19,
|
||||
VKEY_ESCAPE = 0x1B,
|
||||
VKEY_CONVERT = 0x1C,
|
||||
VKEY_NONCONVERT = 0x1D,
|
||||
VKEY_ACCEPT = 0x1E,
|
||||
VKEY_MODECHANGE = 0x1F,
|
||||
VKEY_SPACE = 0x20,
|
||||
VKEY_PRIOR = 0x21,
|
||||
VKEY_NEXT = 0x22,
|
||||
VKEY_END = 0x23,
|
||||
VKEY_HOME = 0x24,
|
||||
VKEY_LEFT = 0x25,
|
||||
VKEY_UP = 0x26,
|
||||
VKEY_RIGHT = 0x27,
|
||||
VKEY_DOWN = 0x28,
|
||||
VKEY_SELECT = 0x29,
|
||||
VKEY_PRINT = 0x2A,
|
||||
VKEY_EXECUTE = 0x2B,
|
||||
VKEY_SNAPSHOT = 0x2C, // Print Screen / SysRq
|
||||
VKEY_INSERT = 0x2D,
|
||||
VKEY_DELETE = 0x2E,
|
||||
VKEY_HELP = 0x2F,
|
||||
VKEY_0 = 0x30,
|
||||
VKEY_1 = 0x31,
|
||||
VKEY_2 = 0x32,
|
||||
VKEY_3 = 0x33,
|
||||
VKEY_4 = 0x34,
|
||||
VKEY_5 = 0x35,
|
||||
VKEY_6 = 0x36,
|
||||
VKEY_7 = 0x37,
|
||||
VKEY_8 = 0x38,
|
||||
VKEY_9 = 0x39,
|
||||
VKEY_A = 0x41,
|
||||
VKEY_B = 0x42,
|
||||
VKEY_C = 0x43,
|
||||
VKEY_D = 0x44,
|
||||
VKEY_E = 0x45,
|
||||
VKEY_F = 0x46,
|
||||
VKEY_G = 0x47,
|
||||
VKEY_H = 0x48,
|
||||
VKEY_I = 0x49,
|
||||
VKEY_J = 0x4A,
|
||||
VKEY_K = 0x4B,
|
||||
VKEY_L = 0x4C,
|
||||
VKEY_M = 0x4D,
|
||||
VKEY_N = 0x4E,
|
||||
VKEY_O = 0x4F,
|
||||
VKEY_P = 0x50,
|
||||
VKEY_Q = 0x51,
|
||||
VKEY_R = 0x52,
|
||||
VKEY_S = 0x53,
|
||||
VKEY_T = 0x54,
|
||||
VKEY_U = 0x55,
|
||||
VKEY_V = 0x56,
|
||||
VKEY_W = 0x57,
|
||||
VKEY_X = 0x58,
|
||||
VKEY_Y = 0x59,
|
||||
VKEY_Z = 0x5A,
|
||||
VKEY_LWIN = 0x5B,
|
||||
VKEY_COMMAND = VKEY_LWIN, // Provide the Mac name for convenience.
|
||||
VKEY_RWIN = 0x5C,
|
||||
VKEY_APPS = 0x5D,
|
||||
VKEY_SLEEP = 0x5F,
|
||||
VKEY_NUMPAD0 = 0x60,
|
||||
VKEY_NUMPAD1 = 0x61,
|
||||
VKEY_NUMPAD2 = 0x62,
|
||||
VKEY_NUMPAD3 = 0x63,
|
||||
VKEY_NUMPAD4 = 0x64,
|
||||
VKEY_NUMPAD5 = 0x65,
|
||||
VKEY_NUMPAD6 = 0x66,
|
||||
VKEY_NUMPAD7 = 0x67,
|
||||
VKEY_NUMPAD8 = 0x68,
|
||||
VKEY_NUMPAD9 = 0x69,
|
||||
VKEY_MULTIPLY = 0x6A,
|
||||
VKEY_ADD = 0x6B,
|
||||
VKEY_SEPARATOR = 0x6C,
|
||||
VKEY_SUBTRACT = 0x6D,
|
||||
VKEY_DECIMAL = 0x6E,
|
||||
VKEY_DIVIDE = 0x6F,
|
||||
VKEY_F1 = 0x70,
|
||||
VKEY_F2 = 0x71,
|
||||
VKEY_F3 = 0x72,
|
||||
VKEY_F4 = 0x73,
|
||||
VKEY_F5 = 0x74,
|
||||
VKEY_F6 = 0x75,
|
||||
VKEY_F7 = 0x76,
|
||||
VKEY_F8 = 0x77,
|
||||
VKEY_F9 = 0x78,
|
||||
VKEY_F10 = 0x79,
|
||||
VKEY_F11 = 0x7A,
|
||||
VKEY_F12 = 0x7B,
|
||||
VKEY_F13 = 0x7C,
|
||||
VKEY_F14 = 0x7D,
|
||||
VKEY_F15 = 0x7E,
|
||||
VKEY_F16 = 0x7F,
|
||||
VKEY_F17 = 0x80,
|
||||
VKEY_F18 = 0x81,
|
||||
VKEY_F19 = 0x82,
|
||||
VKEY_F20 = 0x83,
|
||||
VKEY_F21 = 0x84,
|
||||
VKEY_F22 = 0x85,
|
||||
VKEY_F23 = 0x86,
|
||||
VKEY_F24 = 0x87,
|
||||
VKEY_NUMLOCK = 0x90,
|
||||
VKEY_SCROLL = 0x91,
|
||||
VKEY_LSHIFT = 0xA0,
|
||||
VKEY_RSHIFT = 0xA1,
|
||||
VKEY_LCONTROL = 0xA2,
|
||||
VKEY_RCONTROL = 0xA3,
|
||||
VKEY_LMENU = 0xA4,
|
||||
VKEY_RMENU = 0xA5,
|
||||
VKEY_BROWSER_BACK = 0xA6,
|
||||
VKEY_BROWSER_FORWARD = 0xA7,
|
||||
VKEY_BROWSER_REFRESH = 0xA8,
|
||||
VKEY_BROWSER_STOP = 0xA9,
|
||||
VKEY_BROWSER_SEARCH = 0xAA,
|
||||
VKEY_BROWSER_FAVORITES = 0xAB,
|
||||
VKEY_BROWSER_HOME = 0xAC,
|
||||
VKEY_VOLUME_MUTE = 0xAD,
|
||||
VKEY_VOLUME_DOWN = 0xAE,
|
||||
VKEY_VOLUME_UP = 0xAF,
|
||||
VKEY_MEDIA_NEXT_TRACK = 0xB0,
|
||||
VKEY_MEDIA_PREV_TRACK = 0xB1,
|
||||
VKEY_MEDIA_STOP = 0xB2,
|
||||
VKEY_MEDIA_PLAY_PAUSE = 0xB3,
|
||||
VKEY_MEDIA_LAUNCH_MAIL = 0xB4,
|
||||
VKEY_MEDIA_LAUNCH_MEDIA_SELECT = 0xB5,
|
||||
VKEY_MEDIA_LAUNCH_APP1 = 0xB6,
|
||||
VKEY_MEDIA_LAUNCH_APP2 = 0xB7,
|
||||
VKEY_OEM_1 = 0xBA,
|
||||
VKEY_OEM_PLUS = 0xBB,
|
||||
VKEY_OEM_COMMA = 0xBC,
|
||||
VKEY_OEM_MINUS = 0xBD,
|
||||
VKEY_OEM_PERIOD = 0xBE,
|
||||
VKEY_OEM_2 = 0xBF,
|
||||
VKEY_OEM_3 = 0xC0,
|
||||
VKEY_OEM_4 = 0xDB,
|
||||
VKEY_OEM_5 = 0xDC,
|
||||
VKEY_OEM_6 = 0xDD,
|
||||
VKEY_OEM_7 = 0xDE,
|
||||
VKEY_OEM_8 = 0xDF,
|
||||
VKEY_OEM_102 = 0xE2,
|
||||
VKEY_OEM_103 = 0xE3, // GTV KEYCODE_MEDIA_REWIND
|
||||
VKEY_OEM_104 = 0xE4, // GTV KEYCODE_MEDIA_FAST_FORWARD
|
||||
VKEY_PROCESSKEY = 0xE5,
|
||||
VKEY_PACKET = 0xE7,
|
||||
VKEY_OEM_ATTN = 0xF0, // JIS DomKey::ALPHANUMERIC
|
||||
VKEY_OEM_FINISH = 0xF1, // JIS DomKey::KATAKANA
|
||||
VKEY_OEM_COPY = 0xF2, // JIS DomKey::HIRAGANA
|
||||
VKEY_DBE_SBCSCHAR = 0xF3, // JIS DomKey::HANKAKU
|
||||
VKEY_DBE_DBCSCHAR = 0xF4, // JIS DomKey::ZENKAKU
|
||||
VKEY_OEM_BACKTAB = 0xF5, // JIS DomKey::ROMAJI
|
||||
VKEY_ATTN = 0xF6, // DomKey::ATTN or JIS DomKey::KANA_MODE
|
||||
VKEY_CRSEL = 0xF7,
|
||||
VKEY_EXSEL = 0xF8,
|
||||
VKEY_EREOF = 0xF9,
|
||||
VKEY_PLAY = 0xFA,
|
||||
VKEY_ZOOM = 0xFB,
|
||||
VKEY_NONAME = 0xFC,
|
||||
VKEY_PA1 = 0xFD,
|
||||
VKEY_OEM_CLEAR = 0xFE,
|
||||
VKEY_UNKNOWN = 0,
|
||||
|
||||
// POSIX specific VKEYs. Note that as of Windows SDK 7.1, 0x97-9F, 0xD8-DA,
|
||||
// and 0xE8 are unassigned.
|
||||
VKEY_WLAN = 0x97,
|
||||
VKEY_POWER = 0x98,
|
||||
VKEY_ASSISTANT = 0x99,
|
||||
VKEY_SETTINGS = 0x9A,
|
||||
VKEY_PRIVACY_SCREEN_TOGGLE = 0x9B,
|
||||
VKEY_BRIGHTNESS_DOWN = 0xD8,
|
||||
VKEY_BRIGHTNESS_UP = 0xD9,
|
||||
VKEY_KBD_BRIGHTNESS_DOWN = 0xDA,
|
||||
VKEY_KBD_BRIGHTNESS_UP = 0xE8,
|
||||
|
||||
// Windows does not have a specific key code for AltGr. We use the unused 0xE1
|
||||
// (VK_OEM_AX) code to represent AltGr, matching the behaviour of Firefox on
|
||||
// Linux.
|
||||
VKEY_ALTGR = 0xE1,
|
||||
|
||||
// Windows does not have a specific key code for Compose. We use the unused
|
||||
// 0xE6 (VK_ICO_CLEAR) code to represent Compose.
|
||||
VKEY_COMPOSE = 0xE6,
|
||||
|
||||
// Windows does not have specific key codes for Media Play and Media Pause. We
|
||||
// use the unused 0xE9 (VK_OEM_RESET) and 0xEA (VK_OEM_JUMP) codes to
|
||||
// represent them.
|
||||
VKEY_MEDIA_PLAY = 0xE9,
|
||||
VKEY_MEDIA_PAUSE = 0xEA,
|
||||
};
|
||||
}
|
||||
}
|
||||
186
Robust.Client.CEF/CefManager.BrowserWindow.cs
Normal file
186
Robust.Client.CEF/CefManager.BrowserWindow.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public partial class CefManager
|
||||
{
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
|
||||
private readonly List<BrowserWindowImpl> _browserWindows = new();
|
||||
|
||||
public IEnumerable<IBrowserWindow> AllBrowserWindows => _browserWindows;
|
||||
|
||||
public IBrowserWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams)
|
||||
{
|
||||
var mainHWnd = (_clyde.MainWindow as IClydeWindowInternal)?.WindowsHWnd ?? 0;
|
||||
|
||||
var info = CefWindowInfo.Create();
|
||||
info.Width = createParams.Width;
|
||||
info.Height = createParams.Height;
|
||||
info.SetAsPopup(mainHWnd, "ss14cef");
|
||||
|
||||
var impl = new BrowserWindowImpl(this);
|
||||
|
||||
var lifeSpanHandler = new WindowLifeSpanHandler(impl);
|
||||
var reqHandler = new RobustRequestHandler(Logger.GetSawmill("root"));
|
||||
var client = new WindowCefClient(lifeSpanHandler, reqHandler);
|
||||
var settings = new CefBrowserSettings();
|
||||
|
||||
impl.Browser = CefBrowserHost.CreateBrowserSync(info, client, settings, createParams.Url);
|
||||
impl.RequestHandler = reqHandler;
|
||||
|
||||
_browserWindows.Add(impl);
|
||||
|
||||
return impl;
|
||||
}
|
||||
|
||||
private sealed class BrowserWindowImpl : IBrowserWindow
|
||||
{
|
||||
private readonly CefManager _manager;
|
||||
internal CefBrowser Browser = default!;
|
||||
internal RobustRequestHandler RequestHandler = default!;
|
||||
|
||||
public Action<RequestHandlerContext>? OnResourceRequest { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Url
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckClosed();
|
||||
return Browser.GetMainFrame().Url;
|
||||
}
|
||||
set
|
||||
{
|
||||
CheckClosed();
|
||||
Browser.GetMainFrame().LoadUrl(value);
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public bool IsLoading
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckClosed();
|
||||
return Browser.IsLoading;
|
||||
}
|
||||
}
|
||||
|
||||
public BrowserWindowImpl(CefManager manager)
|
||||
{
|
||||
_manager = manager;
|
||||
}
|
||||
|
||||
public void StopLoad()
|
||||
{
|
||||
CheckClosed();
|
||||
Browser.StopLoad();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
CheckClosed();
|
||||
Browser.Reload();
|
||||
}
|
||||
|
||||
public bool GoBack()
|
||||
{
|
||||
CheckClosed();
|
||||
if (!Browser.CanGoBack)
|
||||
return false;
|
||||
|
||||
Browser.GoBack();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool GoForward()
|
||||
{
|
||||
CheckClosed();
|
||||
if (!Browser.CanGoForward)
|
||||
return false;
|
||||
|
||||
Browser.GoForward();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ExecuteJavaScript(string code)
|
||||
{
|
||||
CheckClosed();
|
||||
Browser.GetMainFrame().ExecuteJavaScript(code, string.Empty, 1);
|
||||
}
|
||||
|
||||
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
RequestHandler.AddResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
RequestHandler.RemoveResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Closed)
|
||||
return;
|
||||
|
||||
Browser.GetHost().CloseBrowser(true);
|
||||
Closed = true;
|
||||
}
|
||||
|
||||
public bool Closed { get; private set; }
|
||||
|
||||
public void OnClose()
|
||||
{
|
||||
Closed = true;
|
||||
_manager._browserWindows.Remove(this);
|
||||
Logger.Debug("Removing window");
|
||||
}
|
||||
|
||||
private void CheckClosed()
|
||||
{
|
||||
if (Closed)
|
||||
throw new ObjectDisposedException("BrowserWindow");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class WindowCefClient : CefClient
|
||||
{
|
||||
private readonly CefLifeSpanHandler _lifeSpanHandler;
|
||||
private readonly CefRequestHandler _requestHandler;
|
||||
|
||||
public WindowCefClient(CefLifeSpanHandler lifeSpanHandler, CefRequestHandler requestHandler)
|
||||
{
|
||||
_lifeSpanHandler = lifeSpanHandler;
|
||||
_requestHandler = requestHandler;
|
||||
}
|
||||
|
||||
protected override CefLifeSpanHandler GetLifeSpanHandler() => _lifeSpanHandler;
|
||||
protected override CefRequestHandler GetRequestHandler() => _requestHandler;
|
||||
}
|
||||
|
||||
private sealed class WindowLifeSpanHandler : CefLifeSpanHandler
|
||||
{
|
||||
private readonly BrowserWindowImpl _windowImpl;
|
||||
|
||||
public WindowLifeSpanHandler(BrowserWindowImpl windowImpl)
|
||||
{
|
||||
_windowImpl = windowImpl;
|
||||
}
|
||||
|
||||
protected override void OnBeforeClose(CefBrowser browser)
|
||||
{
|
||||
base.OnBeforeClose(browser);
|
||||
|
||||
_windowImpl.OnClose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
104
Robust.Client.CEF/CefManager.cs
Normal file
104
Robust.Client.CEF/CefManager.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
// The library we're using right now. TODO CEF: Do we want to use something else? We will need to ship it ourselves if so.
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
// Register this with IoC.
|
||||
// TODO CEF: think if making this inherit CefApp is a good idea...
|
||||
// TODO CEF: A way to handle external window browsers...
|
||||
[UsedImplicitly]
|
||||
public partial class CefManager
|
||||
{
|
||||
private CefApp _app = default!;
|
||||
private bool _initialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// Call this to initialize CEF.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
DebugTools.Assert(!_initialized);
|
||||
|
||||
string subProcessName;
|
||||
if (OperatingSystem.IsWindows())
|
||||
subProcessName = "Robust.Client.CEF.exe";
|
||||
else if (OperatingSystem.IsLinux())
|
||||
subProcessName = "Robust.Client.CEF";
|
||||
else
|
||||
throw new NotSupportedException("Unsupported platform for CEF!");
|
||||
|
||||
var subProcessPath = PathHelpers.ExecutableRelativeFile(subProcessName);
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
WindowlessRenderingEnabled = true, // So we can render to our UI controls.
|
||||
ExternalMessagePump = false, // Unsure, honestly. TODO CEF: Research this?
|
||||
NoSandbox = true, // Not disabling the sandbox crashes CEF.
|
||||
BrowserSubprocessPath = subProcessPath,
|
||||
LocalesDirPath = Path.Combine(PathHelpers.GetExecutableDirectory(), "locales"),
|
||||
ResourcesDirPath = PathHelpers.GetExecutableDirectory(),
|
||||
RemoteDebuggingPort = 9222
|
||||
};
|
||||
|
||||
Logger.Info($"CEF Version: {CefRuntime.ChromeVersion}");
|
||||
|
||||
// --------------------------- README --------------------------------------------------
|
||||
// By the way! You're gonna need the CEF binaries in your client's bin folder.
|
||||
// More specifically, version cef_binary_91.1.21+g9dd45fe+chromium-91.0.4472.114
|
||||
// https://cef-builds.spotifycdn.com/cef_binary_91.1.21%2Bg9dd45fe%2Bchromium-91.0.4472.114_windows64_minimal.tar.bz2
|
||||
// https://cef-builds.spotifycdn.com/cef_binary_91.1.21%2Bg9dd45fe%2Bchromium-91.0.4472.114_linux64_minimal.tar.bz2
|
||||
// Here's how to get it to work:
|
||||
// 1. Copy all the contents of "Release" to the bin folder.
|
||||
// 2. Copy all the contents of "Resources" to the bin folder.
|
||||
// Supposedly, you should just need libcef.so in Release and icudtl.dat in Resources...
|
||||
// The rest might be optional.
|
||||
// Maybe. Good luck! If you get odd crashes with no info and a weird exit code, use GDB!
|
||||
// -------------------------------------------------------------------------------------
|
||||
|
||||
_app = new RobustCefApp();
|
||||
|
||||
// We pass no main arguments...
|
||||
CefRuntime.Initialize(new CefMainArgs(null), settings, _app, IntPtr.Zero);
|
||||
|
||||
// TODO CEF: After this point, debugging breaks. No, literally. My client crashes but ONLY with the debugger.
|
||||
// I have tried using the DEBUG and RELEASE versions of libcef.so, stripped or non-stripped...
|
||||
// And nothing seemed to work. Odd.
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
public void CheckInitialized()
|
||||
{
|
||||
if (!_initialized)
|
||||
throw new InvalidOperationException("CefManager has not been initialized!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Needs to be called regularly for CEF to keep working.
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
DebugTools.Assert(_initialized);
|
||||
|
||||
// Calling this makes CEF do its work, without using its own update loop.
|
||||
CefRuntime.DoMessageLoopWork();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call before program shutdown.
|
||||
/// </summary>
|
||||
public void Shutdown()
|
||||
{
|
||||
DebugTools.Assert(_initialized);
|
||||
|
||||
CefRuntime.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Robust.Client.CEF/IBrowserControl.cs
Normal file
48
Robust.Client.CEF/IBrowserControl.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public interface IBrowserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Current URL of the browser. Set to load a new page.
|
||||
/// </summary>
|
||||
string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the browser is currently loading a page.
|
||||
/// </summary>
|
||||
bool IsLoading { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Stops loading the current page.
|
||||
/// </summary>
|
||||
void StopLoad();
|
||||
|
||||
/// <summary>
|
||||
/// Reload the current page.
|
||||
/// </summary>
|
||||
void Reload();
|
||||
|
||||
/// <summary>
|
||||
/// Navigate back.
|
||||
/// </summary>
|
||||
/// <returns>Whether the browser could navigate back.</returns>
|
||||
bool GoBack();
|
||||
|
||||
/// <summary>
|
||||
/// Navigate forward.
|
||||
/// </summary>
|
||||
/// <returns>Whether the browser could navigate forward.</returns>
|
||||
bool GoForward();
|
||||
|
||||
/// <summary>
|
||||
/// Execute arbitrary JavaScript on the current page.
|
||||
/// </summary>
|
||||
/// <param name="code">JavaScript code.</param>
|
||||
void ExecuteJavaScript(string code);
|
||||
|
||||
void AddResourceRequestHandler(Action<RequestHandlerContext> handler);
|
||||
void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler);
|
||||
}
|
||||
}
|
||||
9
Robust.Client.CEF/IBrowserWindow.cs
Normal file
9
Robust.Client.CEF/IBrowserWindow.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public interface IBrowserWindow : IBrowserControl, IDisposable
|
||||
{
|
||||
bool Closed { get; }
|
||||
}
|
||||
}
|
||||
42
Robust.Client.CEF/ImageBuffer.cs
Normal file
42
Robust.Client.CEF/ImageBuffer.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Maths;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
internal sealed class ImageBuffer
|
||||
{
|
||||
private readonly Control _control;
|
||||
|
||||
public ImageBuffer(Control control)
|
||||
{
|
||||
_control = control;
|
||||
}
|
||||
|
||||
public Image<Bgra32> Buffer { get; private set; } = new(1, 1);
|
||||
|
||||
public unsafe void UpdateBuffer(int width, int height, IntPtr buffer, CefRectangle dirtyRect)
|
||||
{
|
||||
if (width != Buffer.Width || height != Buffer.Height)
|
||||
UpdateSize(width, height);
|
||||
|
||||
var span = new ReadOnlySpan<Bgra32>((void*) buffer, width * height);
|
||||
|
||||
ImageSharpExt.Blit(
|
||||
span,
|
||||
width,
|
||||
UIBox2i.FromDimensions(dirtyRect.X, dirtyRect.Y, dirtyRect.Width, dirtyRect.Height),
|
||||
Buffer,
|
||||
(dirtyRect.X, dirtyRect.Y));
|
||||
}
|
||||
|
||||
private void UpdateSize(int width, int height)
|
||||
{
|
||||
Buffer = new Image<Bgra32>(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Robust.Client.CEF/Program.cs
Normal file
33
Robust.Client.CEF/Program.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
// This was supposed to be the main entry for the subprocess program... It doesn't work.
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
// This is a workaround for this to work on UNIX.
|
||||
var argv = args;
|
||||
if (CefRuntime.Platform != CefRuntimePlatform.Windows)
|
||||
{
|
||||
argv = new string[args.Length + 1];
|
||||
Array.Copy(args, 0, argv, 1, args.Length);
|
||||
argv[0] = "-";
|
||||
}
|
||||
|
||||
var mainArgs = new CefMainArgs(argv);
|
||||
|
||||
// This will block executing until the subprocess is shut down.
|
||||
var code = CefRuntime.ExecuteProcess(mainArgs, null, IntPtr.Zero);
|
||||
|
||||
if (code != 0)
|
||||
{
|
||||
System.Console.WriteLine($"CEF Subprocess exited unsuccessfully with exit code {code}! Arguments: {string.Join(' ', argv)}");
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Robust.Client.CEF/RequestHandlerContext.cs
Normal file
56
Robust.Client.CEF/RequestHandlerContext.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public sealed class RequestHandlerContext
|
||||
{
|
||||
internal readonly CefRequest CefRequest;
|
||||
|
||||
public bool IsNavigation { get; }
|
||||
public bool IsDownload { get; }
|
||||
public string RequestInitiator { get; }
|
||||
|
||||
public string Url => CefRequest.Url;
|
||||
public string Method => CefRequest.Method;
|
||||
|
||||
public bool IsHandled { get; private set; }
|
||||
|
||||
public bool IsCancelled { get; private set; }
|
||||
|
||||
internal IRequestResult? Result { get; private set; }
|
||||
|
||||
internal RequestHandlerContext(
|
||||
bool isNavigation,
|
||||
bool isDownload,
|
||||
string requestInitiator,
|
||||
CefRequest cefRequest)
|
||||
{
|
||||
CefRequest = cefRequest;
|
||||
IsNavigation = isNavigation;
|
||||
IsDownload = isDownload;
|
||||
RequestInitiator = requestInitiator;
|
||||
}
|
||||
|
||||
public void DoCancel()
|
||||
{
|
||||
CheckNotHandled();
|
||||
|
||||
IsHandled = true;
|
||||
IsCancelled = true;
|
||||
}
|
||||
|
||||
public void DoRespondStream(Stream stream, string contentType, HttpStatusCode code = HttpStatusCode.OK)
|
||||
{
|
||||
Result = new RequestResultStream(stream, contentType, code);
|
||||
}
|
||||
|
||||
private void CheckNotHandled()
|
||||
{
|
||||
if (IsHandled)
|
||||
throw new InvalidOperationException("Request has already been handled");
|
||||
}
|
||||
}
|
||||
}
|
||||
93
Robust.Client.CEF/RequestResults.cs
Normal file
93
Robust.Client.CEF/RequestResults.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
internal interface IRequestResult
|
||||
{
|
||||
CefResourceHandler MakeHandler();
|
||||
}
|
||||
|
||||
internal sealed class RequestResultStream : IRequestResult
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
private readonly HttpStatusCode _code;
|
||||
private readonly string _contentType;
|
||||
|
||||
public RequestResultStream(Stream stream, string contentType, HttpStatusCode code)
|
||||
{
|
||||
_stream = stream;
|
||||
_code = code;
|
||||
_contentType = contentType;
|
||||
}
|
||||
|
||||
public CefResourceHandler MakeHandler()
|
||||
{
|
||||
return new Handler(_stream, _contentType, _code);
|
||||
}
|
||||
|
||||
private sealed class Handler : CefResourceHandler
|
||||
{
|
||||
// TODO: async
|
||||
// TODO: exception handling
|
||||
|
||||
private readonly Stream _stream;
|
||||
private readonly HttpStatusCode _code;
|
||||
private readonly string _contentType;
|
||||
|
||||
public Handler(Stream stream, string contentType, HttpStatusCode code)
|
||||
{
|
||||
_stream = stream;
|
||||
_code = code;
|
||||
_contentType = contentType;
|
||||
}
|
||||
|
||||
protected override bool Open(CefRequest request, out bool handleRequest, CefCallback callback)
|
||||
{
|
||||
handleRequest = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void GetResponseHeaders(CefResponse response, out long responseLength, out string? redirectUrl)
|
||||
{
|
||||
response.Status = (int) _code;
|
||||
response.StatusText = _code.ToString();
|
||||
response.MimeType = _contentType;
|
||||
|
||||
if (_stream.CanSeek)
|
||||
responseLength = _stream.Length;
|
||||
else
|
||||
responseLength = -1;
|
||||
|
||||
redirectUrl = default;
|
||||
}
|
||||
|
||||
protected override bool Skip(long bytesToSkip, out long bytesSkipped, CefResourceSkipCallback callback)
|
||||
{
|
||||
if (!_stream.CanSeek)
|
||||
{
|
||||
bytesSkipped = -2;
|
||||
return false;
|
||||
}
|
||||
|
||||
bytesSkipped = _stream.Seek(bytesToSkip, SeekOrigin.Begin);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override unsafe bool Read(IntPtr dataOut, int bytesToRead, out int bytesRead, CefResourceReadCallback callback)
|
||||
{
|
||||
var byteSpan = new Span<byte>((void*) dataOut, bytesToRead);
|
||||
|
||||
bytesRead = _stream.Read(byteSpan);
|
||||
|
||||
return bytesRead != 0;
|
||||
}
|
||||
|
||||
protected override void Cancel()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Robust.Client.CEF/Robust.Client.CEF.csproj
Normal file
25
Robust.Client.CEF/Robust.Client.CEF.csproj
Normal file
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets" />
|
||||
<Import Project="..\MSBuild\Robust.Engine.props" />
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<OutputType>WinExe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
|
||||
<Target Name="RobustAfterBuild" AfterTargets="Build" />
|
||||
<Import Project="..\MSBuild\Robust.Engine.targets" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="5.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\cefglue\CefGlue\CefGlue.csproj" />
|
||||
<ProjectReference Include="..\Robust.Client\Robust.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
48
Robust.Client.CEF/RobustCefApp.cs
Normal file
48
Robust.Client.CEF/RobustCefApp.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
internal class RobustCefApp : CefApp
|
||||
{
|
||||
private readonly BrowserProcessHandler _browserProcessHandler = new();
|
||||
private readonly RenderProcessHandler _renderProcessHandler = new();
|
||||
|
||||
protected override CefBrowserProcessHandler GetBrowserProcessHandler()
|
||||
{
|
||||
return _browserProcessHandler;
|
||||
}
|
||||
|
||||
protected override CefRenderProcessHandler GetRenderProcessHandler()
|
||||
{
|
||||
return _renderProcessHandler;
|
||||
}
|
||||
|
||||
protected override void OnBeforeCommandLineProcessing(string processType, CefCommandLine commandLine)
|
||||
{
|
||||
// Disable zygote on Linux.
|
||||
commandLine.AppendSwitch("--no-zygote");
|
||||
|
||||
//commandLine.AppendSwitch("--disable-gpu");
|
||||
//commandLine.AppendSwitch("--disable-gpu-compositing");
|
||||
//commandLine.AppendSwitch("--in-process-gpu");
|
||||
|
||||
commandLine.AppendSwitch("disable-threaded-scrolling", "1");
|
||||
commandLine.AppendSwitch("disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents");
|
||||
|
||||
if(IoCManager.Instance != null)
|
||||
Logger.Debug($"{commandLine}");
|
||||
}
|
||||
|
||||
private class BrowserProcessHandler : CefBrowserProcessHandler
|
||||
{
|
||||
}
|
||||
|
||||
// TODO CEF: Research - Is this even needed?
|
||||
private class RenderProcessHandler : CefRenderProcessHandler
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Robust.Client.CEF/RobustCefClient.cs
Normal file
23
Robust.Client.CEF/RobustCefClient.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
// Simple CEF client.
|
||||
internal class RobustCefClient : CefClient
|
||||
{
|
||||
private readonly CefRenderHandler _renderHandler;
|
||||
private readonly CefRequestHandler _requestHandler;
|
||||
private readonly CefLoadHandler _loadHandler;
|
||||
|
||||
internal RobustCefClient(CefRenderHandler handler, CefRequestHandler requestHandler, CefLoadHandler loadHandler)
|
||||
{
|
||||
_renderHandler = handler;
|
||||
_requestHandler = requestHandler;
|
||||
_loadHandler = loadHandler;
|
||||
}
|
||||
|
||||
protected override CefRenderHandler GetRenderHandler() => _renderHandler;
|
||||
protected override CefRequestHandler GetRequestHandler() => _requestHandler;
|
||||
protected override CefLoadHandler GetLoadHandler() => _loadHandler;
|
||||
}
|
||||
}
|
||||
17
Robust.Client.CEF/RobustLoadHandler.cs
Normal file
17
Robust.Client.CEF/RobustLoadHandler.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public sealed class RobustLoadHandler : CefLoadHandler
|
||||
{
|
||||
protected override void OnLoadStart(CefBrowser browser, CefFrame frame, CefTransitionType transitionType)
|
||||
{
|
||||
base.OnLoadStart(browser, frame, transitionType);
|
||||
}
|
||||
|
||||
protected override void OnLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode)
|
||||
{
|
||||
base.OnLoadEnd(browser, frame, httpStatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
128
Robust.Client.CEF/RobustRequestHandler.cs
Normal file
128
Robust.Client.CEF/RobustRequestHandler.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Log;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
internal sealed class RobustRequestHandler : CefRequestHandler
|
||||
{
|
||||
private readonly ISawmill _sawmill;
|
||||
private readonly List<Action<RequestHandlerContext>> _resourceRequestHandlers = new();
|
||||
private readonly List<Action<BeforeBrowseContext>> _beforeBrowseHandlers = new();
|
||||
|
||||
public RobustRequestHandler(ISawmill sawmill)
|
||||
{
|
||||
_sawmill = sawmill;
|
||||
}
|
||||
|
||||
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
lock (_resourceRequestHandlers)
|
||||
{
|
||||
_resourceRequestHandlers.Add(handler);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
lock (_resourceRequestHandlers)
|
||||
{
|
||||
_resourceRequestHandlers.Remove(handler);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
|
||||
{
|
||||
lock (_beforeBrowseHandlers)
|
||||
{
|
||||
_beforeBrowseHandlers.Add(handler);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
|
||||
{
|
||||
lock (_beforeBrowseHandlers)
|
||||
{
|
||||
_beforeBrowseHandlers.Remove(handler);
|
||||
}
|
||||
}
|
||||
|
||||
protected override CefResourceRequestHandler? GetResourceRequestHandler(
|
||||
CefBrowser browser,
|
||||
CefFrame frame,
|
||||
CefRequest request,
|
||||
bool isNavigation,
|
||||
bool isDownload,
|
||||
string requestInitiator,
|
||||
ref bool disableDefaultHandling)
|
||||
{
|
||||
lock (_resourceRequestHandlers)
|
||||
{
|
||||
_sawmill.Debug($"HANDLING REQUEST: {request.Url}");
|
||||
|
||||
var context = new RequestHandlerContext(isNavigation, isDownload, requestInitiator, request);
|
||||
|
||||
foreach (var handler in _resourceRequestHandlers)
|
||||
{
|
||||
handler(context);
|
||||
|
||||
if (context.IsHandled)
|
||||
disableDefaultHandling = true;
|
||||
|
||||
if (context.IsCancelled)
|
||||
return null;
|
||||
|
||||
if (context.Result != null)
|
||||
return new WrapReaderResourceHandler(context.Result.MakeHandler());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override bool OnBeforeBrowse(CefBrowser browser, CefFrame frame, CefRequest request, bool userGesture, bool isRedirect)
|
||||
{
|
||||
lock (_beforeBrowseHandlers)
|
||||
{
|
||||
var context = new BeforeBrowseContext(isRedirect, userGesture, request);
|
||||
|
||||
foreach (var handler in _beforeBrowseHandlers)
|
||||
{
|
||||
handler(context);
|
||||
|
||||
if (context.IsCancelled)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private sealed class WrapReaderResourceHandler : CefResourceRequestHandler
|
||||
{
|
||||
private readonly CefResourceHandler _handler;
|
||||
|
||||
public WrapReaderResourceHandler(CefResourceHandler handler)
|
||||
{
|
||||
_handler = handler;
|
||||
}
|
||||
|
||||
protected override CefCookieAccessFilter? GetCookieAccessFilter(
|
||||
CefBrowser browser,
|
||||
CefFrame frame,
|
||||
CefRequest request)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override CefResourceHandler GetResourceHandler(
|
||||
CefBrowser browser,
|
||||
CefFrame frame,
|
||||
CefRequest request)
|
||||
{
|
||||
return _handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,26 +29,27 @@ namespace Robust.Build.Tasks
|
||||
}
|
||||
}
|
||||
|
||||
//formatted according to https://github.com/dotnet/msbuild/blob/main/src/Shared/CanonicalError.cs#L57
|
||||
class ConsoleBuildEngine : IBuildEngine
|
||||
{
|
||||
public void LogErrorEvent(BuildErrorEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"ERROR: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
|
||||
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL ERROR {e.Code}: {e.Message}");
|
||||
}
|
||||
|
||||
public void LogWarningEvent(BuildWarningEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"WARNING: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
|
||||
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL WARNING {e.Code}: {e.Message}");
|
||||
}
|
||||
|
||||
public void LogMessageEvent(BuildMessageEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"MESSAGE: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
|
||||
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL MESSAGE {e.Code}: {e.Message}");
|
||||
}
|
||||
|
||||
public void LogCustomEvent(CustomBuildEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"CUSTOM: {e.Message}");
|
||||
Console.WriteLine(e.Message);
|
||||
}
|
||||
|
||||
public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using XamlX;
|
||||
using XamlX.Ast;
|
||||
using XamlX.Emit;
|
||||
using XamlX.IL;
|
||||
@@ -11,11 +13,14 @@ namespace Robust.Build.Tasks
|
||||
/// Emitters & Transformers based on:
|
||||
/// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs
|
||||
/// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
|
||||
/// - https://github.com/AvaloniaUI/Avalonia/blob/afb8ae6f3c517dae912729511483995b16cb31af/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
|
||||
/// </summary>
|
||||
public class RobustXamlILCompiler : XamlILCompiler
|
||||
{
|
||||
public RobustXamlILCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings, bool fillWithDefaults) : base(configuration, emitMappings, fillWithDefaults)
|
||||
{
|
||||
Transformers.Insert(0, new IgnoredDirectivesTransformer());
|
||||
|
||||
Transformers.Add(new AddNameScopeRegistration());
|
||||
Transformers.Add(new RobustMarkRootObjectScopeNode());
|
||||
|
||||
@@ -197,5 +202,24 @@ namespace Robust.Build.Tasks
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IgnoredDirectivesTransformer : IXamlAstTransformer
|
||||
{
|
||||
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
|
||||
{
|
||||
if (node is XamlAstObjectNode astNode)
|
||||
{
|
||||
astNode.Children.RemoveAll(n =>
|
||||
n is XamlAstXmlDirective dir &&
|
||||
dir.Namespace == XamlNamespaces.Xaml2006 &&
|
||||
(dir.Name == "Class" ||
|
||||
dir.Name == "Precompile" ||
|
||||
dir.Name == "FieldModifier" ||
|
||||
dir.Name == "ClassModifier"));
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,8 +280,8 @@ namespace Robust.Build.Tasks
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
engine.LogWarningEvent(new BuildWarningEventArgs("XAMLIL", "", res.Uri, 0, 0, 0, 0,
|
||||
e.ToString(), "", "CompileRobustXaml"));
|
||||
engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0,
|
||||
$"{res.FilePath}: {e.Message}", "", "CompileRobustXaml"));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Robust.Client.Audio.Midi
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
|
||||
|
||||
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
|
||||
private SharedBroadphaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public bool IsAvailable
|
||||
@@ -175,7 +175,7 @@ namespace Robust.Client.Audio.Midi
|
||||
_midiThread = new Thread(ThreadUpdate);
|
||||
_midiThread.Start();
|
||||
|
||||
_broadPhaseSystem = EntitySystem.Get<SharedBroadPhaseSystem>();
|
||||
_broadPhaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
|
||||
FluidsynthInitialized = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using NFluidsynth;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Asynchronous;
|
||||
@@ -8,7 +7,6 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using MidiEvent = NFluidsynth.MidiEvent;
|
||||
|
||||
@@ -220,7 +218,7 @@ namespace Robust.Client.Audio.Midi
|
||||
set
|
||||
{
|
||||
lock (_playerStateLock)
|
||||
for (var i = 0; i < 16; i++)
|
||||
for (var i = 0; i < _synth.MidiChannelCount; i++)
|
||||
_synth.ProgramChange(i, value);
|
||||
|
||||
_midiProgram = value;
|
||||
@@ -234,7 +232,7 @@ namespace Robust.Client.Audio.Midi
|
||||
set
|
||||
{
|
||||
lock (_playerStateLock)
|
||||
for (var i = 0; i < 16; i++)
|
||||
for (var i = 0; i < _synth.MidiChannelCount; i++)
|
||||
_synth.BankSelect(i, value);
|
||||
|
||||
_midiBank = value;
|
||||
@@ -248,7 +246,7 @@ namespace Robust.Client.Audio.Midi
|
||||
set
|
||||
{
|
||||
lock (_playerStateLock)
|
||||
for (var i = 0; i < 16; i++)
|
||||
for (var i = 0; i < _synth.MidiChannelCount; i++)
|
||||
_synth.SoundFontSelect(i, value);
|
||||
|
||||
_midiSoundfont = value;
|
||||
@@ -575,16 +573,20 @@ namespace Robust.Client.Audio.Midi
|
||||
// Sometimes MIDI files spam these for no good reason and I can't find any info on what they are.
|
||||
case 1:
|
||||
case 5:
|
||||
// MetaEvent -- SetTempo - 0x51
|
||||
case 81:
|
||||
// Already handled by the player.
|
||||
return;
|
||||
// System Messages - 0xF0
|
||||
case 240:
|
||||
switch (midiEvent.Control)
|
||||
switch ((byte)midiEvent.Control)
|
||||
{
|
||||
case 11:
|
||||
_synth.AllNotesOff(midiEvent.Channel);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
_midiSawmill.Warning("Unhandled midi event of type {0}", midiEvent.Type, midiEvent);
|
||||
@@ -634,7 +636,7 @@ namespace Robust.Client.Audio.Midi
|
||||
_sequencer?.UnregisterClient(_debugRegister);
|
||||
_sequencer?.UnregisterClient(_synthRegister);
|
||||
_sequencer?.Dispose();
|
||||
|
||||
|
||||
_synth?.Dispose();
|
||||
_player?.Dispose();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net;
|
||||
using Robust.Client.Debugging;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
@@ -25,6 +25,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
@@ -76,6 +77,7 @@ namespace Robust.Client
|
||||
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
|
||||
IoCManager.Register<IMidiManager, MidiManager>();
|
||||
IoCManager.Register<IAuthManager, AuthManager>();
|
||||
IoCManager.Register<IPhysicsManager, PhysicsManager>();
|
||||
switch (mode)
|
||||
{
|
||||
case GameController.DisplayMode.Headless:
|
||||
|
||||
@@ -83,6 +83,8 @@ namespace Robust.Client.Console
|
||||
OutputText(text, true, true);
|
||||
}
|
||||
|
||||
public override event ConAnyCommandCallback? AnyCommandExecuted;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ExecuteCommand(ICommonSession? session, string command)
|
||||
{
|
||||
@@ -103,7 +105,11 @@ namespace Robust.Client.Console
|
||||
{
|
||||
var command1 = AvailableCommands[commandName];
|
||||
args.RemoveAt(0);
|
||||
command1.Execute(new ConsoleShell(this, null), command, args.ToArray());
|
||||
var shell = new ConsoleShell(this, null);
|
||||
var cmdArgs = args.ToArray();
|
||||
|
||||
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
|
||||
command1.Execute(shell, command, cmdArgs);
|
||||
}
|
||||
else
|
||||
WriteError(null, "Unknown command: " + commandName);
|
||||
|
||||
@@ -27,6 +27,7 @@ using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
@@ -459,13 +460,18 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var root = IoCManager.Resolve<IUserInterfaceManager>().RootControl;
|
||||
var uiMgr = IoCManager.Resolve<IUserInterfaceManager>();
|
||||
var res = IoCManager.Resolve<IResourceManager>();
|
||||
|
||||
using (var stream = res.UserData.Create(new ResourcePath("/guidump.txt")))
|
||||
using (var writer = new StreamWriter(stream, EncodingHelpers.UTF8))
|
||||
{
|
||||
_writeNode(root, 0, writer);
|
||||
foreach (var root in uiMgr.AllRoots)
|
||||
{
|
||||
writer.WriteLine($"ROOT: {root}");
|
||||
_writeNode(root, 0, writer);
|
||||
writer.WriteLine("---------------");
|
||||
}
|
||||
}
|
||||
|
||||
shell.WriteLine("Saved guidump");
|
||||
@@ -475,7 +481,7 @@ namespace Robust.Client.Console.Commands
|
||||
{
|
||||
var indentation = new string(' ', indents * 2);
|
||||
writer.WriteLine("{0}{1}", indentation, control);
|
||||
foreach (var (key, value) in _propertyValuesFor(control))
|
||||
foreach (var (key, value) in PropertyValuesFor(control))
|
||||
{
|
||||
writer.WriteLine("{2} * {0}: {1}", key, value, indentation);
|
||||
}
|
||||
@@ -486,14 +492,14 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
private static List<(string, string)> _propertyValuesFor(Control control)
|
||||
internal static List<(string, string)> PropertyValuesFor(Control control)
|
||||
{
|
||||
var members = new List<(string, string)>();
|
||||
var type = control.GetType();
|
||||
|
||||
foreach (var fieldInfo in type.GetAllFields())
|
||||
{
|
||||
if (fieldInfo.GetCustomAttribute<ViewVariablesAttribute>() == null)
|
||||
if (!ViewVariablesUtility.TryGetViewVariablesAccess(fieldInfo, out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -503,7 +509,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
foreach (var propertyInfo in type.GetAllProperties())
|
||||
{
|
||||
if (propertyInfo.GetCustomAttribute<ViewVariablesAttribute>() == null)
|
||||
if (!ViewVariablesUtility.TryGetViewVariablesAccess(propertyInfo, out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -536,7 +542,10 @@ namespace Robust.Client.Console.Commands
|
||||
var scroll = new ScrollContainer();
|
||||
tabContainer.AddChild(scroll);
|
||||
//scroll.SetAnchorAndMarginPreset(Control.LayoutPreset.Wide);
|
||||
var vBox = new VBoxContainer();
|
||||
var vBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
scroll.AddChild(vBox);
|
||||
|
||||
var progressBar = new ProgressBar { MaxValue = 10, Value = 5 };
|
||||
@@ -594,7 +603,10 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
|
||||
var group = new ButtonGroup();
|
||||
var vBoxRadioButtons = new VBoxContainer();
|
||||
var vBoxRadioButtons = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
vBoxRadioButtons.AddChild(new Button
|
||||
@@ -610,8 +622,9 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
TabContainer.SetTabTitle(vBoxRadioButtons, "Radio buttons!!");
|
||||
|
||||
tabContainer.AddChild(new VBoxContainer
|
||||
tabContainer.AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
Name = "Slider",
|
||||
Children =
|
||||
{
|
||||
@@ -863,7 +876,7 @@ namespace Robust.Client.Console.Commands
|
||||
var chunkIndex = grid.LocalToChunkIndices(grid.MapToGrid(mousePos));
|
||||
var chunk = internalGrid.GetChunk(chunkIndex);
|
||||
|
||||
shell.WriteLine($"worldBounds: {chunk.CalcWorldBounds()} localBounds: {chunk.CalcLocalBounds()}");
|
||||
shell.WriteLine($"worldBounds: {chunk.CalcWorldAABB()} localBounds: {chunk.CalcLocalBounds()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
17
Robust.Client/Console/Commands/GridChunkBBCommand.cs
Normal file
17
Robust.Client/Console/Commands/GridChunkBBCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public class GridChunkBBCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showchunkbb";
|
||||
public string Description => "Displays chunk bounds for the purposes of rendering";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<GridChunkBoundsDebugSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,11 @@ using Microsoft.CodeAnalysis.Text;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.ViewVariables;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Scripting;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
|
||||
#nullable enable
|
||||
|
||||
@@ -116,7 +117,7 @@ namespace Robust.Client.Console
|
||||
}
|
||||
else
|
||||
{
|
||||
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager);
|
||||
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager).AddReferences(typeof(Image).Assembly);
|
||||
newScript = CSharpScript.Create(code, options, typeof(ScriptGlobals));
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Scripting;
|
||||
using Robust.Shared.Timing;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.Console
|
||||
{
|
||||
@@ -18,7 +19,7 @@ namespace Robust.Client.Console
|
||||
{
|
||||
private readonly IReflectionManager _reflectionManager;
|
||||
|
||||
private readonly VBoxContainer _watchesVBox;
|
||||
private readonly BoxContainer _watchesVBox;
|
||||
private readonly LineEdit _addWatchEdit;
|
||||
private readonly Button _addWatchButton;
|
||||
|
||||
@@ -31,17 +32,20 @@ namespace Robust.Client.Console
|
||||
|
||||
Title = "Watch Window";
|
||||
|
||||
var mainVBox = new VBoxContainer
|
||||
var mainVBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
MinSize = (500, 300),
|
||||
Children =
|
||||
{
|
||||
(_watchesVBox = new VBoxContainer
|
||||
(_watchesVBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
VerticalExpand = true
|
||||
}),
|
||||
new HBoxContainer
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
(_addWatchEdit = new HistoryLineEdit
|
||||
@@ -105,8 +109,9 @@ namespace Robust.Client.Console
|
||||
Button delButton;
|
||||
_runner = runner;
|
||||
|
||||
AddChild(new HBoxContainer
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
(_outputLabel = new Label
|
||||
@@ -166,8 +171,9 @@ namespace Robust.Client.Console
|
||||
public CompilationErrorControl(string message)
|
||||
{
|
||||
Button delButton;
|
||||
AddChild(new HBoxContainer
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
|
||||
@@ -158,16 +158,18 @@ namespace Robust.Client.Debugging
|
||||
_hoverStartScreen = mouseScreenPos.Position;
|
||||
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
var viewBounds = _eyeManager.GetWorldViewbounds();
|
||||
|
||||
if (viewport.IsEmpty()) return;
|
||||
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var sleepThreshold = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.TimeToSleep);
|
||||
var colorEdge = Color.Red.WithAlpha(0.33f);
|
||||
var drawnJoints = new HashSet<Joint>();
|
||||
|
||||
foreach (var physBody in EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(mapId, viewport))
|
||||
foreach (var physBody in EntitySystem.Get<SharedBroadphaseSystem>().GetCollidingEntities(mapId, viewBounds))
|
||||
{
|
||||
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
|
||||
|
||||
// all entities have a TransformComponent
|
||||
var transform = physBody.Owner.Transform;
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Dynamics.Contacts;
|
||||
@@ -40,6 +41,8 @@ namespace Robust.Client.Debugging
|
||||
* Used for debugging shapes, controllers, joints, contacts
|
||||
*/
|
||||
|
||||
[Dependency] private readonly IPhysicsManager _physicsManager = default!;
|
||||
|
||||
private const int MaxContactPoints = 2048;
|
||||
internal int PointCount;
|
||||
|
||||
@@ -79,8 +82,7 @@ namespace Robust.Client.Debugging
|
||||
CollisionManager.GetPointStates(out state1, out state2, oldManifold, manifold);
|
||||
|
||||
Span<Vector2> points = stackalloc Vector2[2];
|
||||
Vector2 normal;
|
||||
contact.GetWorldManifold(out normal, points);
|
||||
contact.GetWorldManifold(_physicsManager, out var normal, points);
|
||||
|
||||
for (int i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Runtime;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.Audio.Midi;
|
||||
using Robust.Client.Console;
|
||||
@@ -139,6 +140,7 @@ namespace Robust.Client
|
||||
|
||||
_authManager.LoadFromEnv();
|
||||
|
||||
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
||||
GC.Collect();
|
||||
|
||||
// Setup main loop
|
||||
@@ -255,6 +257,15 @@ namespace Robust.Client
|
||||
_configurationManager.OverrideConVars(_commandLineArgs.CVars);
|
||||
}
|
||||
|
||||
{
|
||||
// Handle GameControllerOptions implicit CVar overrides.
|
||||
_configurationManager.OverrideConVars(new []
|
||||
{
|
||||
(CVars.DisplayWindowIconSet.Name, options.WindowIconSet.ToString()),
|
||||
(CVars.DisplaySplashLogo.Name, options.SplashLogo.ToString())
|
||||
});
|
||||
}
|
||||
|
||||
ProfileOptSetup.Setup(_configurationManager);
|
||||
|
||||
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
@@ -472,6 +483,8 @@ namespace Robust.Client
|
||||
|
||||
internal void Cleanup()
|
||||
{
|
||||
_modLoader.Shutdown();
|
||||
|
||||
_networkManager.Shutdown("Client shutting down");
|
||||
_midiManager.Shutdown();
|
||||
IoCManager.Resolve<IEntityLookup>().Shutdown();
|
||||
|
||||
@@ -52,6 +52,16 @@ namespace Robust.Client
|
||||
/// </summary>
|
||||
public ResourcePath PrototypeDirectory { get; init; } = new(@"/Prototypes/");
|
||||
|
||||
/// <summary>
|
||||
/// Directory resource path containing window icons to load.
|
||||
/// </summary>
|
||||
public ResourcePath WindowIconSet { get; init; } = new("/Textures/Logo/icon");
|
||||
|
||||
/// <summary>
|
||||
/// Resource path for splash image to show when the game starts up.
|
||||
/// </summary>
|
||||
public ResourcePath SplashLogo { get; init; } = new("/Textures/Logo/logo.png");
|
||||
|
||||
/// <summary>
|
||||
/// Whether to disable mounting the "Resources/" folder on FULL_RELEASE.
|
||||
/// </summary>
|
||||
|
||||
@@ -25,10 +25,9 @@ namespace Robust.Client.GameObjects
|
||||
RegisterClass<InputComponent>();
|
||||
RegisterClass<SpriteComponent>();
|
||||
RegisterClass<ClientOccluderComponent>();
|
||||
RegisterClass<OccluderTreeComponent>();
|
||||
RegisterClass<EyeComponent>();
|
||||
RegisterClass<AppearanceComponent>();
|
||||
RegisterClass<AppearanceTestComponent>();
|
||||
RegisterClass<SnapGridComponent>();
|
||||
RegisterClass<AnimationPlayerComponent>();
|
||||
RegisterClass<TimerComponent>();
|
||||
|
||||
|
||||
@@ -84,6 +84,23 @@ namespace Robust.Client.GameObjects
|
||||
if (curState is not AppearanceComponentState actualState)
|
||||
return;
|
||||
|
||||
var stateDiff = data.Count != actualState.Data.Count;
|
||||
|
||||
if (!stateDiff)
|
||||
{
|
||||
foreach (var (key, value) in data)
|
||||
{
|
||||
if (!actualState.Data.TryGetValue(key, out var stateValue) ||
|
||||
!value.Equals(stateValue))
|
||||
{
|
||||
stateDiff = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!stateDiff) return;
|
||||
|
||||
data = actualState.Data;
|
||||
MarkDirty();
|
||||
}
|
||||
@@ -141,22 +158,4 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
sealed class AppearanceTestComponent : Component
|
||||
{
|
||||
public override string Name => "AppearanceTest";
|
||||
|
||||
float time;
|
||||
bool state;
|
||||
|
||||
public void OnUpdate(float frameTime)
|
||||
{
|
||||
time += frameTime;
|
||||
if (time > 1)
|
||||
{
|
||||
time -= 1;
|
||||
Owner.GetComponent<AppearanceComponent>().SetData("test", state = !state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -10,7 +8,7 @@ namespace Robust.Client.GameObjects
|
||||
/// <summary>
|
||||
/// Defines data fields used in the <see cref="InputSystem"/>.
|
||||
/// </summary>
|
||||
class InputComponent : Component
|
||||
public class InputComponent : Component
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Input";
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
@@ -17,41 +9,28 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IPointLightComponent))]
|
||||
[NetworkedComponent()]
|
||||
public class PointLightComponent : Component, IPointLightComponent, ISerializationHooks
|
||||
[ComponentReference(typeof(SharedPointLightComponent))]
|
||||
public class PointLightComponent : SharedPointLightComponent, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
public override string Name => "PointLight";
|
||||
|
||||
internal bool TreeUpdateQueued { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[Animatable]
|
||||
public Color Color
|
||||
public override Color Color
|
||||
{
|
||||
get => _color;
|
||||
set => _color = value;
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 Offset
|
||||
{
|
||||
get => _offset;
|
||||
set => _offset = value;
|
||||
set => base.Color = value;
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[Animatable]
|
||||
public bool Enabled
|
||||
public override bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
|
||||
_enabled = value;
|
||||
base.Enabled = value;
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PointLightUpdateEvent());
|
||||
}
|
||||
}
|
||||
@@ -92,7 +71,6 @@ namespace Robust.Client.GameObjects
|
||||
set => _rotation = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// The resource path to the mask texture the light will use.
|
||||
/// </summary>
|
||||
@@ -102,8 +80,9 @@ namespace Robust.Client.GameObjects
|
||||
get => _maskPath;
|
||||
set
|
||||
{
|
||||
if (_maskPath?.Equals(value) != false) return;
|
||||
_maskPath = value;
|
||||
UpdateMask();
|
||||
EntitySystem.Get<PointLightSystem>().UpdateMask(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,16 +120,8 @@ namespace Robust.Client.GameObjects
|
||||
set => _visibleNested = value;
|
||||
}
|
||||
|
||||
[DataField("radius")]
|
||||
private float _radius = 5f;
|
||||
[DataField("nestedvisible")]
|
||||
private bool _visibleNested = true;
|
||||
[DataField("color")]
|
||||
private Color _color = Color.White;
|
||||
[DataField("offset")]
|
||||
private Vector2 _offset = Vector2.Zero;
|
||||
[DataField("enabled")]
|
||||
private bool _enabled = true;
|
||||
[DataField("autoRot")]
|
||||
private bool _maskAutoRotate;
|
||||
private Angle _rotation;
|
||||
@@ -159,74 +130,27 @@ namespace Robust.Client.GameObjects
|
||||
[DataField("softness")]
|
||||
private float _softness = 1f;
|
||||
[DataField("mask")]
|
||||
private string? _maskPath;
|
||||
internal string? _maskPath;
|
||||
|
||||
/// <summary>
|
||||
/// Radius, in meters.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[Animatable]
|
||||
public float Radius
|
||||
public override float Radius
|
||||
{
|
||||
get => _radius;
|
||||
set
|
||||
{
|
||||
if (MathHelper.CloseTo(value, _radius)) return;
|
||||
|
||||
_radius = MathF.Max(value, 0.01f); // setting radius to 0 causes exceptions, so just use a value close enough to zero that it's unnoticeable.
|
||||
base.Radius = value;
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PointLightRadiusChangedEvent(this));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMask()
|
||||
{
|
||||
if (_maskPath is not null)
|
||||
Mask = _resourceCache.GetResource<TextureResource>(_maskPath);
|
||||
else
|
||||
Mask = null;
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
internal RenderingTreeComponent? RenderTree { get; set; }
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
if (_maskPath != null)
|
||||
{
|
||||
Mask = IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(_maskPath);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
UpdateMask();
|
||||
}
|
||||
|
||||
protected override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
|
||||
var map = Owner.Transform.MapID;
|
||||
if (map != MapId.Nullspace)
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
|
||||
new RenderTreeRemoveLightEvent(this, map));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
if (curState == null)
|
||||
return;
|
||||
|
||||
var newState = (PointLightComponentState) curState;
|
||||
Enabled = newState.Enabled;
|
||||
Radius = newState.Radius;
|
||||
Offset = newState.Offset;
|
||||
Color = newState.Color;
|
||||
}
|
||||
}
|
||||
|
||||
public class PointLightRadiusChangedEvent : EntityEventArgs
|
||||
|
||||
@@ -228,6 +228,6 @@ namespace Robust.Client.GameObjects
|
||||
/// <summary>
|
||||
/// Calculate sprite bounding box in world-space coordinates.
|
||||
/// </summary>
|
||||
Box2 CalculateBoundingBox();
|
||||
Box2 CalculateBoundingBox(Vector2 worldPos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Clyde;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public sealed class ShowSpriteBBCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showspritebb";
|
||||
public string Description => "Toggle whether sprite bounds are shown";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<SpriteBoundsSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SpriteBoundsSystem : EntitySystem
|
||||
{
|
||||
private SpriteBoundsOverlay? _overlay;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
|
||||
_enabled = value;
|
||||
|
||||
if (_enabled)
|
||||
{
|
||||
DebugTools.AssertNull(_overlay);
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
_overlay = new SpriteBoundsOverlay(EntitySystem.Get<RenderingTreeSystem>(), IoCManager.Resolve<IEyeManager>());
|
||||
overlayManager.AddOverlay(_overlay);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_overlay == null) return;
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
overlayManager.RemoveOverlay(_overlay);
|
||||
_overlay = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enabled;
|
||||
}
|
||||
|
||||
public class SpriteBoundsOverlay : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
private readonly IEyeManager _eyeManager = default!;
|
||||
private RenderingTreeSystem _renderTree;
|
||||
|
||||
public SpriteBoundsOverlay(RenderingTreeSystem renderTree, IEyeManager eyeManager)
|
||||
{
|
||||
_renderTree = renderTree;
|
||||
_eyeManager = eyeManager;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var handle = args.WorldHandle;
|
||||
var currentMap = _eyeManager.CurrentMap;
|
||||
var viewport = _eyeManager.GetWorldViewbounds();
|
||||
|
||||
foreach (var comp in _renderTree.GetRenderTrees(currentMap, viewport))
|
||||
{
|
||||
var localAABB = comp.Owner.Transform.InvWorldMatrix.TransformBox(viewport);
|
||||
|
||||
foreach (var sprite in comp.SpriteTree.QueryAabb(localAABB))
|
||||
{
|
||||
var worldPos = sprite.Owner.Transform.WorldPosition;
|
||||
var bounds = sprite.CalculateBoundingBox(worldPos);
|
||||
handle.DrawRect(bounds, Color.Red.WithAlpha(0.2f));
|
||||
handle.DrawRect(bounds.Scale(0.2f).Translated(-new Vector2(0f, bounds.Extents.Y)), Color.Blue.WithAlpha(0.5f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -365,7 +365,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath);
|
||||
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'.", rsiPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1212,13 +1212,12 @@ namespace Robust.Client.GameObjects
|
||||
public IEnumerable<ISpriteLayer> AllLayers => Layers;
|
||||
|
||||
// Lobby SpriteView rendering path
|
||||
internal void Render(DrawingHandleWorld drawingHandle, Angle worldRotation, Direction? overrideDirection = null)
|
||||
internal void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null)
|
||||
{
|
||||
RenderInternal(drawingHandle, worldRotation, Vector2.Zero, overrideDirection);
|
||||
RenderInternal(drawingHandle, eyeRotation, worldRotation, Vector2.Zero, overrideDirection);
|
||||
}
|
||||
|
||||
[DataField("noRot")]
|
||||
private bool _screenLock = true;
|
||||
[DataField("noRot")] private bool _screenLock = false;
|
||||
|
||||
[DataField("overrideDir")]
|
||||
private Direction _overrideDirection = Direction.East;
|
||||
@@ -1239,7 +1238,7 @@ namespace Robust.Client.GameObjects
|
||||
public bool EnableDirectionOverride { get => _enableOverrideDirection; set => _enableOverrideDirection = value; }
|
||||
|
||||
// Sprite rendering path
|
||||
internal void Render(DrawingHandleWorld drawingHandle, in Angle worldRotation, in Vector2 worldPosition)
|
||||
internal void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, in Angle worldRotation, in Vector2 worldPosition)
|
||||
{
|
||||
Direction? overrideDir = null;
|
||||
if (_enableOverrideDirection)
|
||||
@@ -1247,16 +1246,18 @@ namespace Robust.Client.GameObjects
|
||||
overrideDir = _overrideDirection;
|
||||
}
|
||||
|
||||
RenderInternal(drawingHandle, worldRotation, worldPosition, overrideDir);
|
||||
RenderInternal(drawingHandle, eyeRotation, worldRotation, worldPosition, overrideDir);
|
||||
}
|
||||
|
||||
private void CalcModelMatrix(int numDirs, Angle worldRotation, Vector2 worldPosition, out Matrix3 modelMatrix)
|
||||
private void CalcModelMatrix(int numDirs, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, out Matrix3 modelMatrix)
|
||||
{
|
||||
Angle angle;
|
||||
|
||||
if (_screenLock)
|
||||
{
|
||||
angle = Angle.Zero;
|
||||
// Negate the eye rotation in the model matrix, so that later when the view matrix is applied the
|
||||
// sprite will be locked upright to the screen
|
||||
angle = new Angle(-eyeRotation.Theta);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1267,7 +1268,7 @@ namespace Robust.Client.GameObjects
|
||||
modelMatrix = Matrix3.CreateTransform(in worldPosition, in sWorldRotation);
|
||||
}
|
||||
|
||||
private void RenderInternal(DrawingHandleWorld drawingHandle, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection)
|
||||
private void RenderInternal(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection)
|
||||
{
|
||||
var localMatrix = GetLocalMatrix();
|
||||
|
||||
@@ -1280,7 +1281,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
var numDirs = GetLayerDirectionCount(layer);
|
||||
|
||||
CalcModelMatrix(numDirs, worldRotation, worldPosition, out var modelMatrix);
|
||||
CalcModelMatrix(numDirs, eyeRotation, worldRotation, worldPosition, out var modelMatrix);
|
||||
Matrix3.Multiply(ref localMatrix, ref modelMatrix, out var transformMatrix);
|
||||
drawingHandle.SetTransform(in transformMatrix);
|
||||
|
||||
@@ -1510,6 +1511,11 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private void QueueUpdateIsInert()
|
||||
{
|
||||
// Look this was an easy way to get bounds checks for layer updates.
|
||||
// If you really want it optimal you'll need to comb through all 2k lines of spritecomponent.
|
||||
if (Owner?.EntityManager?.EventBus != null)
|
||||
UpdateBounds();
|
||||
|
||||
if (_inertUpdateQueued)
|
||||
return;
|
||||
|
||||
@@ -1624,11 +1630,11 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box2 CalculateBoundingBox()
|
||||
public Box2 CalculateBoundingBox(Vector2 worldPos)
|
||||
{
|
||||
// fast check for empty sprites
|
||||
if (Layers.Count == 0)
|
||||
return new Box2();
|
||||
return new Box2(worldPos, worldPos);
|
||||
|
||||
// we need to calculate bounding box taking into account all nested layers
|
||||
// because layers can have offsets, scale or rotation we need to calculate a new BB
|
||||
@@ -1651,11 +1657,15 @@ namespace Robust.Client.GameObjects
|
||||
new Box2Rotated(spriteBox, Rotation).CalcBoundingBox() : spriteBox;
|
||||
|
||||
// move it all to world transform system (with sprite offset)
|
||||
var worldPosition = Owner.Transform.WorldPosition;
|
||||
var worldBB = spriteBB.Translated(Offset + worldPosition);
|
||||
var worldBB = spriteBB.Translated(Offset + worldPos);
|
||||
return worldBB;
|
||||
}
|
||||
|
||||
internal void UpdateBounds()
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to "offset" a cardinal direction.
|
||||
/// </summary>
|
||||
@@ -1711,7 +1721,19 @@ namespace Robust.Client.GameObjects
|
||||
public bool AutoAnimated = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 Offset { get; set; }
|
||||
public Vector2 Offset
|
||||
{
|
||||
get => _offset;
|
||||
set
|
||||
{
|
||||
if (_offset.EqualsApprox(value)) return;
|
||||
|
||||
_offset = value;
|
||||
_parent.UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 _offset;
|
||||
|
||||
[ViewVariables]
|
||||
public DirectionOffset DirOffset { get; set; }
|
||||
@@ -1983,7 +2005,6 @@ namespace Robust.Client.GameObjects
|
||||
// TODO: scale & rotation for layers is currently unimplemented.
|
||||
return Box2.CenteredAround(Offset, PixelSize / EyeManager.PixelsPerMeter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void IAnimationProperties.SetAnimatableProperty(string name, object value)
|
||||
|
||||
@@ -15,29 +15,49 @@ namespace Robust.Client.GameObjects
|
||||
private static Box2 SpriteAabbFunc(in SpriteComponent value)
|
||||
{
|
||||
var worldPos = value.Owner.Transform.WorldPosition;
|
||||
return new Box2(worldPos, worldPos);
|
||||
var worldRot = value.Owner.Transform.WorldRotation;
|
||||
var bounds = new Box2Rotated(value.CalculateBoundingBox(worldPos), worldRot, worldPos);
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
|
||||
var localAABB = tree?.Owner.Transform.InvWorldMatrix.TransformBox(bounds) ?? bounds.CalcBoundingBox();
|
||||
|
||||
return localAABB;
|
||||
}
|
||||
|
||||
private static Box2 LightAabbFunc(in PointLightComponent value)
|
||||
{
|
||||
var worldPos = value.Owner.Transform.WorldPosition;
|
||||
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
var boxSize = value.Radius * 2;
|
||||
return Box2.CenteredAround(worldPos, (boxSize, boxSize));
|
||||
|
||||
var localPos = tree?.Owner.Transform.InvWorldMatrix.Transform(worldPos) ?? worldPos;
|
||||
|
||||
return Box2.CenteredAround(localPos, (boxSize, boxSize));
|
||||
}
|
||||
|
||||
internal static Box2 SpriteAabbFunc(SpriteComponent value, Vector2? worldPos = null)
|
||||
internal static Box2 SpriteAabbFunc(SpriteComponent value, Vector2? worldPos = null, Angle? worldRot = null)
|
||||
{
|
||||
worldPos ??= value.Owner.Transform.WorldPosition;
|
||||
worldRot ??= value.Owner.Transform.WorldRotation;
|
||||
var bounds = new Box2Rotated(value.CalculateBoundingBox(worldPos.Value), worldRot.Value, worldPos.Value);
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
|
||||
return new Box2(worldPos.Value, worldPos.Value);
|
||||
var localAABB = tree?.Owner.Transform.InvWorldMatrix.TransformBox(bounds) ?? bounds.CalcBoundingBox();
|
||||
|
||||
return localAABB;
|
||||
}
|
||||
|
||||
internal static Box2 LightAabbFunc(PointLightComponent value, Vector2? worldPos = null)
|
||||
{
|
||||
// Lights are circles so don't need entity's rotation
|
||||
|
||||
worldPos ??= value.Owner.Transform.WorldPosition;
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
var boxSize = value.Radius * 2;
|
||||
return Box2.CenteredAround(worldPos.Value, (boxSize, boxSize));
|
||||
|
||||
var localPos = tree?.Owner.Transform.InvWorldMatrix.Transform(worldPos.Value) ?? worldPos.Value;
|
||||
|
||||
return Box2.CenteredAround(localPos, (boxSize, boxSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
class AppearanceTestSystem : EntitySystem
|
||||
{
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var appearanceTestComponent in EntityManager.ComponentManager.EntityQuery<AppearanceTestComponent>(true))
|
||||
{
|
||||
appearanceTestComponent.OnUpdate(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -8,6 +10,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Player;
|
||||
@@ -23,8 +26,7 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IClydeAudio _clyde = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
|
||||
[Dependency] private readonly SharedBroadphaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
private readonly List<PlayingStream> _playingClydeStreams = new();
|
||||
|
||||
@@ -38,7 +40,6 @@ namespace Robust.Client.GameObjects
|
||||
SubscribeNetworkEvent<StopAudioMessageClient>(StopAudioMessageHandler);
|
||||
|
||||
SubscribeLocalEvent<SoundSystem.QueryAudioSystem>((ev => ev.Audio = this));
|
||||
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
|
||||
}
|
||||
|
||||
private void StopAudioMessageHandler(StopAudioMessageClient ev)
|
||||
@@ -98,6 +99,8 @@ namespace Robust.Client.GameObjects
|
||||
// Update positions of streams every frame.
|
||||
try
|
||||
{
|
||||
var ourPos = _eyeManager.CurrentEye.Position.Position;
|
||||
|
||||
foreach (var stream in _playingClydeStreams)
|
||||
{
|
||||
if (!stream.Source.IsPlaying)
|
||||
@@ -141,7 +144,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
else
|
||||
{
|
||||
var sourceRelative = _eyeManager.CurrentEye.Position.Position - pos.Position;
|
||||
var sourceRelative = ourPos - pos.Position;
|
||||
var occlusion = 0f;
|
||||
if (sourceRelative.Length > 0)
|
||||
{
|
||||
@@ -155,14 +158,54 @@ namespace Robust.Client.GameObjects
|
||||
stream.TrackingEntity);
|
||||
}
|
||||
|
||||
stream.Source.SetVolume(stream.Volume);
|
||||
var distance = MathF.Max(stream.ReferenceDistance, MathF.Min(sourceRelative.Length, stream.MaxDistance));
|
||||
float gain;
|
||||
|
||||
// Technically these are formulas for gain not decibels but EHHHHHHHH.
|
||||
switch (stream.Attenuation)
|
||||
{
|
||||
case Attenuation.Default:
|
||||
gain = 1f;
|
||||
break;
|
||||
// You thought I'd implement clamping per source? Hell no that's just for the overall OpenAL setting
|
||||
// I didn't even wanna implement this much for linear but figured it'd be cleaner.
|
||||
case Attenuation.InverseDistanceClamped:
|
||||
case Attenuation.InverseDistance:
|
||||
gain = stream.ReferenceDistance /
|
||||
(stream.ReferenceDistance + stream.RolloffFactor * (distance - stream.ReferenceDistance));
|
||||
|
||||
break;
|
||||
case Attenuation.LinearDistanceClamped:
|
||||
case Attenuation.LinearDistance:
|
||||
gain = 1f - stream.RolloffFactor * (distance - stream.ReferenceDistance) /
|
||||
(stream.MaxDistance - stream.ReferenceDistance);
|
||||
|
||||
break;
|
||||
case Attenuation.ExponentDistanceClamped:
|
||||
case Attenuation.ExponentDistance:
|
||||
gain = MathF.Pow((distance / stream.ReferenceDistance),
|
||||
(-stream.RolloffFactor));
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"No implemented attenuation for {stream.Attenuation.ToString()}");
|
||||
}
|
||||
|
||||
var volume = MathF.Pow(10, stream.Volume / 10);
|
||||
var actualGain = MathF.Max(0f, volume * gain);
|
||||
|
||||
stream.Source.SetVolumeDirect(actualGain);
|
||||
stream.Source.SetOcclusion(occlusion);
|
||||
}
|
||||
|
||||
if (!stream.Source.SetPosition(pos.Position))
|
||||
SetAudioPos(stream, stream.Attenuation != Attenuation.NoAttenuation ? pos.Position : ourPos);
|
||||
|
||||
void SetAudioPos(PlayingStream stream, Vector2 pos)
|
||||
{
|
||||
Logger.Warning("Interrupting positional audio, can't set position.");
|
||||
stream.Source.StopPlaying();
|
||||
if (!stream.Source.SetPosition(pos))
|
||||
{
|
||||
Logger.Warning("Interrupting positional audio, can't set position.");
|
||||
stream.Source.StopPlaying();
|
||||
}
|
||||
}
|
||||
|
||||
if (stream.TrackingEntity != null)
|
||||
@@ -219,6 +262,10 @@ namespace Robust.Client.GameObjects
|
||||
var playing = new PlayingStream
|
||||
{
|
||||
Source = source,
|
||||
Attenuation = audioParams?.Attenuation ?? Attenuation.Default,
|
||||
MaxDistance = audioParams?.MaxDistance ?? float.MaxValue,
|
||||
ReferenceDistance = audioParams?.ReferenceDistance ?? 1f,
|
||||
RolloffFactor = audioParams?.RolloffFactor ?? 1f,
|
||||
Volume = audioParams?.Volume ?? 0
|
||||
};
|
||||
_playingClydeStreams.Add(playing);
|
||||
@@ -265,6 +312,10 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
Source = source,
|
||||
TrackingEntity = entity,
|
||||
Attenuation = audioParams?.Attenuation ?? Attenuation.Default,
|
||||
MaxDistance = audioParams?.MaxDistance ?? float.MaxValue,
|
||||
ReferenceDistance = audioParams?.ReferenceDistance ?? 1f,
|
||||
RolloffFactor = audioParams?.RolloffFactor ?? 1f,
|
||||
Volume = audioParams?.Volume ?? 0
|
||||
};
|
||||
_playingClydeStreams.Add(playing);
|
||||
@@ -301,7 +352,7 @@ namespace Robust.Client.GameObjects
|
||||
if (!source.SetPosition(coordinates.ToMapPos(EntityManager)))
|
||||
{
|
||||
source.Dispose();
|
||||
Logger.Warning("Can't play positional audio \"{stream.Name}\", can't set position.");
|
||||
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -312,6 +363,10 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
Source = source,
|
||||
TrackingCoordinates = coordinates,
|
||||
Attenuation = audioParams?.Attenuation ?? Attenuation.Default,
|
||||
MaxDistance = audioParams?.MaxDistance ?? float.MaxValue,
|
||||
ReferenceDistance = audioParams?.ReferenceDistance ?? 1f,
|
||||
RolloffFactor = audioParams?.RolloffFactor ?? 1f,
|
||||
Volume = audioParams?.Volume ?? 0
|
||||
};
|
||||
_playingClydeStreams.Add(playing);
|
||||
@@ -327,6 +382,9 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
source.SetPitch(audioParams.Value.PitchScale);
|
||||
source.SetVolume(audioParams.Value.Volume);
|
||||
source.SetRolloffFactor(audioParams.Value.RolloffFactor);
|
||||
source.SetMaxDistance(audioParams.Value.MaxDistance);
|
||||
source.SetReferenceDistance(audioParams.Value.ReferenceDistance);
|
||||
source.SetPlaybackPosition(audioParams.Value.PlayOffsetSeconds);
|
||||
source.IsLooping = audioParams.Value.Loop;
|
||||
}
|
||||
@@ -340,6 +398,27 @@ namespace Robust.Client.GameObjects
|
||||
public bool Done;
|
||||
public float Volume;
|
||||
|
||||
public float MaxDistance;
|
||||
public float ReferenceDistance;
|
||||
public float RolloffFactor;
|
||||
|
||||
public Attenuation Attenuation
|
||||
{
|
||||
get => _attenuation;
|
||||
set
|
||||
{
|
||||
if (value == _attenuation) return;
|
||||
_attenuation = value;
|
||||
if (_attenuation != Attenuation.Default)
|
||||
{
|
||||
// Need to disable default attenuation when using a custom one
|
||||
// Damn Sloth wanting linear ambience sounds so they smoothly cut-off and are short-range
|
||||
Source.SetRolloffFactor(0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
private Attenuation _attenuation = Attenuation.Default;
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
Source.StopPlaying();
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public class ClientContainerSystem : ContainerSystem
|
||||
{
|
||||
private readonly HashSet<IEntity> _updateQueue = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<UpdateContainerOcclusionMessage>(UpdateContainerOcclusion);
|
||||
|
||||
UpdatesBefore.Add(typeof(SpriteSystem));
|
||||
}
|
||||
|
||||
private void UpdateContainerOcclusion(UpdateContainerOcclusionMessage ev)
|
||||
{
|
||||
_updateQueue.Add(ev.Entity);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
foreach (var toUpdate in _updateQueue)
|
||||
{
|
||||
if (toUpdate.Deleted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UpdateEntityRecursively(toUpdate);
|
||||
}
|
||||
|
||||
_updateQueue.Clear();
|
||||
}
|
||||
|
||||
private static void UpdateEntityRecursively(IEntity entity)
|
||||
{
|
||||
// TODO: Since we are recursing down,
|
||||
// we could cache ShowContents data here to speed it up for children.
|
||||
// Am lazy though.
|
||||
UpdateEntity(entity);
|
||||
|
||||
foreach (var child in entity.Transform.Children)
|
||||
{
|
||||
UpdateEntityRecursively(child.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateEntity(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out SpriteComponent? sprite))
|
||||
{
|
||||
sprite.ContainerOccluded = false;
|
||||
|
||||
// We have to recursively scan for containers upwards in case of nested containers.
|
||||
var tempParent = entity;
|
||||
while (tempParent.TryGetContainer(out var container))
|
||||
{
|
||||
if (!container.ShowContents)
|
||||
{
|
||||
sprite.ContainerOccluded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tempParent = container.Owner;
|
||||
}
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out PointLightComponent? light))
|
||||
{
|
||||
light.ContainerOccluded = false;
|
||||
|
||||
// We have to recursively scan for containers upwards in case of nested containers.
|
||||
var tempParent = entity;
|
||||
while (tempParent.TryGetContainer(out var container))
|
||||
{
|
||||
if (container.OccludesLight)
|
||||
{
|
||||
light.ContainerOccluded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tempParent = container.Owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleAnchorChanged(EntityUid uid, ClientOccluderComponent component, AnchorStateChangedEvent args)
|
||||
private static void HandleAnchorChanged(EntityUid uid, ClientOccluderComponent component, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
component.AnchorStateChanged();
|
||||
}
|
||||
|
||||
237
Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs
Normal file
237
Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using static Robust.Shared.Containers.ContainerManagerComponent;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public class ContainerSystem : SharedContainerSystem
|
||||
{
|
||||
[Dependency] private readonly IRobustSerializer _serializer = default!;
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
|
||||
|
||||
private readonly HashSet<IEntity> _updateQueue = new();
|
||||
|
||||
public readonly Dictionary<EntityUid, IContainer> ExpectedEntities = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<UpdateContainerOcclusionMessage>(UpdateContainerOcclusion);
|
||||
SubscribeLocalEvent<EntityInitializedMessage>(HandleEntityInitialized);
|
||||
SubscribeLocalEvent<ContainerManagerComponent, ComponentHandleState>(HandleComponentState);
|
||||
|
||||
UpdatesBefore.Add(typeof(SpriteSystem));
|
||||
}
|
||||
|
||||
private void UpdateContainerOcclusion(UpdateContainerOcclusionMessage ev)
|
||||
{
|
||||
_updateQueue.Add(ev.Entity);
|
||||
}
|
||||
|
||||
private void HandleEntityInitialized(EntityInitializedMessage ev)
|
||||
{
|
||||
if (!ExpectedEntities.TryGetValue(ev.Entity.Uid, out var container))
|
||||
return;
|
||||
|
||||
RemoveExpectedEntity(ev.Entity.Uid);
|
||||
|
||||
if (container.Deleted)
|
||||
return;
|
||||
|
||||
container.Insert(ev.Entity);
|
||||
}
|
||||
|
||||
private void HandleComponentState(EntityUid uid, ContainerManagerComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not ContainerManagerComponentState cast)
|
||||
return;
|
||||
|
||||
// Delete now-gone containers.
|
||||
List<string>? toDelete = null;
|
||||
foreach (var (id, container) in component.Containers)
|
||||
{
|
||||
if (!cast.ContainerSet.Any(data => data.Id == id))
|
||||
{
|
||||
container.Shutdown();
|
||||
toDelete ??= new List<string>();
|
||||
toDelete.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (toDelete != null)
|
||||
{
|
||||
foreach (var dead in toDelete)
|
||||
{
|
||||
component.Containers.Remove(dead);
|
||||
}
|
||||
}
|
||||
|
||||
// Add new containers and update existing contents.
|
||||
|
||||
foreach (var (containerType, id, showEnts, occludesLight, entityUids) in cast.ContainerSet)
|
||||
{
|
||||
if (!component.Containers.TryGetValue(id, out var container))
|
||||
{
|
||||
container = ContainerFactory(component, containerType, id);
|
||||
component.Containers.Add(id, container);
|
||||
}
|
||||
|
||||
// sync show flag
|
||||
container.ShowContents = showEnts;
|
||||
container.OccludesLight = occludesLight;
|
||||
|
||||
// Remove gone entities.
|
||||
List<IEntity>? toRemove = null;
|
||||
foreach (var entity in container.ContainedEntities)
|
||||
{
|
||||
if (!entityUids.Contains(entity.Uid))
|
||||
{
|
||||
toRemove ??= new List<IEntity>();
|
||||
toRemove.Add(entity);
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove != null)
|
||||
{
|
||||
foreach (var goner in toRemove)
|
||||
container.Remove(goner);
|
||||
}
|
||||
|
||||
// Remove entities that were expected, but have been removed from the container.
|
||||
List<EntityUid>? removedExpected = null;
|
||||
foreach (var entityUid in container.ExpectedEntities)
|
||||
{
|
||||
if (!entityUids.Contains(entityUid))
|
||||
{
|
||||
removedExpected ??= new List<EntityUid>();
|
||||
removedExpected.Add(entityUid);
|
||||
}
|
||||
}
|
||||
|
||||
if (removedExpected != null)
|
||||
{
|
||||
foreach (var entityUid in removedExpected)
|
||||
RemoveExpectedEntity(entityUid);
|
||||
}
|
||||
|
||||
// Add new entities.
|
||||
foreach (var entityUid in entityUids)
|
||||
{
|
||||
if (!EntityManager.TryGetEntity(entityUid, out var entity))
|
||||
{
|
||||
AddExpectedEntity(entityUid, container);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!container.ContainedEntities.Contains(entity))
|
||||
container.Insert(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IContainer ContainerFactory(ContainerManagerComponent component, string containerType, string id)
|
||||
{
|
||||
var type = _serializer.FindSerializedType(typeof(IContainer), containerType);
|
||||
if (type is null) throw new ArgumentException($"Container of type {containerType} for id {id} cannot be found.");
|
||||
|
||||
var newContainer = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type);
|
||||
newContainer.ID = id;
|
||||
newContainer.Manager = component;
|
||||
return newContainer;
|
||||
}
|
||||
|
||||
public void AddExpectedEntity(EntityUid uid, IContainer container)
|
||||
{
|
||||
if (ExpectedEntities.ContainsKey(uid))
|
||||
return;
|
||||
|
||||
ExpectedEntities.Add(uid, container);
|
||||
container.ExpectedEntities.Add(uid);
|
||||
}
|
||||
|
||||
public void RemoveExpectedEntity(EntityUid uid)
|
||||
{
|
||||
if (!ExpectedEntities.TryGetValue(uid, out var container))
|
||||
return;
|
||||
|
||||
ExpectedEntities.Remove(uid);
|
||||
container.ExpectedEntities.Remove(uid);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
foreach (var toUpdate in _updateQueue)
|
||||
{
|
||||
if (toUpdate.Deleted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UpdateEntityRecursively(toUpdate);
|
||||
}
|
||||
|
||||
_updateQueue.Clear();
|
||||
}
|
||||
|
||||
private static void UpdateEntityRecursively(IEntity entity)
|
||||
{
|
||||
// TODO: Since we are recursing down,
|
||||
// we could cache ShowContents data here to speed it up for children.
|
||||
// Am lazy though.
|
||||
UpdateEntity(entity);
|
||||
|
||||
foreach (var child in entity.Transform.Children)
|
||||
{
|
||||
UpdateEntityRecursively(child.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateEntity(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out SpriteComponent? sprite))
|
||||
{
|
||||
sprite.ContainerOccluded = false;
|
||||
|
||||
// We have to recursively scan for containers upwards in case of nested containers.
|
||||
var tempParent = entity;
|
||||
while (tempParent.TryGetContainer(out var container))
|
||||
{
|
||||
if (!container.ShowContents)
|
||||
{
|
||||
sprite.ContainerOccluded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tempParent = container.Owner;
|
||||
}
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out PointLightComponent? light))
|
||||
{
|
||||
light.ContainerOccluded = false;
|
||||
|
||||
// We have to recursively scan for containers upwards in case of nested containers.
|
||||
var tempParent = entity;
|
||||
while (tempParent.TryGetContainer(out var container))
|
||||
{
|
||||
if (container.OccludesLight)
|
||||
{
|
||||
light.ContainerOccluded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tempParent = container.Owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,14 +65,15 @@ namespace Robust.Client.GameObjects
|
||||
var map = _eyeManager.CurrentMap;
|
||||
if (map == MapId.Nullspace) return;
|
||||
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
var viewport = _eyeManager.GetWorldViewbounds();
|
||||
var worldBounds = viewport.CalcBoundingBox();
|
||||
|
||||
foreach (var tree in _tree.GetLightTrees(map, viewport))
|
||||
foreach (var tree in _tree.GetRenderTrees(map, viewport))
|
||||
{
|
||||
foreach (var light in tree)
|
||||
foreach (var light in tree.LightTree)
|
||||
{
|
||||
var aabb = _lookup.GetWorldAabbFromEntity(light.Owner);
|
||||
if (!aabb.Intersects(viewport)) continue;
|
||||
if (!aabb.Intersects(worldBounds)) continue;
|
||||
|
||||
args.WorldHandle.DrawRect(aabb, Color.Green.WithAlpha(0.1f));
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Physics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
@@ -20,12 +21,11 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
// How fast the camera rotates in radians
|
||||
private const float CameraRotateSpeed = MathF.PI;
|
||||
private const float CameraSnapTolerance = 0.01f;
|
||||
|
||||
#pragma warning disable 649, CS8618
|
||||
// ReSharper disable once NotNullMemberIsNotInitialized
|
||||
[Dependency] private readonly IEyeManager _eyeManager;
|
||||
#pragma warning restore 649, CS8618
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
private bool _isLerping = false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
@@ -55,6 +55,7 @@ namespace Robust.Client.GameObjects
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
var currentEye = _eyeManager.CurrentEye;
|
||||
/*
|
||||
var inputSystem = EntitySystemManager.GetEntitySystem<InputSystem>();
|
||||
|
||||
var direction = 0;
|
||||
@@ -74,17 +75,16 @@ namespace Robust.Client.GameObjects
|
||||
currentEye.Rotation += CameraRotateSpeed * frameTime * direction;
|
||||
currentEye.Rotation = currentEye.Rotation.Reduced();
|
||||
}
|
||||
else
|
||||
{
|
||||
// snap to cardinal directions
|
||||
var closestDir = currentEye.Rotation.GetCardinalDir().ToVec();
|
||||
var currentDir = currentEye.Rotation.ToVec();
|
||||
*/
|
||||
|
||||
var dot = Vector2.Dot(closestDir, currentDir);
|
||||
if (MathHelper.CloseTo(dot, 1, CameraSnapTolerance))
|
||||
{
|
||||
currentEye.Rotation = closestDir.ToAngle();
|
||||
}
|
||||
var parent = _playerManager.LocalPlayer?.ControlledEntity?.Transform.Parent;
|
||||
|
||||
if (parent != null && !_isLerping)
|
||||
{
|
||||
// TODO: Detect parent change and start lerping
|
||||
|
||||
var parentRotation = parent.WorldRotation;
|
||||
currentEye.Rotation = -parentRotation;
|
||||
}
|
||||
|
||||
foreach (var eyeComponent in EntityManager.ComponentManager.EntityQuery<EyeComponent>(true))
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public class GridChunkBoundsDebugSystem : EntitySystem
|
||||
{
|
||||
private GridChunkBoundsOverlay? _overlay;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
|
||||
_enabled = value;
|
||||
|
||||
if (_enabled)
|
||||
{
|
||||
DebugTools.Assert(_overlay == null);
|
||||
_overlay = new GridChunkBoundsOverlay(
|
||||
IoCManager.Resolve<IEntityManager>(),
|
||||
IoCManager.Resolve<IEyeManager>(),
|
||||
IoCManager.Resolve<IMapManager>());
|
||||
|
||||
IoCManager.Resolve<IOverlayManager>().AddOverlay(_overlay);
|
||||
}
|
||||
else
|
||||
{
|
||||
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(_overlay!);
|
||||
_overlay = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enabled;
|
||||
}
|
||||
|
||||
internal sealed class GridChunkBoundsOverlay : Overlay
|
||||
{
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public GridChunkBoundsOverlay(IEntityManager entManager, IEyeManager eyeManager, IMapManager mapManager)
|
||||
{
|
||||
_entityManager = entManager;
|
||||
_eyeManager = eyeManager;
|
||||
_mapManager = mapManager;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var currentMap = _eyeManager.CurrentMap;
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(currentMap, viewport))
|
||||
{
|
||||
var mapGrid = (IMapGridInternal) grid;
|
||||
var gridEnt = _entityManager.GetEntity(grid.GridEntityId);
|
||||
|
||||
var worldPos = gridEnt.Transform.WorldPosition;
|
||||
var worldRot = gridEnt.Transform.WorldRotation;
|
||||
|
||||
foreach (var chunk in mapGrid.GetMapChunks(viewport))
|
||||
{
|
||||
var chunkBounds = chunk.CalcWorldBounds(worldPos, worldRot);
|
||||
var aabb = chunkBounds.CalcBoundingBox();
|
||||
|
||||
args.WorldHandle.DrawRect(chunkBounds, Color.Green.WithAlpha(0.2f), true);
|
||||
args.WorldHandle.DrawRect(aabb, Color.Red, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
internal sealed class GridFixtureSystem : SharedGridFixtureSystem
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
9
Robust.Client/GameObjects/EntitySystems/MapSystem.cs
Normal file
9
Robust.Client/GameObjects/EntitySystems/MapSystem.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
internal sealed class MapSystem : SharedMapSystem
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
44
Robust.Client/GameObjects/EntitySystems/PointLightSystem.cs
Normal file
44
Robust.Client/GameObjects/EntitySystems/PointLightSystem.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public sealed class PointLightSystem : SharedPointLightSystem
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PointLightComponent, ComponentInit>(HandleInit);
|
||||
SubscribeLocalEvent<PointLightComponent, ComponentRemove>(HandleRemove);
|
||||
}
|
||||
|
||||
private void HandleInit(EntityUid uid, PointLightComponent component, ComponentInit args)
|
||||
{
|
||||
UpdateMask(component);
|
||||
}
|
||||
|
||||
private void HandleRemove(EntityUid uid, PointLightComponent component, ComponentRemove args)
|
||||
{
|
||||
var ent = EntityManager.GetEntity(uid);
|
||||
var map = ent.Transform.MapID;
|
||||
// TODO: Just make this update the tree directly and not allocate
|
||||
if (map != MapId.Nullspace)
|
||||
{
|
||||
EntityManager.EventBus.RaiseEvent(EventSource.Local,
|
||||
new RenderTreeRemoveLightEvent(component, map));
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateMask(PointLightComponent component)
|
||||
{
|
||||
if (component._maskPath is not null)
|
||||
component.Mask = _resourceCache.GetResource<TextureResource>(component._maskPath);
|
||||
else
|
||||
component.Mask = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,42 +37,28 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
public float MaxLightRadius { get; private set; }
|
||||
|
||||
internal IEnumerable<RenderingTreeComponent> GetRenderTrees(MapId mapId, Box2Rotated worldBounds)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) yield break;
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
|
||||
{
|
||||
yield return EntityManager.GetEntity(grid.GridEntityId).GetComponent<RenderingTreeComponent>();
|
||||
}
|
||||
|
||||
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
|
||||
}
|
||||
|
||||
internal IEnumerable<RenderingTreeComponent> GetRenderTrees(MapId mapId, Box2 worldAABB)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) yield break;
|
||||
|
||||
var enclosed = false;
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||
{
|
||||
yield return EntityManager.GetEntity(grid.GridEntityId).GetComponent<RenderingTreeComponent>();
|
||||
|
||||
// if we're enclosed then we know no other grids relevant + don't need the map's rendertree
|
||||
if (grid.WorldBounds.Encloses(in worldAABB))
|
||||
{
|
||||
enclosed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!enclosed)
|
||||
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
|
||||
}
|
||||
|
||||
internal IEnumerable<DynamicTree<SpriteComponent>> GetSpriteTrees(MapId mapId, Box2 worldAABB)
|
||||
{
|
||||
foreach (var comp in GetRenderTrees(mapId, worldAABB))
|
||||
{
|
||||
yield return comp.SpriteTree;
|
||||
}
|
||||
}
|
||||
|
||||
internal IEnumerable<DynamicTree<PointLightComponent>> GetLightTrees(MapId mapId, Box2 worldAABB)
|
||||
{
|
||||
foreach (var comp in GetRenderTrees(mapId, worldAABB))
|
||||
{
|
||||
yield return comp.LightTree;
|
||||
}
|
||||
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
@@ -118,7 +104,7 @@ namespace Robust.Client.GameObjects
|
||||
QueueSpriteUpdate(component);
|
||||
}
|
||||
|
||||
private void AnythingMoved(MoveEvent args)
|
||||
private void AnythingMoved(ref MoveEvent args)
|
||||
{
|
||||
AnythingMovedSubHandler(args.Sender.Transform);
|
||||
}
|
||||
@@ -156,7 +142,7 @@ namespace Robust.Client.GameObjects
|
||||
QueueSpriteUpdate(component);
|
||||
}
|
||||
|
||||
private void SpriteParentChanged(EntityUid uid, SpriteComponent component, EntParentChangedMessage args)
|
||||
private void SpriteParentChanged(EntityUid uid, SpriteComponent component, ref EntParentChangedMessage args)
|
||||
{
|
||||
QueueSpriteUpdate(component);
|
||||
}
|
||||
@@ -189,7 +175,7 @@ namespace Robust.Client.GameObjects
|
||||
QueueLightUpdate(component);
|
||||
}
|
||||
|
||||
private void LightParentChanged(EntityUid uid, PointLightComponent component, EntParentChangedMessage args)
|
||||
private void LightParentChanged(EntityUid uid, PointLightComponent component, ref EntParentChangedMessage args)
|
||||
{
|
||||
QueueLightUpdate(component);
|
||||
}
|
||||
@@ -259,9 +245,8 @@ namespace Robust.Client.GameObjects
|
||||
EntityManager.GetEntity(_mapManager.GetGrid(gridId).GridEntityId).EnsureComponent<RenderingTreeComponent>();
|
||||
}
|
||||
|
||||
private RenderingTreeComponent? GetRenderTree(IEntity entity)
|
||||
internal static RenderingTreeComponent? GetRenderTree(IEntity entity)
|
||||
{
|
||||
|
||||
if (entity.Transform.MapID == MapId.Nullspace ||
|
||||
entity.HasComponent<RenderingTreeComponent>()) return null;
|
||||
|
||||
@@ -278,6 +263,11 @@ namespace Robust.Client.GameObjects
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool IsVisible(SpriteComponent component)
|
||||
{
|
||||
return component.Visible && !component.ContainerOccluded;
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
_checkedChildren.Clear();
|
||||
@@ -285,7 +275,7 @@ namespace Robust.Client.GameObjects
|
||||
foreach (var sprite in _spriteQueue)
|
||||
{
|
||||
sprite.TreeUpdateQueued = false;
|
||||
if (!sprite.Visible || sprite.ContainerOccluded)
|
||||
if (!IsVisible(sprite))
|
||||
{
|
||||
ClearSprite(sprite);
|
||||
continue;
|
||||
@@ -302,8 +292,7 @@ namespace Robust.Client.GameObjects
|
||||
continue;
|
||||
}
|
||||
|
||||
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
var aabb = RenderingTreeComponent.SpriteAabbFunc(sprite, worldPos).Translated(-treePos);
|
||||
var aabb = RenderingTreeComponent.SpriteAabbFunc(sprite, worldPos);
|
||||
|
||||
// If we're on a new map then clear the old one.
|
||||
if (oldMapTree != newMapTree)
|
||||
@@ -347,8 +336,7 @@ namespace Robust.Client.GameObjects
|
||||
Logger.WarningS(LoggerSawmill, $"Light radius for {light.Owner} set above max radius of {MaxLightRadius}. This may lead to pop-in.");
|
||||
}
|
||||
|
||||
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
var aabb = RenderingTreeComponent.LightAabbFunc(light, worldPos).Translated(-treePos);
|
||||
var aabb = RenderingTreeComponent.LightAabbFunc(light, worldPos);
|
||||
|
||||
// If we're on a new map then clear the old one.
|
||||
if (oldMapTree != newMapTree)
|
||||
|
||||
@@ -15,15 +15,14 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly RenderingTreeSystem _treeSystem = default!;
|
||||
|
||||
private RenderingTreeSystem _treeSystem = default!;
|
||||
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_treeSystem = Get<RenderingTreeSystem>();
|
||||
SubscribeLocalEvent<SpriteUpdateInertEvent>(QueueUpdateInert);
|
||||
}
|
||||
|
||||
@@ -40,9 +39,7 @@ namespace Robust.Client.GameObjects
|
||||
sprite.DoUpdateIsInert();
|
||||
}
|
||||
|
||||
// So we could calculate the correct size of the entities based on the contents of their sprite...
|
||||
// Or we can just assume that no entity is larger than 10x10 and get a stupid easy check.
|
||||
var pvsBounds = _eyeManager.GetWorldViewport().Enlarged(5);
|
||||
var pvsBounds = _eyeManager.GetWorldViewbounds();
|
||||
|
||||
var currentMap = _eyeManager.CurrentMap;
|
||||
if (currentMap == MapId.Nullspace)
|
||||
@@ -52,7 +49,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
foreach (var comp in _treeSystem.GetRenderTrees(currentMap, pvsBounds))
|
||||
{
|
||||
var bounds = pvsBounds.Translated(-comp.Owner.Transform.WorldPosition);
|
||||
var bounds = comp.Owner.Transform.InvWorldMatrix.TransformBox(pvsBounds);
|
||||
|
||||
comp.SpriteTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
|
||||
{
|
||||
|
||||
@@ -20,6 +20,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -50,6 +51,7 @@ namespace Robust.Client.GameStates
|
||||
[Dependency] private readonly IClientGameTiming _timing = default!;
|
||||
[Dependency] private readonly INetConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IComponentManager _componentManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
#if EXCEPTION_TOLERANCE
|
||||
@@ -334,8 +336,6 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private void ResetPredictedEntities(GameTick curTick)
|
||||
{
|
||||
var bus = (EntityEventBus) _entities.EventBus;
|
||||
|
||||
foreach (var entity in _entities.GetEntities())
|
||||
{
|
||||
// TODO: 99% there's an off-by-one here.
|
||||
@@ -364,7 +364,8 @@ namespace Robust.Client.GameStates
|
||||
|
||||
Logger.DebugS(CVars.NetPredict.Name, $" And also its component {comp.Name}");
|
||||
// TODO: Handle interpolation.
|
||||
bus.RaiseComponentEvent(comp, new ComponentHandleState(compState, null));
|
||||
var handleState = new ComponentHandleState(compState, null);
|
||||
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
|
||||
comp.HandleComponentState(compState, null);
|
||||
}
|
||||
}
|
||||
@@ -381,6 +382,8 @@ namespace Robust.Client.GameStates
|
||||
Debug.Assert(_players.LocalPlayer != null, "_players.LocalPlayer != null");
|
||||
var player = _players.LocalPlayer.Session;
|
||||
|
||||
var bus = _entityManager.EventBus;
|
||||
|
||||
foreach (var createdEntity in createdEntities)
|
||||
{
|
||||
var compData = new Dictionary<uint, ComponentState>();
|
||||
@@ -388,7 +391,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var (netId, component) in _componentManager.GetNetComponents(createdEntity))
|
||||
{
|
||||
var state = component.GetComponentState(player);
|
||||
var state = _componentManager.GetComponentState(bus, component, player);
|
||||
|
||||
if(state.GetType() == typeof(ComponentState))
|
||||
continue;
|
||||
@@ -472,14 +475,12 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
}
|
||||
|
||||
var bus = (EntityEventBus) _entities.EventBus;
|
||||
|
||||
// Make sure this is done after all entities have been instantiated.
|
||||
foreach (var kvStates in toApply)
|
||||
{
|
||||
var ent = kvStates.Key;
|
||||
var entity = (Entity) ent;
|
||||
HandleEntityState(entity.EntityManager.ComponentManager, entity, bus, kvStates.Value.Item1,
|
||||
HandleEntityState(entity.EntityManager.ComponentManager, entity, _entities.EventBus, kvStates.Value.Item1,
|
||||
kvStates.Value.Item2);
|
||||
}
|
||||
|
||||
@@ -547,7 +548,7 @@ namespace Robust.Client.GameStates
|
||||
return created;
|
||||
}
|
||||
|
||||
private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityEventBus bus, EntityState? curState,
|
||||
private void HandleEntityState(IComponentManager compMan, IEntity entity, IEventBus bus, EntityState? curState,
|
||||
EntityState? nextState)
|
||||
{
|
||||
var compStateWork = new Dictionary<ushort, (ComponentState? curState, ComponentState? nextState)>();
|
||||
@@ -609,7 +610,8 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
try
|
||||
{
|
||||
bus.RaiseComponentEvent(component, new ComponentHandleState(cur, next));
|
||||
var handleState = new ComponentHandleState(cur, next);
|
||||
bus.RaiseComponentEvent(component, ref handleState);
|
||||
component.HandleComponentState(cur, next);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -68,6 +68,28 @@ namespace Robust.Client.Graphics
|
||||
return new Box2(left, bottom, right, top);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2Rotated GetWorldViewbounds()
|
||||
{
|
||||
var vpSize = _displayManager.ScreenSize;
|
||||
|
||||
var topRight = ScreenToMap(new Vector2(vpSize.X, 0)).Position;
|
||||
var bottomLeft = ScreenToMap(new Vector2(0, vpSize.Y)).Position;
|
||||
|
||||
var rotation = new Angle(CurrentEye.Rotation);
|
||||
var center = (bottomLeft + topRight) / 2;
|
||||
|
||||
var localTopRight = topRight - center;
|
||||
var localBotLeft = bottomLeft - center;
|
||||
|
||||
localTopRight = rotation.RotateVec(localTopRight);
|
||||
localBotLeft = rotation.RotateVec(localBotLeft);
|
||||
|
||||
var bounds = new Box2(localBotLeft, localTopRight).Translated(center);
|
||||
|
||||
return new Box2Rotated(bounds, -CurrentEye.Rotation, bounds.Center);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2 WorldToScreen(Vector2 point)
|
||||
{
|
||||
@@ -111,10 +133,11 @@ namespace Robust.Client.Graphics
|
||||
public MapCoordinates ScreenToMap(ScreenCoordinates point)
|
||||
{
|
||||
var (pos, window) = point;
|
||||
if (window != MainViewport.Window?.Id)
|
||||
|
||||
if (_uiManager.MouseGetControl(point) is not IViewportControl viewport)
|
||||
return default;
|
||||
|
||||
return MainViewport.ScreenToMap(pos);
|
||||
return viewport.ScreenToMap(pos);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -31,6 +31,8 @@ namespace Robust.Client.Graphics
|
||||
/// </summary>
|
||||
Box2 GetWorldViewport();
|
||||
|
||||
Box2Rotated GetWorldViewbounds();
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the projection matrix to transform a point from camera space
|
||||
/// to UI screen space.
|
||||
@@ -53,7 +55,7 @@ namespace Robust.Client.Graphics
|
||||
ScreenCoordinates CoordinatesToScreen(EntityCoordinates point);
|
||||
|
||||
/// <summary>
|
||||
/// Unprojects a point from UI screen space to world space using the current camera.
|
||||
/// Unprojects a point from UI screen space to world space using the viewport under the screen coordinates.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The game exists on the 2D X/Y plane, so this function returns a point o the plane
|
||||
@@ -64,7 +66,7 @@ namespace Robust.Client.Graphics
|
||||
MapCoordinates ScreenToMap(ScreenCoordinates point);
|
||||
|
||||
/// <summary>
|
||||
/// Unprojects a point from UI screen space to world space using the current camera.
|
||||
/// Unprojects a point from UI screen space to world space using the main viewport.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The game exists on the 2D X/Y plane, so this function returns a point o the plane
|
||||
|
||||
@@ -9,6 +9,7 @@ using OpenToolkit.Audio.OpenAL;
|
||||
using OpenToolkit.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Log;
|
||||
using Vector2 = Robust.Shared.Maths.Vector2;
|
||||
|
||||
@@ -58,6 +59,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX");
|
||||
|
||||
_cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true);
|
||||
_cfg.OnValueChanged(CVars.AudioAttenuation, SetAudioAttenuation, true);
|
||||
}
|
||||
|
||||
private void _audioCreateContext()
|
||||
@@ -192,6 +194,43 @@ namespace Robust.Client.Graphics.Clyde
|
||||
AL.Listener(ALListenerf.Gain, _baseGain * newVolume);
|
||||
}
|
||||
|
||||
public void SetAudioAttenuation(int value)
|
||||
{
|
||||
var attenuation = (Attenuation) value;
|
||||
|
||||
switch (attenuation)
|
||||
{
|
||||
case Attenuation.NoAttenuation:
|
||||
AL.DistanceModel(ALDistanceModel.None);
|
||||
break;
|
||||
case Attenuation.InverseDistance:
|
||||
AL.DistanceModel(ALDistanceModel.InverseDistance);
|
||||
break;
|
||||
case Attenuation.Default:
|
||||
case Attenuation.InverseDistanceClamped:
|
||||
AL.DistanceModel(ALDistanceModel.InverseDistanceClamped);
|
||||
break;
|
||||
case Attenuation.LinearDistance:
|
||||
AL.DistanceModel(ALDistanceModel.LinearDistance);
|
||||
break;
|
||||
case Attenuation.LinearDistanceClamped:
|
||||
AL.DistanceModel(ALDistanceModel.LinearDistanceClamped);
|
||||
break;
|
||||
case Attenuation.ExponentDistance:
|
||||
AL.DistanceModel(ALDistanceModel.ExponentDistance);
|
||||
break;
|
||||
case Attenuation.ExponentDistanceClamped:
|
||||
AL.DistanceModel(ALDistanceModel.ExponentDistanceClamped);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"No implementation to set {attenuation.ToString()} for DistanceModel!");
|
||||
}
|
||||
|
||||
var attToString = attenuation == Attenuation.Default ? Attenuation.InverseDistanceClamped : attenuation;
|
||||
|
||||
_openALSawmill.Info($"Set audio attenuation to {attToString.ToString()}");
|
||||
}
|
||||
|
||||
public IClydeAudioSource CreateAudioSource(AudioStream stream)
|
||||
{
|
||||
var source = AL.GenSource();
|
||||
@@ -419,7 +458,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public void StopPlaying()
|
||||
{
|
||||
_checkDisposed();
|
||||
if (_isDisposed()) return;
|
||||
AL.SourceStop(SourceHandle);
|
||||
_master._checkAlError();
|
||||
}
|
||||
@@ -486,6 +525,27 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetMaxDistance(float distance)
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.MaxDistance, distance);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetRolloffFactor(float rolloffFactor)
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.RolloffFactor, rolloffFactor);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetReferenceDistance(float refDistance)
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.ReferenceDistance, refDistance);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetOcclusion(float blocks)
|
||||
{
|
||||
_checkDisposed();
|
||||
@@ -612,6 +672,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
SourceHandle = -1;
|
||||
}
|
||||
|
||||
private bool _isDisposed()
|
||||
{
|
||||
return SourceHandle == -1;
|
||||
}
|
||||
|
||||
private void _checkDisposed()
|
||||
{
|
||||
if (SourceHandle == -1)
|
||||
@@ -661,7 +726,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public void StopPlaying()
|
||||
{
|
||||
_checkDisposed();
|
||||
if (_isDisposed()) return;
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.SourceStop(SourceHandle!.Value);
|
||||
_master._checkAlError();
|
||||
@@ -726,6 +791,27 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetMaxDistance(float distance)
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.MaxDistance, distance);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetRolloffFactor(float rolloffFactor)
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.RolloffFactor, rolloffFactor);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetReferenceDistance(float refDistance)
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.ReferenceDistance, refDistance);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetOcclusion(float blocks)
|
||||
{
|
||||
_checkDisposed();
|
||||
@@ -850,6 +936,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
SourceHandle = null;
|
||||
}
|
||||
|
||||
private bool _isDisposed()
|
||||
{
|
||||
return SourceHandle == null;
|
||||
}
|
||||
|
||||
private void _checkDisposed()
|
||||
{
|
||||
if (SourceHandle == null)
|
||||
|
||||
@@ -2,7 +2,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal sealed partial class Clyde
|
||||
{
|
||||
private static readonly (string, uint)[] BaseShaderAttribLocations = {
|
||||
private static readonly (string, uint)[] BaseShaderAttribLocations =
|
||||
{
|
||||
("aPos", 0),
|
||||
("tCoord", 1)
|
||||
};
|
||||
@@ -30,14 +31,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// To be clear: You shouldn't change this. This just helps with understanding where Primitive Restart is being used.
|
||||
private const ushort PrimitiveRestartIndex = ushort.MaxValue;
|
||||
|
||||
private enum Renderer : short
|
||||
private enum Renderer : sbyte
|
||||
{
|
||||
// Default: Try all supported renderers (not necessarily the renderers shown here)
|
||||
Default = default,
|
||||
OpenGL33 = 1,
|
||||
OpenGL31 = 2,
|
||||
OpenGLES2 = 3,
|
||||
// Auto: Try all supported renderers (not necessarily the renderers shown here)
|
||||
Auto = default,
|
||||
OpenGL = 1,
|
||||
Explode = -1,
|
||||
}
|
||||
|
||||
private enum RendererOpenGLVersion : byte
|
||||
{
|
||||
Auto = default,
|
||||
GL33 = 1,
|
||||
GL31 = 2,
|
||||
GLES3 = 3,
|
||||
GLES2 = 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
TextEntered?.Invoke(args);
|
||||
break;
|
||||
case DEventWindowClosed(var reg, var args):
|
||||
reg.Closed?.Invoke(args);
|
||||
reg.RequestClosed?.Invoke(args);
|
||||
CloseWindow?.Invoke(args);
|
||||
|
||||
if (reg.DisposeOnClose && !reg.IsMainWindow)
|
||||
@@ -95,24 +95,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_eventDispatchQueue.Enqueue(new DEventScroll(ev));
|
||||
}
|
||||
|
||||
private void SendCloseWindow(WindowReg windowReg, WindowClosedEventArgs ev)
|
||||
private void SendCloseWindow(WindowReg windowReg, WindowRequestClosedEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventWindowClosed(windowReg, ev));
|
||||
}
|
||||
|
||||
private void SendWindowResized(WindowReg reg, Vector2i oldSize)
|
||||
{
|
||||
if (reg.IsMainWindow)
|
||||
{
|
||||
UpdateMainWindowLoadedRtSize();
|
||||
GL.Viewport(0, 0, reg.FramebufferSize.X, reg.FramebufferSize.Y);
|
||||
CheckGlError();
|
||||
}
|
||||
else
|
||||
{
|
||||
reg.RenderTexture!.Dispose();
|
||||
CreateWindowRenderTexture(reg);
|
||||
}
|
||||
var loaded = RtToLoaded(reg.RenderTarget);
|
||||
loaded.Size = reg.FramebufferSize;
|
||||
|
||||
_glContext!.WindowResized(reg, oldSize);
|
||||
|
||||
var eventArgs = new WindowResizedEventArgs(
|
||||
oldSize,
|
||||
@@ -156,7 +149,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private sealed record DEventScroll(MouseWheelEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventWindowClosed(WindowReg Reg, WindowClosedEventArgs Args) : DEventBase;
|
||||
private sealed record DEventWindowClosed(WindowReg Reg, WindowRequestClosedEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventWindowResized(WindowResizedEventArgs Args) : DEventBase;
|
||||
|
||||
|
||||
85
Robust.Client/Graphics/Clyde/Clyde.GLContext.cs
Normal file
85
Robust.Client/Graphics/Clyde/Clyde.GLContext.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal sealed partial class Clyde
|
||||
{
|
||||
private GLContextBase? _glContext;
|
||||
|
||||
// Current OpenGL version we managed to initialize with.
|
||||
private RendererOpenGLVersion _openGLVersion;
|
||||
|
||||
private void InitGLContextManager()
|
||||
{
|
||||
// Advanced GL contexts currently disabled due to lack of testing etc.
|
||||
if (OperatingSystem.IsWindows() && _cfg.GetCVar(CVars.DisplayAngle))
|
||||
{
|
||||
/*
|
||||
if (_cfg.GetCVar(CVars.DisplayAngleCustomSwapChain))
|
||||
{
|
||||
_sawmillOgl.Debug("Trying custom swap chain ANGLE.");
|
||||
var ctxAngle = new GLContextAngle(this);
|
||||
|
||||
if (ctxAngle.TryInitialize())
|
||||
{
|
||||
_sawmillOgl.Debug("Successfully initialized custom ANGLE");
|
||||
_glContext = ctxAngle;
|
||||
|
||||
ctxAngle.EarlyInit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
if (_cfg.GetCVar(CVars.DisplayEgl))
|
||||
{
|
||||
_sawmillOgl.Debug("Trying EGL");
|
||||
var ctxEgl = new GLContextEgl(this);
|
||||
ctxEgl.InitializePublic();
|
||||
_glContext = ctxEgl;
|
||||
return;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
if (OperatingSystem.IsLinux() && _cfg.GetCVar(CVars.DisplayEgl))
|
||||
{
|
||||
_sawmillOgl.Debug("Trying EGL");
|
||||
var ctxEgl = new GLContextEgl(this);
|
||||
ctxEgl.InitializePublic();
|
||||
_glContext = ctxEgl;
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
_glContext = new GLContextWindow(this);
|
||||
}
|
||||
|
||||
private struct GLContextSpec
|
||||
{
|
||||
public int Major;
|
||||
public int Minor;
|
||||
public GLContextProfile Profile;
|
||||
public GLContextCreationApi CreationApi;
|
||||
// Used by GLContextWindow to figure out which GL version managed to initialize.
|
||||
public RendererOpenGLVersion OpenGLVersion;
|
||||
}
|
||||
|
||||
private enum GLContextProfile
|
||||
{
|
||||
Compatibility,
|
||||
Core,
|
||||
Es
|
||||
}
|
||||
|
||||
private enum GLContextCreationApi
|
||||
{
|
||||
Native,
|
||||
Egl,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
@@ -24,6 +23,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private bool _hasGLVertexArrayObject;
|
||||
private bool _hasGLVertexArrayObjectOes;
|
||||
private bool _hasGLFloatFramebuffers;
|
||||
private bool _hasGLES3Shaders;
|
||||
private bool HasGLAnyMapBuffer => _hasGLMapBuffer || _hasGLMapBufferRange || _hasGLMapBufferOes;
|
||||
private bool _hasGLMapBuffer;
|
||||
private bool _hasGLMapBufferOes;
|
||||
@@ -72,6 +72,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
CheckGLCap(ref _hasGLMapBufferRange, "map_buffer_range", (3, 0));
|
||||
CheckGLCap(ref _hasGLPixelBufferObjects, "pixel_buffer_object", (2, 1));
|
||||
CheckGLCap(ref _hasGLStandardDerivatives, "standard_derivatives", (2, 1));
|
||||
|
||||
_hasGLSrgb = true;
|
||||
_hasGLReadFramebuffer = true;
|
||||
_hasGLPrimitiveRestart = true;
|
||||
_hasGLUniformBuffers = true;
|
||||
_hasGLFloatFramebuffers = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -94,15 +100,31 @@ namespace Robust.Client.Graphics.Clyde
|
||||
CheckGLCap(ref _hasGLMapBufferRange, "map_buffer_range", (3, 0));
|
||||
CheckGLCap(ref _hasGLPixelBufferObjects, "pixel_buffer_object", (3, 0));
|
||||
CheckGLCap(ref _hasGLStandardDerivatives, "standard_derivatives", (3, 0), "GL_OES_standard_derivatives");
|
||||
}
|
||||
CheckGLCap(ref _hasGLReadFramebuffer, "read_framebuffer", (3, 0));
|
||||
CheckGLCap(ref _hasGLPrimitiveRestart, "primitive_restart", (3, 1));
|
||||
CheckGLCap(ref _hasGLUniformBuffers, "uniform_buffers", (3, 0));
|
||||
CheckGLCap(ref _hasGLFloatFramebuffers, "float_framebuffers", (3, 2), "GL_EXT_color_buffer_float");
|
||||
CheckGLCap(ref _hasGLES3Shaders, "gles3_shaders", (3, 0));
|
||||
|
||||
// TODO: Enable these on ES 3.0
|
||||
_hasGLSrgb = !_isGLES;
|
||||
_hasGLReadFramebuffer = !_isGLES;
|
||||
_hasGLPrimitiveRestart = !_isGLES;
|
||||
_hasGLUniformBuffers = !_isGLES;
|
||||
// This is 3.2 or extensions
|
||||
_hasGLFloatFramebuffers = !_isGLES;
|
||||
if (major >= 3)
|
||||
{
|
||||
if (_glContext!.HasBrokenWindowSrgb)
|
||||
{
|
||||
_hasGLSrgb = false;
|
||||
_sawmillOgl.Debug(" sRGB: false (window broken sRGB)");
|
||||
}
|
||||
else
|
||||
{
|
||||
_hasGLSrgb = true;
|
||||
_sawmillOgl.Debug(" sRGB: true");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_hasGLSrgb = false;
|
||||
_sawmillOgl.Debug(" sRGB: false");
|
||||
}
|
||||
}
|
||||
|
||||
_sawmillOgl.Debug($" GLES: {_isGLES}");
|
||||
|
||||
@@ -141,7 +163,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
"map_buffer_range",
|
||||
"pixel_buffer_object",
|
||||
"map_buffer_oes",
|
||||
"standard_derivatives"
|
||||
"standard_derivatives",
|
||||
"read_framebuffer",
|
||||
"primitive_restart",
|
||||
"uniform_buffers",
|
||||
"float_framebuffers",
|
||||
"gles3_shaders",
|
||||
};
|
||||
|
||||
foreach (var cvar in cvars)
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -19,18 +20,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private int _verticesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
|
||||
private int _indicesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
|
||||
|
||||
private void _drawGrids(Box2 worldBounds)
|
||||
private void _drawGrids(Viewport viewport, Box2Rotated worldBounds, IEye eye)
|
||||
{
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var mapId = eye.Position.MapId;
|
||||
if (!_mapManager.MapExists(mapId))
|
||||
{
|
||||
// fall back to the default eye's map
|
||||
_eyeManager.ClearCurrentEye();
|
||||
mapId = _eyeManager.CurrentMap;
|
||||
// fall back to nullspace map
|
||||
mapId = MapId.Nullspace;
|
||||
}
|
||||
|
||||
SetTexture(TextureUnit.Texture0, _tileDefinitionManager.TileTextureAtlas);
|
||||
SetTexture(TextureUnit.Texture1, _lightingReady ? _currentViewport!.LightRenderTarget.Texture : _stockTextureWhite);
|
||||
SetTexture(TextureUnit.Texture1, _lightingReady ? viewport.LightRenderTarget.Texture : _stockTextureWhite);
|
||||
|
||||
var (gridProgram, _) = ActivateShaderInstance(_defaultShader.Handle);
|
||||
SetupGlobalUniformsImmediate(gridProgram, (ClydeTexture) _tileDefinitionManager.TileTextureAtlas);
|
||||
@@ -53,14 +53,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var transform = compMan.GetComponent<ITransformComponent>(grid.GridEntityId);
|
||||
gridProgram.SetUniform(UniIModelMatrix, transform.WorldMatrix);
|
||||
|
||||
foreach (var (_, chunk) in grid.GetMapChunks())
|
||||
foreach (var chunk in grid.GetMapChunks(worldBounds))
|
||||
{
|
||||
// Calc world bounds for chunk.
|
||||
if (!chunk.CalcWorldBounds().Intersects(in worldBounds))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_isChunkDirty(grid, chunk))
|
||||
{
|
||||
_updateChunkMesh(grid, chunk);
|
||||
@@ -210,11 +204,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void _updateOnGridCreated(MapId mapId, GridId gridId)
|
||||
{
|
||||
Logger.DebugS("grid", $"Adding {gridId} to grid renderer");
|
||||
_mapChunkData.Add(gridId, new Dictionary<Vector2i, MapChunkData>());
|
||||
}
|
||||
|
||||
private void _updateOnGridRemoved(MapId mapId, GridId gridId)
|
||||
{
|
||||
Logger.DebugS("grid", $"Removing {gridId} from grid renderer");
|
||||
|
||||
var data = _mapChunkData[gridId];
|
||||
foreach (var chunkDatum in data.Values)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Map;
|
||||
@@ -9,6 +10,7 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
@@ -25,12 +27,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
=
|
||||
new();
|
||||
|
||||
public unsafe void Render()
|
||||
public void Render()
|
||||
{
|
||||
CheckTransferringScreenshots();
|
||||
|
||||
var allMinimized = true;
|
||||
foreach (var windowReg in _windowing!.AllWindows)
|
||||
foreach (var windowReg in _windows)
|
||||
{
|
||||
if (!windowReg.IsMinimized)
|
||||
{
|
||||
@@ -44,9 +46,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
ClearFramebuffer(Color.Black);
|
||||
|
||||
// We have to keep running swapbuffers here
|
||||
// or else the user's PC will turn into a heater!!
|
||||
SwapMainBuffers();
|
||||
// Sleep to avoid turning the computer into a heater.
|
||||
Thread.Sleep(16);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -58,11 +59,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_debugStats.Reset();
|
||||
|
||||
// Basic pre-render busywork.
|
||||
// Clear screen to black.
|
||||
ClearFramebuffer(Color.Black);
|
||||
|
||||
// Update shared UBOs.
|
||||
_updateUniformConstants(_windowing.MainWindow!.FramebufferSize);
|
||||
_updateUniformConstants(_mainWindow!.FramebufferSize);
|
||||
|
||||
{
|
||||
CalcScreenMatrices(ScreenSize, out var proj, out var view);
|
||||
@@ -74,7 +73,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
DrawSplash(_renderHandle);
|
||||
FlushRenderQueue();
|
||||
SwapMainBuffers();
|
||||
SwapAllBuffers();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -84,6 +83,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
RenderViewport(viewport);
|
||||
}
|
||||
|
||||
// Clear screen to correct color.
|
||||
ClearFramebuffer(_userInterfaceManager.GetMainClearColor());
|
||||
|
||||
using (DebugGroup("UI"))
|
||||
{
|
||||
_userInterfaceManager.Render(_renderHandle);
|
||||
@@ -92,10 +94,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
TakeScreenshot(ScreenshotType.Final);
|
||||
|
||||
BlitSecondaryWindows();
|
||||
|
||||
// And finally, swap those buffers!
|
||||
SwapMainBuffers();
|
||||
SwapAllBuffers();
|
||||
}
|
||||
|
||||
private void RenderOverlays(Viewport vp, OverlaySpace space, in Box2 worldBox)
|
||||
@@ -130,7 +130,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
var list = GetOverlaysForSpace(space);
|
||||
|
||||
var worldBounds = CalcWorldBounds(vp);
|
||||
var worldBounds = CalcWorldAABB(vp);
|
||||
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, worldBounds);
|
||||
|
||||
foreach (var overlay in list)
|
||||
@@ -201,23 +201,19 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
|
||||
private void DrawEntities(Viewport viewport, Box2 worldBounds)
|
||||
private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 worldAABB, IEye eye)
|
||||
{
|
||||
if (_eyeManager.CurrentMap == MapId.Nullspace || !_mapManager.HasMapEntity(_eyeManager.CurrentMap))
|
||||
var mapId = eye.Position.MapId;
|
||||
if (mapId == MapId.Nullspace || !_mapManager.HasMapEntity(mapId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowEntities, worldBounds);
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowEntities, worldAABB);
|
||||
|
||||
var screenSize = viewport.Size;
|
||||
|
||||
// So we could calculate the correct size of the entities based on the contents of their sprite...
|
||||
// Or we can just assume that no entity is larger than 10x10 and get a stupid easy check.
|
||||
// TODO: Make this check more accurate.
|
||||
var widerBounds = worldBounds.Enlarged(5);
|
||||
|
||||
ProcessSpriteEntities(_eyeManager.CurrentMap, widerBounds, _drawingSpriteList);
|
||||
ProcessSpriteEntities(mapId, worldBounds, _drawingSpriteList);
|
||||
|
||||
var worldOverlays = new List<Overlay>();
|
||||
|
||||
@@ -265,7 +261,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
null,
|
||||
viewport,
|
||||
new UIBox2i((0, 0), viewport.Size),
|
||||
worldBounds);
|
||||
worldAABB);
|
||||
overlayIndex = j;
|
||||
continue;
|
||||
}
|
||||
@@ -273,13 +269,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
break;
|
||||
}
|
||||
|
||||
var matrix = entry.worldMatrix;
|
||||
var worldPosition = new Vector2(matrix.R0C2, matrix.R1C2);
|
||||
|
||||
RenderTexture? entityPostRenderTarget = null;
|
||||
Vector2i roundedPos = default;
|
||||
if (entry.sprite.PostShader != null)
|
||||
{
|
||||
// calculate world bounding box
|
||||
var spriteBB = entry.sprite.CalculateBoundingBox();
|
||||
var spriteBB = entry.sprite.CalculateBoundingBox(worldPosition);
|
||||
var spriteLB = spriteBB.BottomLeft;
|
||||
var spriteRT = spriteBB.TopRight;
|
||||
|
||||
@@ -291,6 +289,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// scale can be passed with PostShader as variable in future
|
||||
var postShadeScale = 1.25f;
|
||||
var screenSpriteSize = (Vector2i) ((screenRT - screenLB) * postShadeScale).Rounded();
|
||||
|
||||
// Rotate the vector by the eye angle, otherwise the bounding box will be incorrect
|
||||
screenSpriteSize = (Vector2i) eye.Rotation.RotateVec(screenSpriteSize).Rounded();
|
||||
screenSpriteSize.Y = -screenSpriteSize.Y;
|
||||
|
||||
// I'm not 100% sure why it works, but without it post-shader
|
||||
@@ -323,9 +324,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
var matrix = entry.worldMatrix;
|
||||
var worldPosition = new Vector2(matrix.R0C2, matrix.R1C2);
|
||||
entry.sprite.Render(_renderHandle.DrawingHandleWorld, in entry.worldRotation, in worldPosition);
|
||||
entry.sprite.Render(_renderHandle.DrawingHandleWorld, eye.Rotation, in entry.worldRotation, in worldPosition);
|
||||
|
||||
if (entry.sprite.PostShader != null && entityPostRenderTarget != null)
|
||||
{
|
||||
@@ -363,14 +362,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void ProcessSpriteEntities(MapId map, Box2 worldBounds,
|
||||
private void ProcessSpriteEntities(MapId map, Box2Rotated worldBounds,
|
||||
RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> list)
|
||||
{
|
||||
foreach (var comp in _entitySystemManager.GetEntitySystem<RenderingTreeSystem>().GetRenderTrees(map, worldBounds))
|
||||
{
|
||||
var bounds = worldBounds.Translated(-comp.Owner.Transform.WorldPosition);
|
||||
var bounds = comp.Owner.Transform.InvWorldMatrix.TransformBox(worldBounds);
|
||||
|
||||
comp.SpriteTree.QueryAabb(ref list, ((
|
||||
comp.SpriteTree.QueryAabb(ref list, (
|
||||
ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state,
|
||||
in SpriteComponent value) =>
|
||||
{
|
||||
@@ -381,22 +380,28 @@ namespace Robust.Client.Graphics.Clyde
|
||||
entry.sprite = value;
|
||||
entry.worldRot = transform.WorldRotation;
|
||||
entry.matrix = transform.WorldMatrix;
|
||||
var worldPos = entry.matrix.Transform(transform.LocalPosition);
|
||||
entry.yWorldPos = worldPos.Y;
|
||||
var worldPos = new Vector2(entry.matrix.R0C2, entry.matrix.R1C2);
|
||||
// Didn't use the bounds from the query as that has to be re-calculated (and is probably more expensive than this).
|
||||
var bounds = value.CalculateBoundingBox(worldPos);
|
||||
entry.yWorldPos = worldPos.Y - bounds.Extents.Y;
|
||||
return true;
|
||||
|
||||
}), bounds, true);
|
||||
}, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSplash(IRenderHandle handle)
|
||||
{
|
||||
var texture = _resourceCache.GetResource<TextureResource>("/Textures/Logo/logo.png").Texture;
|
||||
// Clear screen to black for splash.
|
||||
ClearFramebuffer(Color.Black);
|
||||
|
||||
var splashTex = _cfg.GetCVar(CVars.DisplaySplashLogo);
|
||||
var texture = _resourceCache.GetResource<TextureResource>(splashTex).Texture;
|
||||
|
||||
handle.DrawingHandleScreen.DrawTexture(texture, (ScreenSize - texture.Size) / 2);
|
||||
}
|
||||
|
||||
private void RenderInRenderTarget(RenderTargetBase rt, Action a)
|
||||
private void RenderInRenderTarget(RenderTargetBase rt, Action a, Color clearColor=default)
|
||||
{
|
||||
// TODO: for the love of god all this state pushing/popping needs to be cleaned up.
|
||||
|
||||
@@ -410,7 +415,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
{
|
||||
BindRenderTargetFull(RtToLoaded(rt));
|
||||
ClearFramebuffer(default);
|
||||
ClearFramebuffer(clearColor);
|
||||
SetViewportImmediate(Box2i.FromDimensions(Vector2i.Zero, rt.Size));
|
||||
_updateUniformConstants(rt.Size);
|
||||
CalcScreenMatrices(rt.Size, out var proj, out var view);
|
||||
@@ -453,30 +458,31 @@ namespace Robust.Client.Graphics.Clyde
|
||||
SetProjViewFull(proj, view);
|
||||
|
||||
// Calculate world-space AABB for camera, to cull off-screen things.
|
||||
var worldAABB = CalcWorldAABB(viewport);
|
||||
var worldBounds = CalcWorldBounds(viewport);
|
||||
|
||||
if (_eyeManager.CurrentMap != MapId.Nullspace)
|
||||
{
|
||||
using (DebugGroup("Lights"))
|
||||
{
|
||||
DrawLightsAndFov(viewport, worldBounds, eye);
|
||||
DrawLightsAndFov(viewport, worldBounds, worldAABB, eye);
|
||||
}
|
||||
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowWorld, worldBounds);
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowWorld, worldAABB);
|
||||
FlushRenderQueue();
|
||||
|
||||
using (DebugGroup("Grids"))
|
||||
{
|
||||
_drawGrids(worldBounds);
|
||||
_drawGrids(viewport, worldBounds, eye);
|
||||
}
|
||||
|
||||
// We will also render worldspace overlays here so we can do them under / above entities as necessary
|
||||
using (DebugGroup("Entities"))
|
||||
{
|
||||
DrawEntities(viewport, worldBounds);
|
||||
DrawEntities(viewport, worldBounds, worldAABB, eye);
|
||||
}
|
||||
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowFOV, worldBounds);
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowFOV, worldAABB);
|
||||
|
||||
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawFov)
|
||||
{
|
||||
@@ -509,24 +515,40 @@ namespace Robust.Client.Graphics.Clyde
|
||||
UIBox2.FromDimensions(Vector2.Zero, viewport.Size), new Color(1, 1, 1, 0.5f));
|
||||
}
|
||||
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldBounds);
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldAABB);
|
||||
FlushRenderQueue();
|
||||
|
||||
_currentViewport = oldVp;
|
||||
});
|
||||
}
|
||||
|
||||
private static Box2 CalcWorldBounds(Viewport viewport)
|
||||
private static Box2 CalcWorldAABB(Viewport viewport)
|
||||
{
|
||||
var eye = viewport.Eye;
|
||||
if (eye == null)
|
||||
return default;
|
||||
|
||||
// TODO: This seems completely unfit by lacking things like rotation handling.
|
||||
return GetAABB(eye, viewport);
|
||||
}
|
||||
|
||||
private static Box2 GetAABB(IEye eye, Viewport viewport)
|
||||
{
|
||||
return Box2.CenteredAround(eye.Position.Position,
|
||||
viewport.Size / viewport.RenderScale / EyeManager.PixelsPerMeter * eye.Zoom);
|
||||
}
|
||||
|
||||
private static Box2Rotated CalcWorldBounds(Viewport viewport)
|
||||
{
|
||||
var eye = viewport.Eye;
|
||||
if (eye == null)
|
||||
return default;
|
||||
|
||||
var rotation = -eye.Rotation;
|
||||
var aabb = GetAABB(eye, viewport);
|
||||
|
||||
return new Box2Rotated(aabb, rotation, aabb.Center);
|
||||
}
|
||||
|
||||
private sealed class OverlayComparer : IComparer<Overlay>
|
||||
{
|
||||
public static readonly OverlayComparer Instance = new();
|
||||
|
||||
@@ -2,10 +2,8 @@ using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using ES20 = OpenToolkit.Graphics.ES20;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
@@ -276,7 +274,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void LoadGLProc<T>(string name, out T field) where T : Delegate
|
||||
{
|
||||
var proc = _windowing!.GraphicsBindingContext.GetProcAddress(name);
|
||||
var proc = _glBindingsContext.GetProcAddress(name);
|
||||
if (proc == IntPtr.Zero || proc == new IntPtr(1) || proc == new IntPtr(2))
|
||||
{
|
||||
throw new InvalidOperationException($"Unable to load GL function '{name}'!");
|
||||
|
||||
@@ -5,6 +5,7 @@ using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -316,18 +317,24 @@ namespace Robust.Client.Graphics.Clyde
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
private void DrawLightsAndFov(Viewport viewport, Box2 worldBounds, IEye eye)
|
||||
private void DrawLightsAndFov(Viewport viewport, Box2Rotated worldBounds, Box2 worldAABB, IEye eye)
|
||||
{
|
||||
if (!_lightManager.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var map = eye.Position.MapId;
|
||||
var mapId = eye.Position.MapId;
|
||||
|
||||
var (lights, count, expandedBounds) = GetLightsToRender(map, worldBounds);
|
||||
// If this map has lighting disabled, return
|
||||
if (!_mapManager.GetMapEntity(mapId).GetComponent<IMapComponent>().LightingEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateOcclusionGeometry(map, expandedBounds, eye.Position.Position);
|
||||
var (lights, count, expandedBounds) = GetLightsToRender(mapId, worldBounds, worldAABB);
|
||||
|
||||
UpdateOcclusionGeometry(mapId, expandedBounds, eye.Position.Position);
|
||||
|
||||
DrawFov(viewport, eye);
|
||||
|
||||
@@ -491,39 +498,41 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
private ((PointLightComponent light, Vector2 pos, float distanceSquared)[] lights, int count, Box2 expandedBounds)
|
||||
GetLightsToRender(MapId map, in Box2 worldBounds)
|
||||
GetLightsToRender(MapId map, in Box2Rotated worldBounds, in Box2 worldAABB)
|
||||
{
|
||||
var renderingTreeSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
|
||||
var enlargedBounds = worldBounds.Enlarged(renderingTreeSystem.MaxLightRadius);
|
||||
var enlargedBounds = worldAABB.Enlarged(renderingTreeSystem.MaxLightRadius);
|
||||
|
||||
// Use worldbounds for this one as we only care if the light intersects our actual bounds
|
||||
var state = (this, worldBounds, count: 0);
|
||||
var state = (this, worldAABB, count: 0);
|
||||
|
||||
foreach (var comp in renderingTreeSystem.GetRenderTrees(map, enlargedBounds))
|
||||
{
|
||||
var bounds = worldBounds.Translated(-comp.Owner.Transform.WorldPosition);
|
||||
var bounds = comp.Owner.Transform.InvWorldMatrix.TransformBox(worldBounds);
|
||||
|
||||
comp.LightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) =>
|
||||
comp.LightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldAABB, int count) state, in PointLightComponent light) =>
|
||||
{
|
||||
var transform = light.Owner.Transform;
|
||||
|
||||
if (state.count >= LightsToRenderListSize)
|
||||
{
|
||||
// There are too many lights to fit in the static memory.
|
||||
return false;
|
||||
}
|
||||
|
||||
var transform = light.Owner.Transform;
|
||||
|
||||
if (float.IsNaN(transform.LocalPosition.X) || float.IsNaN(transform.LocalPosition.Y)) return true;
|
||||
|
||||
var lightPos = transform.WorldMatrix.Transform(light.Offset);
|
||||
|
||||
var circle = new Circle(lightPos, light.Radius);
|
||||
|
||||
// If the light doesn't touch anywhere the camera can see, it doesn't matter.
|
||||
if (!circle.Intersects(state.worldBounds))
|
||||
if (!circle.Intersects(state.worldAABB))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
float distanceSquared = (state.worldBounds.Center - lightPos).LengthSquared;
|
||||
float distanceSquared = (state.worldAABB.Center - lightPos).LengthSquared;
|
||||
state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared);
|
||||
|
||||
return true;
|
||||
@@ -548,7 +557,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// We expand the world bounds so that it encompasses the center of every light source.
|
||||
// This should make it so no culled occluder can make a difference.
|
||||
// (if the occluder is in the current lights at all, it's still not between the light and the world bounds).
|
||||
var expandedBounds = worldBounds;
|
||||
var expandedBounds = worldAABB;
|
||||
|
||||
for (var i = 0; i < state.count; i++)
|
||||
{
|
||||
@@ -773,24 +782,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var ii = 0;
|
||||
var imi = 0;
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, expandedBounds, true))
|
||||
foreach (var comp in occluderSystem.GetOccluderTrees(map, expandedBounds))
|
||||
{
|
||||
if (!occluderSystem.TryGetOccluderTreeForGrid(map, gridId, out var occluderTree)) continue;
|
||||
var treeBounds = comp.Owner.Transform.InvWorldMatrix.TransformBox(expandedBounds);
|
||||
|
||||
Box2 gridBounds;
|
||||
|
||||
if (gridId == GridId.Invalid)
|
||||
{
|
||||
gridBounds = expandedBounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Ideally this would clamp to the outer border of what we can see
|
||||
var grid = _mapManager.GetGrid(gridId);
|
||||
gridBounds = expandedBounds.Translated(-grid.WorldPosition);
|
||||
}
|
||||
|
||||
occluderTree.QueryAabb((in OccluderComponent sOccluder) =>
|
||||
comp.Tree.QueryAabb((in OccluderComponent sOccluder) =>
|
||||
{
|
||||
var occluder = (ClientOccluderComponent)sOccluder;
|
||||
var transform = occluder.Owner.Transform;
|
||||
@@ -919,7 +915,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
ami += 4;
|
||||
|
||||
return true;
|
||||
}, gridBounds);
|
||||
}, treeBounds);
|
||||
}
|
||||
|
||||
_occlusionDataLength = ii;
|
||||
|
||||
@@ -112,9 +112,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return clydeTexture;
|
||||
}
|
||||
|
||||
public void RenderInRenderTarget(IRenderTarget target, Action a)
|
||||
public void RenderInRenderTarget(IRenderTarget target, Action a, Color clearColor=default)
|
||||
{
|
||||
_clyde.RenderInRenderTarget((RenderTargetBase) target, a);
|
||||
_clyde.RenderInRenderTarget((RenderTargetBase) target, a, clearColor);
|
||||
}
|
||||
|
||||
public void SetScissor(UIBox2i? scissorBox)
|
||||
@@ -136,7 +136,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// Switch rendering to pseudo-world space.
|
||||
{
|
||||
CalcWorldProjMatrix(_clyde._currentRenderTarget.Size, out var proj);
|
||||
_clyde.CalcWorldProjMatrix(_clyde._currentRenderTarget.Size, out var proj);
|
||||
|
||||
var ofsX = position.X - _clyde.ScreenSize.X / 2f;
|
||||
var ofsY = position.Y - _clyde.ScreenSize.Y / 2f;
|
||||
@@ -153,6 +153,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Draw the entity.
|
||||
sprite.Render(
|
||||
DrawingHandleWorld,
|
||||
Angle.Zero,
|
||||
overrideDirection == null
|
||||
? entity.Transform.WorldRotation
|
||||
: Angle.Zero,
|
||||
@@ -354,18 +355,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public override void DrawCircle(Vector2 position, float radius, Color color, bool filled = true)
|
||||
{
|
||||
//TODO: Scale number of sides based on radius
|
||||
const int Divisions = 8;
|
||||
const float ArcLength = MathF.PI * 2 / Divisions;
|
||||
int divisions = Math.Max(16,(int)(radius * 16));
|
||||
float arcLength = MathF.PI * 2 / divisions;
|
||||
|
||||
var filledTriangle = new Vector2[3];
|
||||
|
||||
// Draws a "circle", but its just a polygon with a bunch of sides
|
||||
// this is the GL_LINES version, not GL_LINE_STRIP
|
||||
for (int i = 0; i < Divisions; i++)
|
||||
for (int i = 0; i < divisions; i++)
|
||||
{
|
||||
var startPos = new Vector2(MathF.Cos(ArcLength * i) * radius, MathF.Sin(ArcLength * i) * radius);
|
||||
var endPos = new Vector2(MathF.Cos(ArcLength * (i+1)) * radius, MathF.Sin(ArcLength * (i + 1)) * radius);
|
||||
var startPos = new Vector2(MathF.Cos(arcLength * i) * radius, MathF.Sin(arcLength * i) * radius);
|
||||
var endPos = new Vector2(MathF.Cos(arcLength * (i+1)) * radius, MathF.Sin(arcLength * (i + 1)) * radius);
|
||||
|
||||
if(!filled)
|
||||
_renderHandle.DrawLine(startPos, endPos, color);
|
||||
|
||||
@@ -3,9 +3,9 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Log;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
// ReSharper disable once IdentifierTypo
|
||||
@@ -24,9 +24,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private readonly ConcurrentQueue<ClydeHandle> _renderTargetDisposeQueue
|
||||
= new();
|
||||
|
||||
// Initialized in Clyde's constructor
|
||||
private readonly RenderMainWindow _mainMainWindowRenderMainTarget;
|
||||
|
||||
// This is always kept up-to-date, except in CreateRenderTarget (because it restores the old value)
|
||||
// It is used for SRGB emulation.
|
||||
// It, like _mainWindowRenderTarget, is initialized in Clyde's constructor
|
||||
@@ -248,8 +245,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// NOTE: It's critically important that this be the "focal point" of all framebuffer bindings.
|
||||
if (rt.IsWindow)
|
||||
{
|
||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
|
||||
CheckGlError();
|
||||
_glContext!.BindWindowRenderTarget(rt.WindowId);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -267,18 +263,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMainWindowLoadedRtSize()
|
||||
{
|
||||
var loadedRt = RtToLoaded(_mainMainWindowRenderMainTarget);
|
||||
loadedRt.Size = _windowing!.MainWindow!.FramebufferSize;
|
||||
}
|
||||
|
||||
private sealed class LoadedRenderTarget
|
||||
{
|
||||
public bool IsWindow;
|
||||
public WindowId WindowId;
|
||||
|
||||
public Vector2i Size;
|
||||
public bool IsSrgb;
|
||||
|
||||
public bool FlipY;
|
||||
|
||||
public RTCF ColorFormat;
|
||||
|
||||
// Remaining properties only apply if the render target is NOT a window.
|
||||
@@ -386,11 +380,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RenderMainWindow : RenderTargetBase
|
||||
private sealed class RenderWindow : RenderTargetBase
|
||||
{
|
||||
public override Vector2i Size => Clyde._windowing!.MainWindow!.FramebufferSize;
|
||||
public override Vector2i Size => Clyde._renderTargets[Handle].Size;
|
||||
|
||||
public RenderMainWindow(Clyde clyde, ClydeHandle handle) : base(clyde, handle)
|
||||
public RenderWindow(Clyde clyde, ClydeHandle handle) : base(clyde, handle)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using System.Runtime.InteropServices;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
@@ -106,7 +105,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
UniformConstantsUBO.Reallocate(constants);
|
||||
}
|
||||
|
||||
private static void CalcScreenMatrices(in Vector2i screenSize, out Matrix3 proj, out Matrix3 view)
|
||||
private void CalcScreenMatrices(in Vector2i screenSize, out Matrix3 proj, out Matrix3 view)
|
||||
{
|
||||
proj = Matrix3.Identity;
|
||||
proj.R0C0 = 2f / screenSize.X;
|
||||
@@ -114,10 +113,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
proj.R0C2 = -1;
|
||||
proj.R1C2 = 1;
|
||||
|
||||
if (_currentRenderTarget.FlipY)
|
||||
{
|
||||
proj.R1C1 *= -1;
|
||||
proj.R1C2 *= -1;
|
||||
}
|
||||
|
||||
view = Matrix3.Identity;
|
||||
}
|
||||
|
||||
private static void CalcWorldMatrices(in Vector2i screenSize, in Vector2 renderScale, IEye eye,
|
||||
private void CalcWorldMatrices(in Vector2i screenSize, in Vector2 renderScale, IEye eye,
|
||||
out Matrix3 proj, out Matrix3 view)
|
||||
{
|
||||
eye.GetViewMatrix(out view, renderScale);
|
||||
@@ -126,11 +131,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void CalcWorldProjMatrix(in Vector2i screenSize, out Matrix3 proj)
|
||||
private void CalcWorldProjMatrix(in Vector2i screenSize, out Matrix3 proj)
|
||||
{
|
||||
proj = Matrix3.Identity;
|
||||
proj.R0C0 = EyeManager.PixelsPerMeter * 2f / screenSize.X;
|
||||
proj.R1C1 = EyeManager.PixelsPerMeter * 2f / screenSize.Y;
|
||||
|
||||
if (_currentRenderTarget.FlipY)
|
||||
{
|
||||
proj.R1C1 *= -1;
|
||||
proj.R1C2 *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetProjViewBuffer(in Matrix3 proj, in Matrix3 view)
|
||||
@@ -353,7 +364,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// Don't forget to flip it, these coordinates have bottom left as origin.
|
||||
// TODO: Broken when rendering to non-screen render targets.
|
||||
GL.Scissor(box.Left, _currentRenderTarget.Size.Y - box.Bottom, box.Width, box.Height);
|
||||
|
||||
if (_currentRenderTarget.FlipY)
|
||||
{
|
||||
GL.Scissor(box.Left, box.Top, box.Width, box.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.Scissor(box.Left, _currentRenderTarget.Size.Y - box.Bottom, box.Width, box.Height);
|
||||
}
|
||||
CheckGlError();
|
||||
}
|
||||
else if (oldIsScissoring)
|
||||
@@ -827,9 +846,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_lightingReady = false;
|
||||
_currentMatrixModel = Matrix3.Identity;
|
||||
SetScissorFull(null);
|
||||
BindRenderTargetFull(_mainMainWindowRenderMainTarget);
|
||||
BindRenderTargetFull(_mainWindow!.RenderTarget);
|
||||
_batchMetaData = null;
|
||||
_queuedShader = _defaultShader.Handle;
|
||||
|
||||
GL.Viewport(0, 0, _mainWindow!.FramebufferSize.X, _mainWindow!.FramebufferSize.Y);
|
||||
}
|
||||
|
||||
private void ResetBlendFunc()
|
||||
@@ -841,98 +862,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
BlendingFactorDest.OneMinusSrcAlpha);
|
||||
}
|
||||
|
||||
private void BlitSecondaryWindows()
|
||||
{
|
||||
// Only got main window.
|
||||
if (_windowing!.AllWindows.Count == 1)
|
||||
return;
|
||||
|
||||
if (!_hasGLFenceSync && _cfg.GetCVar(CVars.DisplayForceSyncWindows))
|
||||
{
|
||||
GL.Finish();
|
||||
}
|
||||
|
||||
if (EffectiveThreadWindowBlit)
|
||||
{
|
||||
foreach (var window in _windowing.AllWindows)
|
||||
{
|
||||
if (window.IsMainWindow)
|
||||
continue;
|
||||
|
||||
window.BlitDoneEvent!.Reset();
|
||||
window.BlitStartEvent!.Set();
|
||||
window.BlitDoneEvent.Wait();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var window in _windowing.AllWindows)
|
||||
{
|
||||
if (window.IsMainWindow)
|
||||
continue;
|
||||
|
||||
_windowing.GLMakeContextCurrent(window);
|
||||
BlitThreadDoSecondaryWindowBlit(window);
|
||||
}
|
||||
|
||||
_windowing.GLMakeContextCurrent(_windowing.MainWindow!);
|
||||
}
|
||||
}
|
||||
|
||||
private void BlitThreadDoSecondaryWindowBlit(WindowReg window)
|
||||
{
|
||||
var rt = window.RenderTexture!;
|
||||
|
||||
if (_hasGLFenceSync)
|
||||
{
|
||||
// 0xFFFFFFFFFFFFFFFFUL is GL_TIMEOUT_IGNORED
|
||||
var sync = rt!.LastGLSync;
|
||||
GL.WaitSync(sync, WaitSyncFlags.None, unchecked((long) 0xFFFFFFFFFFFFFFFFUL));
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
GL.Viewport(0, 0, window.FramebufferSize.X, window.FramebufferSize.Y);
|
||||
CheckGlError();
|
||||
|
||||
SetTexture(TextureUnit.Texture0, window.RenderTexture!.Texture);
|
||||
|
||||
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
|
||||
CheckGlError();
|
||||
|
||||
window.BlitDoneEvent?.Set();
|
||||
_windowing!.WindowSwapBuffers(window);
|
||||
}
|
||||
|
||||
private void BlitThreadInit(WindowReg reg)
|
||||
{
|
||||
_windowing!.GLMakeContextCurrent(reg);
|
||||
_windowing.GLSwapInterval(0);
|
||||
|
||||
if (!_isGLES)
|
||||
GL.Enable(EnableCap.FramebufferSrgb);
|
||||
|
||||
var vao = GL.GenVertexArray();
|
||||
GL.BindVertexArray(vao);
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, WindowVBO.ObjectHandle);
|
||||
// Vertex Coords
|
||||
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 0);
|
||||
GL.EnableVertexAttribArray(0);
|
||||
// Texture Coords.
|
||||
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 2 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(1);
|
||||
|
||||
var program = _compileProgram(_winBlitShaderVert, _winBlitShaderFrag, new (string, uint)[]
|
||||
{
|
||||
("aPos", 0),
|
||||
("tCoord", 1),
|
||||
}, includeLib: false);
|
||||
|
||||
GL.UseProgram(program.Handle);
|
||||
var loc = GL.GetUniformLocation(program.Handle, "tex");
|
||||
SetTexture(TextureUnit.Texture0, reg.RenderTexture!.Texture);
|
||||
GL.Uniform1(loc, 0);
|
||||
}
|
||||
|
||||
private void FenceRenderTarget(RenderTargetBase rt)
|
||||
{
|
||||
if (!_hasGLFenceSync || !rt.MakeGLFence)
|
||||
@@ -1078,7 +1007,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var a = _drawList[x].Item1;
|
||||
var b = _drawList[y].Item1;
|
||||
|
||||
var cmp = (a.DrawDepth).CompareTo(b.DrawDepth);
|
||||
var cmp = a.DrawDepth.CompareTo(b.DrawDepth);
|
||||
if (cmp != 0)
|
||||
{
|
||||
return cmp;
|
||||
@@ -1091,7 +1020,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = _drawList[x].Item4.CompareTo(_drawList[y].Item4);
|
||||
cmp = _drawList[y].Item4.CompareTo(_drawList[x].Item4);
|
||||
|
||||
if (cmp != 0)
|
||||
{
|
||||
|
||||
@@ -150,12 +150,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
if (_isGLES)
|
||||
{
|
||||
// GLES2 uses a different GLSL versioning scheme to desktop GL.
|
||||
versionHeader = "#version 100\n#define HAS_VARYING_ATTRIBUTE\n";
|
||||
if (_hasGLStandardDerivatives)
|
||||
if (_hasGLES3Shaders)
|
||||
{
|
||||
versionHeader += "#extension GL_OES_standard_derivatives : enable\n";
|
||||
versionHeader = "#version 300 es\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
// GLES2 uses a different GLSL versioning scheme to desktop GL.
|
||||
versionHeader = "#version 100\n#define HAS_VARYING_ATTRIBUTE\n";
|
||||
if (_hasGLStandardDerivatives)
|
||||
{
|
||||
versionHeader += "#extension GL_OES_standard_derivatives : enable\n";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (_hasGLStandardDerivatives)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.Utility;
|
||||
@@ -153,6 +155,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
isActuallySrgb = loadParams.Srgb;
|
||||
}
|
||||
else if (pixelType == typeof(Bgra32))
|
||||
{
|
||||
isActuallySrgb = loadParams.Srgb;
|
||||
}
|
||||
else if (pixelType == typeof(A8))
|
||||
{
|
||||
DebugTools.Assert(_hasGLTextureSwizzle);
|
||||
@@ -258,6 +264,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Note that if _hasGLSrgb is off, we import an sRGB texture as non-sRGB.
|
||||
// Shaders are expected to compensate for this
|
||||
Rgba32 => (srgb && _hasGLSrgb ? PIF.Srgb8Alpha8 : PIF.Rgba8, PF.Rgba, PT.UnsignedByte),
|
||||
Bgra32 => (srgb && _hasGLSrgb ? PIF.Srgb8Alpha8 : PIF.Rgba8, PF.Bgra, PT.UnsignedByte),
|
||||
A8 or L8 => (PIF.R8, PF.Red, PT.UnsignedByte),
|
||||
_ => throw new NotSupportedException("Unsupported pixel type."),
|
||||
};
|
||||
@@ -315,27 +322,80 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private unsafe void SetSubImage<T>(
|
||||
ClydeTexture texture,
|
||||
Vector2i dstTl,
|
||||
Image<T> srcImage,
|
||||
Image<T> img,
|
||||
in UIBox2i srcBox)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
if (!_hasGLTextureSwizzle)
|
||||
if (srcBox.Left < 0 ||
|
||||
srcBox.Top < 0 ||
|
||||
srcBox.Right > srcBox.Width ||
|
||||
srcBox.Bottom > srcBox.Height)
|
||||
{
|
||||
if (typeof(T) == typeof(A8))
|
||||
{
|
||||
SetSubImage(texture, dstTl, ApplyA8Swizzle((Image<A8>) (object) srcImage), srcBox);
|
||||
return;
|
||||
}
|
||||
throw new ArgumentOutOfRangeException(nameof(srcBox), "Source rectangle out of bounds.");
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(L8))
|
||||
{
|
||||
SetSubImage(texture, dstTl, ApplyL8Swizzle((Image<L8>) (object) srcImage), srcBox);
|
||||
return;
|
||||
}
|
||||
var size = srcBox.Width * srcBox.Height;
|
||||
|
||||
T[]? pooled = null;
|
||||
// C# won't let me use an if due to the stackalloc.
|
||||
var copyBuffer = size < 16 * 16
|
||||
? stackalloc T[size]
|
||||
: (pooled = ArrayPool<T>.Shared.Rent(size)).AsSpan(0, size);
|
||||
|
||||
var srcSpan = img.GetPixelSpan();
|
||||
var w = img.Width;
|
||||
FlipCopySubRegion(srcBox, w, srcSpan, copyBuffer);
|
||||
|
||||
SetSubImageImpl<T>(texture, dstTl, (srcBox.Width, srcBox.Height), copyBuffer);
|
||||
|
||||
if (pooled != null)
|
||||
ArrayPool<T>.Shared.Return(pooled);
|
||||
}
|
||||
|
||||
private unsafe void SetSubImage<T>(
|
||||
ClydeTexture texture,
|
||||
Vector2i dstTl,
|
||||
Vector2i size,
|
||||
ReadOnlySpan<T> buf)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
T[]? pooled = null;
|
||||
// C# won't let me use an if due to the stackalloc.
|
||||
var copyBuffer = buf.Length < 16 * 16
|
||||
? stackalloc T[buf.Length]
|
||||
: (pooled = ArrayPool<T>.Shared.Rent(buf.Length)).AsSpan(0, buf.Length);
|
||||
|
||||
FlipCopy(buf, copyBuffer, size.X, size.Y);
|
||||
|
||||
SetSubImageImpl<T>(texture, dstTl, size, copyBuffer);
|
||||
|
||||
if (pooled != null)
|
||||
ArrayPool<T>.Shared.Return(pooled);
|
||||
}
|
||||
|
||||
private unsafe void SetSubImageImpl<T>(
|
||||
ClydeTexture texture,
|
||||
Vector2i dstTl,
|
||||
Vector2i size,
|
||||
ReadOnlySpan<T> buf)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
if (!_hasGLTextureSwizzle && (typeof(T) == typeof(A8) || typeof(T) == typeof(L8)))
|
||||
{
|
||||
var swizzleBuf = ArrayPool<Rgba32>.Shared.Rent(buf.Length);
|
||||
|
||||
var destSpan = swizzleBuf.AsSpan(0, buf.Length);
|
||||
if (typeof(T) == typeof(A8))
|
||||
ApplyA8Swizzle(MemoryMarshal.Cast<T, A8>(buf), destSpan);
|
||||
else if (typeof(T) == typeof(L8))
|
||||
ApplyL8Swizzle(MemoryMarshal.Cast<T, L8>(buf), destSpan);
|
||||
|
||||
SetSubImageImpl<Rgba32>(texture, dstTl, size, destSpan);
|
||||
ArrayPool<Rgba32>.Shared.Return(swizzleBuf);
|
||||
return;
|
||||
}
|
||||
|
||||
var loaded = _loadedTextures[texture.TextureId];
|
||||
|
||||
var pixType = GetTexturePixelType<T>();
|
||||
|
||||
if (pixType != loaded.TexturePixelType)
|
||||
@@ -346,11 +406,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
throw new InvalidOperationException("Mismatching pixel type for texture.");
|
||||
}
|
||||
|
||||
if (loaded.Width < dstTl.X + srcBox.Width || loaded.Height < dstTl.Y + srcBox.Height)
|
||||
throw new ArgumentOutOfRangeException(nameof(srcBox), "Destination rectangle out of bounds.");
|
||||
|
||||
if (srcBox.Left < 0 || srcBox.Top < 0 || srcBox.Right > srcImage.Width || srcBox.Bottom > srcImage.Height)
|
||||
throw new ArgumentOutOfRangeException(nameof(srcBox), "Source rectangle out of bounds.");
|
||||
if (loaded.Width < dstTl.X + size.X || loaded.Height < dstTl.Y + size.Y)
|
||||
throw new ArgumentOutOfRangeException(nameof(size), "Destination rectangle out of bounds.");
|
||||
|
||||
if (sizeof(T) != 4)
|
||||
{
|
||||
@@ -364,24 +421,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GL.BindTexture(TextureTarget.Texture2D, loaded.OpenGLObject.Handle);
|
||||
CheckGlError();
|
||||
|
||||
var size = srcBox.Width * srcBox.Height;
|
||||
|
||||
var copyBuffer = size < 16 * 16 ? stackalloc T[size] : new T[size];
|
||||
|
||||
for (var y = 0; y < srcBox.Height; y++)
|
||||
for (var x = 0; x < srcBox.Width; x++)
|
||||
fixed (T* aPtr = buf)
|
||||
{
|
||||
copyBuffer[(srcBox.Height - y - 1) * srcBox.Width + x] = srcImage[x + srcBox.Left, srcBox.Top + y];
|
||||
}
|
||||
|
||||
fixed (T* aPtr = copyBuffer)
|
||||
{
|
||||
var dstY = loaded.Height - dstTl.Y - srcBox.Height;
|
||||
var dstY = loaded.Height - dstTl.Y - size.Y;
|
||||
GL.TexSubImage2D(
|
||||
TextureTarget.Texture2D,
|
||||
0,
|
||||
dstTl.X, dstY,
|
||||
srcBox.Width, srcBox.Height,
|
||||
size.X, size.Y,
|
||||
pf, pt,
|
||||
(IntPtr) aPtr);
|
||||
CheckGlError();
|
||||
@@ -398,7 +445,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
return default(T) switch
|
||||
{
|
||||
Rgba32 => TexturePixelType.Rgba32,
|
||||
Rgba32 or Bgra32 => TexturePixelType.Rgba32,
|
||||
L8 => TexturePixelType.L8,
|
||||
A8 => TexturePixelType.A8,
|
||||
_ => throw new NotSupportedException("Unsupported pixel type."),
|
||||
@@ -452,17 +499,35 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private static void FlipCopySubRegion<T>(
|
||||
UIBox2i srcBox,
|
||||
int w,
|
||||
ReadOnlySpan<T> srcSpan,
|
||||
Span<T> copyBuffer)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
var subH = srcBox.Height;
|
||||
var subW = srcBox.Width;
|
||||
|
||||
var dr = subH - 1;
|
||||
for (var r = 0; r < subH; r++, dr--)
|
||||
{
|
||||
var si = r * w + srcBox.Left;
|
||||
var di = dr * subW;
|
||||
var srcRow = srcSpan[si..(si + subW)];
|
||||
var dstRow = copyBuffer[di..(di + subW)];
|
||||
|
||||
srcRow.CopyTo(dstRow);
|
||||
}
|
||||
}
|
||||
|
||||
private static Image<Rgba32> ApplyA8Swizzle(Image<A8> source)
|
||||
{
|
||||
var newImage = new Image<Rgba32>(source.Width, source.Height);
|
||||
var sourceSpan = source.GetPixelSpan();
|
||||
var destSpan = newImage.GetPixelSpan();
|
||||
|
||||
for (var i = 0; i < sourceSpan.Length; i++)
|
||||
{
|
||||
var px = sourceSpan[i].PackedValue;
|
||||
destSpan[i] = new Rgba32(255, 255, 255, px);
|
||||
}
|
||||
ApplyA8Swizzle(sourceSpan, destSpan);
|
||||
|
||||
return newImage;
|
||||
}
|
||||
@@ -473,15 +538,28 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var sourceSpan = source.GetPixelSpan();
|
||||
var destSpan = newImage.GetPixelSpan();
|
||||
|
||||
for (var i = 0; i < sourceSpan.Length; i++)
|
||||
{
|
||||
var px = sourceSpan[i].PackedValue;
|
||||
destSpan[i] = new Rgba32(px, px, px, 255);
|
||||
}
|
||||
ApplyL8Swizzle(sourceSpan, destSpan);
|
||||
|
||||
return newImage;
|
||||
}
|
||||
|
||||
private static void ApplyL8Swizzle(ReadOnlySpan<L8> src, Span<Rgba32> dst)
|
||||
{
|
||||
for (var i = 0; i < src.Length; i++)
|
||||
{
|
||||
var px = src[i].PackedValue;
|
||||
dst[i] = new Rgba32(px, px, px, 255);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyA8Swizzle(ReadOnlySpan<A8> src, Span<Rgba32> dst)
|
||||
{
|
||||
for (var i = 0; i < src.Length; i++)
|
||||
{
|
||||
var px = src[i].PackedValue;
|
||||
dst[i] = new Rgba32(255, 255, 255, px);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class LoadedTexture
|
||||
{
|
||||
@@ -525,6 +603,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_clyde.SetSubImage(this, topLeft, sourceImage, sourceRegion);
|
||||
}
|
||||
|
||||
public override void SetSubImage<T>(Vector2i topLeft, Vector2i size, ReadOnlySpan<T> buffer)
|
||||
{
|
||||
_clyde.SetSubImage(this, topLeft, size, buffer);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared;
|
||||
@@ -13,19 +13,24 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using static Robust.Client.Utility.LiterallyJustMessageBox;
|
||||
using static Robust.Client.Utility.Win32;
|
||||
using FrameEventArgs = Robust.Shared.Timing.FrameEventArgs;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
internal partial class Clyde
|
||||
{
|
||||
private readonly List<WindowReg> _windows = new();
|
||||
private readonly List<WindowHandle> _windowHandles = new();
|
||||
private readonly List<MonitorHandle> _monitorHandles = new();
|
||||
private readonly Dictionary<int, MonitorHandle> _monitorHandles = new();
|
||||
|
||||
private int _primaryMonitorId;
|
||||
private WindowReg? _mainWindow;
|
||||
|
||||
private IWindowingImpl? _windowing;
|
||||
private Renderer _chosenRenderer;
|
||||
|
||||
private ResourcePath? _windowIconPath;
|
||||
private Thread? _windowingThread;
|
||||
private bool _vSync;
|
||||
private WindowMode _windowMode;
|
||||
@@ -39,7 +44,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public event Action<KeyEventArgs>? KeyUp;
|
||||
public event Action<KeyEventArgs>? KeyDown;
|
||||
public event Action<MouseWheelEventArgs>? MouseWheel;
|
||||
public event Action<WindowClosedEventArgs>? CloseWindow;
|
||||
public event Action<WindowRequestClosedEventArgs>? CloseWindow;
|
||||
public event Action<WindowDestroyedEventArgs>? DestroyWindow;
|
||||
public event Action<WindowContentScaleEventArgs>? OnWindowScaleChanged;
|
||||
public event Action<WindowResizedEventArgs>? OnWindowResized;
|
||||
@@ -47,18 +52,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// NOTE: in engine we pretend the framebuffer size is the screen size..
|
||||
// For practical reasons like UI rendering.
|
||||
public IClydeWindow MainWindow => _windowing?.MainWindow?.Handle ??
|
||||
public IClydeWindow MainWindow => _mainWindow?.Handle ??
|
||||
throw new InvalidOperationException("Windowing is not initialized");
|
||||
|
||||
public Vector2i ScreenSize => _windowing?.MainWindow?.FramebufferSize ??
|
||||
public Vector2i ScreenSize => _mainWindow?.FramebufferSize ??
|
||||
throw new InvalidOperationException("Windowing is not initialized");
|
||||
|
||||
public bool IsFocused => _windowing?.MainWindow?.IsFocused ??
|
||||
public bool IsFocused => _mainWindow?.IsFocused ??
|
||||
throw new InvalidOperationException("Windowing is not initialized");
|
||||
|
||||
public IEnumerable<IClydeWindow> AllWindows => _windowHandles;
|
||||
|
||||
public Vector2 DefaultWindowScale => _windowing?.MainWindow?.WindowScale ??
|
||||
public Vector2 DefaultWindowScale => _mainWindow?.WindowScale ??
|
||||
throw new InvalidOperationException("Windowing is not initialized");
|
||||
|
||||
public ScreenCoordinates MouseScreenPosition
|
||||
@@ -82,11 +87,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public uint? GetX11WindowId()
|
||||
{
|
||||
return _windowing?.WindowGetX11Id(_windowing.MainWindow!) ?? null;
|
||||
return _windowing?.WindowGetX11Id(_mainWindow!) ?? null;
|
||||
}
|
||||
|
||||
private bool InitWindowing()
|
||||
{
|
||||
var iconPath = _cfg.GetCVar(CVars.DisplayWindowIconSet);
|
||||
if (!string.IsNullOrWhiteSpace(iconPath))
|
||||
_windowIconPath = new ResourcePath(iconPath);
|
||||
|
||||
_windowingThread = Thread.CurrentThread;
|
||||
|
||||
_windowing = new GlfwWindowingImpl(this);
|
||||
@@ -94,40 +103,93 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return _windowing.Init();
|
||||
}
|
||||
|
||||
private bool TryInitMainWindow(GLContextSpec? glSpec, [NotNullWhen(false)] out string? error)
|
||||
{
|
||||
DebugTools.AssertNotNull(_glContext);
|
||||
|
||||
var width = _cfg.GetCVar(CVars.DisplayWidth);
|
||||
var height = _cfg.GetCVar(CVars.DisplayHeight);
|
||||
var prevWidth = width;
|
||||
var prevHeight = height;
|
||||
|
||||
IClydeMonitor? monitor = null;
|
||||
var fullscreen = false;
|
||||
|
||||
if (_windowMode == WindowMode.Fullscreen)
|
||||
{
|
||||
monitor = _monitorHandles[_primaryMonitorId];
|
||||
width = monitor.Size.X;
|
||||
height = monitor.Size.Y;
|
||||
fullscreen = true;
|
||||
}
|
||||
|
||||
var parameters = new WindowCreateParameters
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
Monitor = monitor,
|
||||
Fullscreen = fullscreen
|
||||
};
|
||||
|
||||
var (reg, err) = SharedWindowCreate(glSpec, parameters, null, isMain: true);
|
||||
|
||||
if (reg == null)
|
||||
{
|
||||
error = err!;
|
||||
return false;
|
||||
}
|
||||
|
||||
DebugTools.Assert(reg.Id == WindowId.Main);
|
||||
|
||||
if (fullscreen)
|
||||
{
|
||||
reg.PrevWindowSize = (prevWidth, prevHeight);
|
||||
reg.PrevWindowPos = (50, 50);
|
||||
}
|
||||
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe bool InitMainWindowAndRenderer()
|
||||
{
|
||||
DebugTools.AssertNotNull(_windowing);
|
||||
DebugTools.AssertNotNull(_glContext);
|
||||
|
||||
_chosenRenderer = (Renderer) _cfg.GetCVar(CVars.DisplayRenderer);
|
||||
|
||||
var renderers = _chosenRenderer == Renderer.Default
|
||||
? stackalloc Renderer[]
|
||||
{
|
||||
Renderer.OpenGL33,
|
||||
Renderer.OpenGL31,
|
||||
Renderer.OpenGLES2
|
||||
}
|
||||
: stackalloc Renderer[] {_chosenRenderer};
|
||||
_chosenRenderer = Renderer.OpenGL;
|
||||
|
||||
var succeeded = false;
|
||||
string? lastError = null;
|
||||
foreach (var renderer in renderers)
|
||||
|
||||
if (_glContext!.RequireWindowGL)
|
||||
{
|
||||
if (!_windowing!.TryInitMainWindow(renderer, out lastError))
|
||||
var specs = _glContext!.SpecsToTry;
|
||||
|
||||
foreach (var glSpec in specs)
|
||||
{
|
||||
Logger.DebugS("clyde.win", $"{renderer} unsupported: {lastError}");
|
||||
continue;
|
||||
if (!TryInitMainWindow(glSpec, out lastError))
|
||||
{
|
||||
Logger.DebugS("clyde.win", $"OpenGL {glSpec.OpenGLVersion} unsupported: {lastError}");
|
||||
continue;
|
||||
}
|
||||
|
||||
succeeded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// We should have a main window now.
|
||||
DebugTools.AssertNotNull(_windowing.MainWindow);
|
||||
|
||||
succeeded = true;
|
||||
_chosenRenderer = renderer;
|
||||
_isGLES = _chosenRenderer == Renderer.OpenGLES2;
|
||||
_isCore = _chosenRenderer == Renderer.OpenGL33;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!TryInitMainWindow(null, out lastError))
|
||||
Logger.DebugS("clyde.win", $"Failed to create window: {lastError}");
|
||||
else
|
||||
succeeded = true;
|
||||
}
|
||||
|
||||
// We should have a main window now.
|
||||
DebugTools.AssertNotNull(_mainWindow);
|
||||
|
||||
// _openGLVersion must be set by _glContext.
|
||||
DebugTools.Assert(_openGLVersion != RendererOpenGLVersion.Auto);
|
||||
|
||||
if (!succeeded)
|
||||
{
|
||||
@@ -153,24 +215,28 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return false;
|
||||
}
|
||||
|
||||
_windowing!.GLInitMainContext(_isGLES);
|
||||
|
||||
UpdateMainWindowLoadedRtSize();
|
||||
|
||||
_windowing.GLMakeContextCurrent(_windowing.MainWindow!);
|
||||
InitOpenGL();
|
||||
|
||||
_sawmillOgl.Debug("Setting viewport and rendering splash...");
|
||||
|
||||
GL.Viewport(0, 0, ScreenSize.X, ScreenSize.Y);
|
||||
CheckGlError();
|
||||
|
||||
// Quickly do a render with _drawingSplash = true so the screen isn't blank.
|
||||
Render();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private IEnumerable<Image<Rgba32>> LoadWindowIcons()
|
||||
{
|
||||
if (OperatingSystem.IsMacOS())
|
||||
if (OperatingSystem.IsMacOS() || _windowIconPath == null)
|
||||
{
|
||||
// Does nothing on macOS so don't bother.
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var file in _resourceCache.ContentFindFiles("/Textures/Logo/icon"))
|
||||
foreach (var file in _resourceCache.ContentFindFiles(_windowIconPath))
|
||||
{
|
||||
if (file.Extension != "png")
|
||||
{
|
||||
@@ -190,31 +256,89 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public void SetWindowTitle(string title)
|
||||
{
|
||||
DebugTools.AssertNotNull(_windowing);
|
||||
DebugTools.AssertNotNull(_mainWindow);
|
||||
|
||||
_windowing!.WindowSetTitle(_windowing.MainWindow!, title);
|
||||
_windowing!.WindowSetTitle(_mainWindow!, title);
|
||||
}
|
||||
|
||||
public void SetWindowMonitor(IClydeMonitor monitor)
|
||||
{
|
||||
DebugTools.AssertNotNull(_windowing);
|
||||
DebugTools.AssertNotNull(_mainWindow);
|
||||
|
||||
var window = _windowing!.MainWindow!;
|
||||
|
||||
_windowing.WindowSetMonitor(window, monitor);
|
||||
_windowing!.WindowSetMonitor(_mainWindow!, monitor);
|
||||
}
|
||||
|
||||
public void RequestWindowAttention()
|
||||
{
|
||||
DebugTools.AssertNotNull(_windowing);
|
||||
DebugTools.AssertNotNull(_mainWindow);
|
||||
|
||||
_windowing!.WindowRequestAttention(_windowing.MainWindow!);
|
||||
_windowing!.WindowRequestAttention(_mainWindow!);
|
||||
}
|
||||
|
||||
public async Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters)
|
||||
public IClydeWindow CreateWindow(WindowCreateParameters parameters)
|
||||
{
|
||||
DebugTools.AssertNotNull(_windowing);
|
||||
DebugTools.AssertNotNull(_glContext);
|
||||
DebugTools.AssertNotNull(_mainWindow);
|
||||
|
||||
return await _windowing!.WindowCreate(parameters);
|
||||
var glSpec = _glContext!.GetNewWindowSpec();
|
||||
|
||||
_glContext.BeforeSharedWindowCreateUnbind();
|
||||
|
||||
var (reg, error) = SharedWindowCreate(
|
||||
glSpec,
|
||||
parameters,
|
||||
glSpec == null ? null : _mainWindow,
|
||||
isMain: false);
|
||||
|
||||
// Rebinding is handed by WindowCreated in the GL context.
|
||||
|
||||
if (error != null)
|
||||
throw new Exception(error);
|
||||
|
||||
return reg!.Handle;
|
||||
}
|
||||
|
||||
private (WindowReg?, string? error) SharedWindowCreate(
|
||||
GLContextSpec? glSpec,
|
||||
WindowCreateParameters parameters,
|
||||
WindowReg? share,
|
||||
bool isMain)
|
||||
{
|
||||
WindowReg? owner = null;
|
||||
if (parameters.Owner != null)
|
||||
owner = ((WindowHandle)parameters.Owner).Reg;
|
||||
|
||||
var (reg, error) = _windowing!.WindowCreate(glSpec, parameters, share, owner);
|
||||
|
||||
if (reg != null)
|
||||
{
|
||||
// Window init succeeded, do setup.
|
||||
reg.IsMainWindow = isMain;
|
||||
if (isMain)
|
||||
_mainWindow = reg;
|
||||
|
||||
_windows.Add(reg);
|
||||
_windowHandles.Add(reg.Handle);
|
||||
|
||||
var rtId = AllocRid();
|
||||
_renderTargets.Add(rtId, new LoadedRenderTarget
|
||||
{
|
||||
Size = reg.FramebufferSize,
|
||||
IsWindow = true,
|
||||
WindowId = reg.Id,
|
||||
IsSrgb = true
|
||||
});
|
||||
|
||||
reg.RenderTarget = new RenderWindow(this, rtId);
|
||||
|
||||
_glContext!.WindowCreated(glSpec, reg);
|
||||
}
|
||||
|
||||
// Pass through result whether successful or not, caller handles it.
|
||||
return (reg, error);
|
||||
}
|
||||
|
||||
private void DoDestroyWindow(WindowReg reg)
|
||||
@@ -222,8 +346,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (reg.IsMainWindow)
|
||||
throw new InvalidOperationException("Cannot destroy main window.");
|
||||
|
||||
if (reg.IsDisposed)
|
||||
return;
|
||||
|
||||
reg.IsDisposed = true;
|
||||
|
||||
_glContext!.WindowDestroyed(reg);
|
||||
_windowing!.WindowDestroy(reg);
|
||||
reg.BlitDoneEvent?.Set();
|
||||
|
||||
_windows.Remove(reg);
|
||||
_windowHandles.Remove(reg.Handle);
|
||||
|
||||
var destroyed = new WindowDestroyedEventArgs(reg.Handle);
|
||||
DestroyWindow?.Invoke(destroyed);
|
||||
reg.Closed?.Invoke(destroyed);
|
||||
}
|
||||
|
||||
public void ProcessInput(FrameEventArgs frameEventArgs)
|
||||
@@ -232,28 +368,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
DispatchEvents();
|
||||
}
|
||||
|
||||
private void SwapMainBuffers()
|
||||
private void SwapAllBuffers()
|
||||
{
|
||||
_windowing?.WindowSwapBuffers(_windowing.MainWindow!);
|
||||
_glContext?.SwapAllBuffers();
|
||||
}
|
||||
|
||||
private void VSyncChanged(bool newValue)
|
||||
{
|
||||
_vSync = newValue;
|
||||
_windowing?.UpdateVSync();
|
||||
}
|
||||
|
||||
private void CreateWindowRenderTexture(WindowReg reg)
|
||||
{
|
||||
reg.RenderTexture?.Dispose();
|
||||
|
||||
reg.RenderTexture = CreateRenderTarget(reg.FramebufferSize, new RenderTargetFormatParameters
|
||||
{
|
||||
ColorFormat = RenderTargetColorFormat.Rgba8Srgb,
|
||||
HasDepthStencil = true
|
||||
});
|
||||
// Necessary to correctly sync multi-context blitting.
|
||||
reg.RenderTexture.MakeGLFence = true;
|
||||
_glContext?.UpdateVSync();
|
||||
}
|
||||
|
||||
private void WindowModeChanged(int mode)
|
||||
@@ -264,17 +387,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
Task<string> IClipboardManager.GetText()
|
||||
{
|
||||
return _windowing?.ClipboardGetText() ?? Task.FromResult("");
|
||||
return _windowing?.ClipboardGetText(_mainWindow!) ?? Task.FromResult("");
|
||||
}
|
||||
|
||||
void IClipboardManager.SetText(string text)
|
||||
{
|
||||
_windowing?.ClipboardSetText(text);
|
||||
_windowing?.ClipboardSetText(_mainWindow!, text);
|
||||
}
|
||||
|
||||
public IEnumerable<IClydeMonitor> EnumerateMonitors()
|
||||
{
|
||||
return _monitorHandles;
|
||||
return _monitorHandles.Values;
|
||||
}
|
||||
|
||||
public ICursor GetStandardCursor(StandardCursorShape shape)
|
||||
@@ -295,7 +418,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
DebugTools.AssertNotNull(_windowing);
|
||||
|
||||
_windowing!.CursorSet(_windowing.MainWindow!, cursor);
|
||||
_windowing!.CursorSet(_mainWindow!, cursor);
|
||||
}
|
||||
|
||||
|
||||
@@ -306,68 +429,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_windowing!.WindowSetVisible(reg, visible);
|
||||
}
|
||||
|
||||
private void InitWindowBlitThread(WindowReg reg)
|
||||
{
|
||||
if (EffectiveThreadWindowBlit)
|
||||
{
|
||||
reg.BlitStartEvent = new ManualResetEventSlim();
|
||||
reg.BlitDoneEvent = new ManualResetEventSlim();
|
||||
reg.BlitThread = new Thread(() => BlitThread(reg))
|
||||
{
|
||||
Name = $"WinBlitThread ID:{reg.Id}",
|
||||
IsBackground = true
|
||||
};
|
||||
|
||||
// System.Console.WriteLine("A");
|
||||
reg.BlitThread.Start();
|
||||
// Wait for thread to finish init.
|
||||
reg.BlitDoneEvent.Wait();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Binds GL context.
|
||||
BlitThreadInit(reg);
|
||||
|
||||
_windowing!.GLMakeContextCurrent(_windowing.MainWindow!);
|
||||
}
|
||||
}
|
||||
|
||||
private void BlitThread(WindowReg reg)
|
||||
{
|
||||
BlitThreadInit(reg);
|
||||
|
||||
reg.BlitDoneEvent!.Set();
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
reg.BlitStartEvent!.Wait();
|
||||
if (reg.IsDisposed)
|
||||
{
|
||||
BlitThreadCleanup(reg);
|
||||
return;
|
||||
}
|
||||
|
||||
reg.BlitStartEvent!.Reset();
|
||||
|
||||
// Do channel blit.
|
||||
BlitThreadDoSecondaryWindowBlit(reg);
|
||||
}
|
||||
}
|
||||
catch (AggregateException e)
|
||||
{
|
||||
// ok channel closed, we exit.
|
||||
e.Handle(ec => ec is ChannelClosedException);
|
||||
}
|
||||
}
|
||||
|
||||
private static void BlitThreadCleanup(WindowReg reg)
|
||||
{
|
||||
reg.BlitDoneEvent!.Dispose();
|
||||
reg.BlitStartEvent!.Dispose();
|
||||
}
|
||||
|
||||
private abstract class WindowReg
|
||||
{
|
||||
public bool IsDisposed;
|
||||
@@ -385,21 +446,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public bool IsMinimized;
|
||||
public string Title = "";
|
||||
public bool IsVisible;
|
||||
public IClydeWindow? Owner;
|
||||
|
||||
public bool DisposeOnClose;
|
||||
|
||||
// Used EXCLUSIVELY to run the two rendering commands to blit to the window.
|
||||
public Thread? BlitThread;
|
||||
public ManualResetEventSlim? BlitStartEvent;
|
||||
public ManualResetEventSlim? BlitDoneEvent;
|
||||
|
||||
public bool IsMainWindow;
|
||||
public WindowHandle Handle = default!;
|
||||
public RenderTexture? RenderTexture;
|
||||
public Action<WindowClosedEventArgs>? Closed;
|
||||
public RenderWindow RenderTarget = default!;
|
||||
public Action<WindowRequestClosedEventArgs>? RequestClosed;
|
||||
public Action<WindowDestroyedEventArgs>? Closed;
|
||||
}
|
||||
|
||||
private sealed class WindowHandle : IClydeWindow
|
||||
private sealed class WindowHandle : IClydeWindowInternal
|
||||
{
|
||||
// So funny story
|
||||
// When this class was a record, the C# compiler on .NET 5 stack overflowed
|
||||
@@ -407,65 +465,62 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// VERY funny.
|
||||
|
||||
private readonly Clyde _clyde;
|
||||
private readonly WindowReg _reg;
|
||||
public readonly WindowReg Reg;
|
||||
|
||||
public bool IsDisposed => _reg.IsDisposed;
|
||||
public WindowId Id => _reg.Id;
|
||||
public bool IsDisposed => Reg.IsDisposed;
|
||||
public WindowId Id => Reg.Id;
|
||||
|
||||
public WindowHandle(Clyde clyde, WindowReg reg)
|
||||
{
|
||||
_clyde = clyde;
|
||||
_reg = reg;
|
||||
Reg = reg;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_clyde.DoDestroyWindow(_reg);
|
||||
_clyde.DoDestroyWindow(Reg);
|
||||
}
|
||||
|
||||
public Vector2i Size => _reg.FramebufferSize;
|
||||
public Vector2i Size => Reg.FramebufferSize;
|
||||
|
||||
public IRenderTarget RenderTarget
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_reg.IsMainWindow)
|
||||
{
|
||||
return _clyde._mainMainWindowRenderMainTarget;
|
||||
}
|
||||
|
||||
return _reg.RenderTexture!;
|
||||
}
|
||||
}
|
||||
public IRenderTarget RenderTarget => Reg.RenderTarget;
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => _reg.Title;
|
||||
set => _clyde._windowing!.WindowSetTitle(_reg, value);
|
||||
get => Reg.Title;
|
||||
set => _clyde._windowing!.WindowSetTitle(Reg, value);
|
||||
}
|
||||
|
||||
public bool IsFocused => _reg.IsFocused;
|
||||
public bool IsMinimized => _reg.IsMinimized;
|
||||
public bool IsFocused => Reg.IsFocused;
|
||||
public bool IsMinimized => Reg.IsMinimized;
|
||||
|
||||
public bool IsVisible
|
||||
{
|
||||
get => _reg.IsVisible;
|
||||
set => _clyde.SetWindowVisible(_reg, value);
|
||||
get => Reg.IsVisible;
|
||||
set => _clyde.SetWindowVisible(Reg, value);
|
||||
}
|
||||
|
||||
public Vector2 ContentScale => _reg.WindowScale;
|
||||
public Vector2 ContentScale => Reg.WindowScale;
|
||||
|
||||
public bool DisposeOnClose
|
||||
{
|
||||
get => _reg.DisposeOnClose;
|
||||
set => _reg.DisposeOnClose = value;
|
||||
get => Reg.DisposeOnClose;
|
||||
set => Reg.DisposeOnClose = value;
|
||||
}
|
||||
|
||||
public event Action<WindowClosedEventArgs> Closed
|
||||
public event Action<WindowRequestClosedEventArgs> RequestClosed
|
||||
{
|
||||
add => _reg.Closed += value;
|
||||
remove => _reg.Closed -= value;
|
||||
add => Reg.RequestClosed += value;
|
||||
remove => Reg.RequestClosed -= value;
|
||||
}
|
||||
|
||||
public event Action<WindowDestroyedEventArgs>? Destroyed
|
||||
{
|
||||
add => Reg.Closed += value;
|
||||
remove => Reg.Closed -= value;
|
||||
}
|
||||
|
||||
public nint? WindowsHWnd => _clyde._windowing!.WindowGetWin32Window(Reg);
|
||||
}
|
||||
|
||||
private sealed class MonitorHandle : IClydeMonitor
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using OpenToolkit;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Client.ResourceManagement;
|
||||
@@ -24,6 +25,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
internal sealed partial class Clyde : IClydeInternal, IClydeAudio, IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IClydeTileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly ILightManager _lightManager = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
@@ -64,21 +66,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private ISawmill _sawmillOgl = default!;
|
||||
|
||||
private IBindingsContext _glBindingsContext = default!;
|
||||
|
||||
public Clyde()
|
||||
{
|
||||
// Init main window render target.
|
||||
var windowRid = AllocRid();
|
||||
var window = new RenderMainWindow(this, windowRid);
|
||||
var loadedData = new LoadedRenderTarget
|
||||
{
|
||||
IsWindow = true,
|
||||
IsSrgb = true
|
||||
};
|
||||
_renderTargets.Add(windowRid, loadedData);
|
||||
|
||||
_mainMainWindowRenderMainTarget = window;
|
||||
_currentRenderTarget = RtToLoaded(window);
|
||||
_currentBoundRenderTarget = _currentRenderTarget;
|
||||
_currentBoundRenderTarget = default!;
|
||||
_currentRenderTarget = default!;
|
||||
}
|
||||
|
||||
public bool InitializePreWindowing()
|
||||
@@ -100,6 +93,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public bool InitializePostWindowing()
|
||||
{
|
||||
_gameThread = Thread.CurrentThread;
|
||||
|
||||
InitGLContextManager();
|
||||
if (!InitMainWindowAndRenderer())
|
||||
return false;
|
||||
|
||||
@@ -152,13 +147,31 @@ namespace Robust.Client.Graphics.Clyde
|
||||
RegisterBlockCVars();
|
||||
}
|
||||
|
||||
private void GLInitBindings(bool gles)
|
||||
{
|
||||
_glBindingsContext = _glContext!.BindingsContext;
|
||||
GL.LoadBindings(_glBindingsContext);
|
||||
|
||||
if (gles)
|
||||
{
|
||||
// On GLES we use some OES and KHR functions so make sure to initialize them.
|
||||
OpenToolkit.Graphics.ES20.GL.LoadBindings(_glBindingsContext);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitOpenGL()
|
||||
{
|
||||
_isGLES = _openGLVersion is RendererOpenGLVersion.GLES2 or RendererOpenGLVersion.GLES3;
|
||||
_isCore = _openGLVersion is RendererOpenGLVersion.GL33;
|
||||
|
||||
GLInitBindings(_isGLES);
|
||||
|
||||
var vendor = GL.GetString(StringName.Vendor);
|
||||
var renderer = GL.GetString(StringName.Renderer);
|
||||
var version = GL.GetString(StringName.Version);
|
||||
var major = GL.GetInteger(GetPName.MajorVersion);
|
||||
var minor = GL.GetInteger(GetPName.MinorVersion);
|
||||
// GLES2 doesn't allow you to query major/minor version. Seriously.
|
||||
var major = _openGLVersion == RendererOpenGLVersion.GLES2 ? 2 : GL.GetInteger(GetPName.MajorVersion);
|
||||
var minor = _openGLVersion == RendererOpenGLVersion.GLES2 ? 0 :GL.GetInteger(GetPName.MinorVersion);
|
||||
|
||||
_sawmillOgl.Debug("OpenGL Vendor: {0}", vendor);
|
||||
_sawmillOgl.Debug("OpenGL Renderer: {0}", renderer);
|
||||
@@ -182,7 +195,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
DebugInfo = new ClydeDebugInfo(glVersion, renderer, vendor, version, overrideVersion != null);
|
||||
|
||||
GL.Enable(EnableCap.Blend);
|
||||
if (_hasGLSrgb)
|
||||
if (_hasGLSrgb && !_isGLES)
|
||||
{
|
||||
GL.Enable(EnableCap.FramebufferSrgb);
|
||||
CheckGlError();
|
||||
@@ -221,14 +234,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_sawmillOgl.Debug("Setting up RenderHandle...");
|
||||
|
||||
_renderHandle = new RenderHandle(this);
|
||||
|
||||
_sawmillOgl.Debug("Setting viewport and rendering splash...");
|
||||
|
||||
GL.Viewport(0, 0, ScreenSize.X, ScreenSize.Y);
|
||||
CheckGlError();
|
||||
|
||||
// Quickly do a render with _drawingSplash = true so the screen isn't blank.
|
||||
Render();
|
||||
}
|
||||
|
||||
private (int major, int minor)? ParseGLOverrideVersion()
|
||||
@@ -324,7 +329,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
screenBufferHandle = new GLHandle(GL.GenTexture());
|
||||
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
|
||||
ApplySampleParameters(TextureSampleParameters.Default);
|
||||
ScreenBufferTexture = GenTexture(screenBufferHandle, _windowing!.MainWindow!.FramebufferSize, true, null, TexturePixelType.Rgba32);
|
||||
// TODO: This is atrocious and broken and awful why did I merge this
|
||||
ScreenBufferTexture = GenTexture(screenBufferHandle, (1920, 1080), true, null, TexturePixelType.Rgba32);
|
||||
}
|
||||
|
||||
private GLHandle MakeQuadVao()
|
||||
@@ -467,7 +473,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
[Conditional("DEBUG")]
|
||||
private void PushDebugGroupMaybe(string group)
|
||||
{
|
||||
if (!_hasGLKhrDebug)
|
||||
if (!_hasGLKhrDebug || true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -485,7 +491,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
[Conditional("DEBUG")]
|
||||
private void PopDebugGroupMaybe()
|
||||
{
|
||||
if (!_hasGLKhrDebug)
|
||||
if (!_hasGLKhrDebug || true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -502,6 +508,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
_glContext?.Shutdown();
|
||||
ShutdownWindowing();
|
||||
_shutdownAudio();
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public event Action<KeyEventArgs>? KeyUp { add { } remove { } }
|
||||
public event Action<KeyEventArgs>? KeyDown { add { } remove { } }
|
||||
public event Action<MouseWheelEventArgs>? MouseWheel { add { } remove { } }
|
||||
public event Action<WindowClosedEventArgs>? CloseWindow { add { } remove { } }
|
||||
public event Action<WindowRequestClosedEventArgs>? CloseWindow { add { } remove { } }
|
||||
public event Action<WindowDestroyedEventArgs>? DestroyWindow { add { } remove { } }
|
||||
|
||||
public Texture GetStockTexture(ClydeStockTexture stockTexture)
|
||||
@@ -210,7 +210,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
yield break;
|
||||
}
|
||||
|
||||
public Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters)
|
||||
public IClydeWindow CreateWindow(WindowCreateParameters parameters)
|
||||
{
|
||||
var window = new DummyWindow(CreateRenderTarget((123, 123), default))
|
||||
{
|
||||
@@ -218,7 +218,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
};
|
||||
_windows.Add(window);
|
||||
|
||||
return Task.FromResult<IClydeWindow>(window);
|
||||
return window;
|
||||
}
|
||||
|
||||
public ClydeHandle LoadShader(ParsedShader shader, string? name = null)
|
||||
@@ -334,6 +334,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetMaxDistance(float maxDistance)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetRolloffFactor(float rolloffFactor)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetReferenceDistance(float refDistance)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetOcclusion(float blocks)
|
||||
{
|
||||
// Nada.
|
||||
@@ -396,6 +411,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
// Just do nothing on mutate.
|
||||
}
|
||||
|
||||
public override void SetSubImage<T>(Vector2i topLeft, Vector2i size, ReadOnlySpan<T> buffer)
|
||||
{
|
||||
// Just do nothing on mutate.
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DummyShaderInstance : ShaderInstance
|
||||
@@ -605,7 +625,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public bool IsVisible { get; set; } = true;
|
||||
public Vector2 ContentScale => Vector2.One;
|
||||
public bool DisposeOnClose { get; set; }
|
||||
public event Action<WindowClosedEventArgs>? Closed { add { } remove { } }
|
||||
public event Action<WindowRequestClosedEventArgs>? RequestClosed { add { } remove { } }
|
||||
public event Action<WindowDestroyedEventArgs>? Destroyed;
|
||||
|
||||
public void MaximizeOnMonitor(IClydeMonitor monitor)
|
||||
{
|
||||
@@ -614,6 +635,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public void Dispose()
|
||||
{
|
||||
IsDisposed = true;
|
||||
|
||||
Destroyed?.Invoke(new WindowDestroyedEventArgs(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
140
Robust.Client/Graphics/Clyde/Egl.cs
Normal file
140
Robust.Client/Graphics/Clyde/Egl.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
/// <summary>
|
||||
/// Minimal ANGLE EGL API P/Invokes.
|
||||
/// </summary>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo")]
|
||||
internal static unsafe class Egl
|
||||
{
|
||||
// ANGLE exports all the functions we need directly on the dll. No need to do eglGetProcAddress for EGL itself.
|
||||
// Still need it for OpenGL functions, however.
|
||||
|
||||
private const string LibraryName = "libEGL.dll";
|
||||
|
||||
public const int EGL_FALSE = 0;
|
||||
public const int EGL_TRUE = 1;
|
||||
|
||||
public const int EGL_NONE = 0x3038;
|
||||
public const int EGL_VENDOR = 0x3053;
|
||||
public const int EGL_VERSION = 0x3054;
|
||||
public const int EGL_EXTENSIONS = 0x3055;
|
||||
public const int EGL_CONFIG_ID = 0x3028;
|
||||
public const int EGL_COLOR_BUFFER_TYPE = 0x303F;
|
||||
public const int EGL_SAMPLES = 0x3031;
|
||||
public const int EGL_SAMPLE_BUFFERS = 0x3032;
|
||||
public const int EGL_CONFIG_CAVEAT = 0x3027;
|
||||
public const int EGL_CONFORMANT = 0x3042;
|
||||
public const int EGL_NATIVE_VISUAL_ID = 0x302E;
|
||||
public const int EGL_SURFACE_TYPE = 0x3033;
|
||||
public const int EGL_ALPHA_SIZE = 0x3021;
|
||||
public const int EGL_BLUE_SIZE = 0x3022;
|
||||
public const int EGL_GREEN_SIZE = 0x3023;
|
||||
public const int EGL_RED_SIZE = 0x3024;
|
||||
public const int EGL_DEPTH_SIZE = 0x3025;
|
||||
public const int EGL_STENCIL_SIZE = 0x3026;
|
||||
public const int EGL_WINDOW_BIT = 0x0004;
|
||||
public const int EGL_OPENGL_ES_API = 0x30A0;
|
||||
public const int EGL_RENDERABLE_TYPE = 0x3040;
|
||||
public const int EGL_OPENGL_ES2_BIT = 0x00000004;
|
||||
public const int EGL_OPENGL_ES3_BIT = 0x00000040;
|
||||
public const int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
|
||||
public const int EGL_TEXTURE_FORMAT = 0x3080;
|
||||
public const int EGL_TEXTURE_RGBA = 0x305E;
|
||||
public const int EGL_TEXTURE_TARGET = 0x3081;
|
||||
public const int EGL_TEXTURE_2D = 0x305F;
|
||||
public const int EGL_GL_COLORSPACE = 0x309D;
|
||||
public const int EGL_GL_COLORSPACE_SRGB = 0x3089;
|
||||
public const int EGL_PLATFORM_ANGLE_ANGLE = 0x3202;
|
||||
|
||||
public const nint EGL_NO_CONTEXT = 0;
|
||||
public const nint EGL_NO_DEVICE_EXT = 0;
|
||||
public const nint EGL_NO_SURFACE = 0;
|
||||
|
||||
public const int EGL_D3D_TEXTURE_ANGLE = 0x33A3;
|
||||
public const int EGL_D3D11_DEVICE_ANGLE = 0x33A1;
|
||||
public const int EGL_PLATFORM_DEVICE_EXT = 0x313F;
|
||||
public const int EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE = 0x33A7;
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern int eglInitialize(void* display, int* major, int* minor);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern int eglTerminate(void* display);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern void* eglGetDisplay(void* display);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern void* eglGetPlatformDisplayEXT(int platform, void* native_display, nint* attrib_list);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern int eglBindAPI(int api);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern void* eglCreateDeviceANGLE(int device_type, void* native_device, nint* attrib_list);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern int eglReleaseDeviceANGLE(void* device);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern void* eglGetProcAddress(byte* procname);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern int eglChooseConfig(
|
||||
void* display,
|
||||
int* attrib_list,
|
||||
void** configs,
|
||||
int config_size,
|
||||
int* num_config);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern int eglGetConfigAttrib(void* display, void* config, int attribute, int* value);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern void* eglCreateContext(void* display, void* config, void* share_context, int* attrib_list);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern int eglGetError();
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern byte* eglQueryString(void* display, int name);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern void* eglCreatePbufferFromClientBuffer(
|
||||
void* display,
|
||||
int buftype,
|
||||
void* buffer,
|
||||
void* config,
|
||||
int* attrib_list);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern void* eglCreateWindowSurface(
|
||||
void* display,
|
||||
void* config,
|
||||
void* native_window,
|
||||
int* attrib_list);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern int eglMakeCurrent(
|
||||
void* display,
|
||||
void* draw,
|
||||
void* read,
|
||||
void* context);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern void* eglGetCurrentContext();
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern int eglSwapBuffers(void* display, void* surface);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern int eglSwapInterval(void* display, int interval);
|
||||
|
||||
[DllImport(LibraryName)]
|
||||
public static extern int eglDestroySurface(void* display, void* surface);
|
||||
}
|
||||
}
|
||||
530
Robust.Client/Graphics/Clyde/GLContext/GLContextAngle.cs
Normal file
530
Robust.Client/Graphics/Clyde/GLContext/GLContextAngle.cs
Normal file
@@ -0,0 +1,530 @@
|
||||
// Commented out because I can't be bothered to figure out trimming for TerraFX.
|
||||
/*
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using TerraFX.Interop;
|
||||
using static Robust.Client.Graphics.Clyde.Egl;
|
||||
using static TerraFX.Interop.D3D_DRIVER_TYPE;
|
||||
using static TerraFX.Interop.D3D_FEATURE_LEVEL;
|
||||
using static TerraFX.Interop.DXGI_FORMAT;
|
||||
using static TerraFX.Interop.DXGI_SWAP_EFFECT;
|
||||
using static TerraFX.Interop.Windows;
|
||||
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
/// <summary>
|
||||
/// Explicit ANGLE GL context with manual DXGI/D3D device and swap chain management.
|
||||
/// </summary>
|
||||
private sealed unsafe class GLContextAngle : GLContextBase
|
||||
{
|
||||
// Thanks to mpv's implementation of context_angle for inspiration/hints.
|
||||
// https://github.com/mpv-player/mpv/blob/f8e62d3d82dd0a3d06f9a557d756f0ad78118cc7/video/out/opengl/context_angle.c
|
||||
|
||||
// NOTE: This class only handles GLES3/D3D11.
|
||||
// For anything lower we just let ANGLE fall back and do the work 100%.
|
||||
|
||||
private IDXGIFactory1* _factory;
|
||||
private IDXGIAdapter1* _adapter;
|
||||
private ID3D11Device* _device;
|
||||
private ID3D11DeviceContext* _deviceContext;
|
||||
private D3D_FEATURE_LEVEL _deviceFl;
|
||||
private void* _eglDevice;
|
||||
private void* _eglDisplay;
|
||||
private void* _eglContext;
|
||||
private void* _eglConfig;
|
||||
|
||||
private bool _es3;
|
||||
private uint _swapInterval;
|
||||
|
||||
private readonly Dictionary<WindowId, WindowData> _windowData = new();
|
||||
|
||||
public override GLContextSpec[] SpecsToTry => Array.Empty<GLContextSpec>();
|
||||
public override bool RequireWindowGL => false;
|
||||
public override bool EarlyContextInit => true;
|
||||
public override bool HasBrokenWindowSrgb => false;
|
||||
|
||||
public GLContextAngle(Clyde clyde) : base(clyde)
|
||||
{
|
||||
}
|
||||
|
||||
public override GLContextSpec? SpecWithOpenGLVersion(RendererOpenGLVersion version)
|
||||
{
|
||||
// Do not initialize GL context on the window directly, we use ANGLE.
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void UpdateVSync()
|
||||
{
|
||||
_swapInterval = (uint) (Clyde._vSync ? 1 : 0);
|
||||
}
|
||||
|
||||
public override void WindowCreated(GLContextSpec? spec, WindowReg reg)
|
||||
{
|
||||
var data = new WindowData
|
||||
{
|
||||
Reg = reg
|
||||
};
|
||||
_windowData[reg.Id] = data;
|
||||
|
||||
var hWnd = Clyde._windowing!.WindowGetWin32Window(reg)!.Value;
|
||||
|
||||
// todo: exception management.
|
||||
CreateSwapChain1(hWnd, data);
|
||||
|
||||
_factory->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER);
|
||||
|
||||
var rt = Clyde.RtToLoaded(reg.RenderTarget);
|
||||
rt.FlipY = true;
|
||||
|
||||
if (reg.IsMainWindow)
|
||||
{
|
||||
UpdateVSync();
|
||||
eglMakeCurrent(_eglDisplay, data.EglBackbuffer, data.EglBackbuffer, _eglContext);
|
||||
}
|
||||
}
|
||||
|
||||
private void DestroyBackbuffer(WindowData data)
|
||||
{
|
||||
if (data.EglBackbuffer != null)
|
||||
{
|
||||
eglMakeCurrent(_eglDisplay, null, null, null);
|
||||
eglDestroySurface(_eglDisplay, data.EglBackbuffer);
|
||||
|
||||
data.EglBackbuffer = null;
|
||||
}
|
||||
|
||||
data.Backbuffer->Release();
|
||||
data.Backbuffer = null;
|
||||
}
|
||||
|
||||
private void SetupBackbuffer(WindowData data)
|
||||
{
|
||||
DebugTools.Assert(data.Backbuffer == null, "Backbuffer must have been released!");
|
||||
DebugTools.Assert(data.EglBackbuffer == null, "EGL Backbuffer must have been released!");
|
||||
|
||||
fixed (ID3D11Texture2D** texPtr = &data.Backbuffer)
|
||||
{
|
||||
var iid = IID_ID3D11Texture2D;
|
||||
ThrowIfFailed("GetBuffer", data.SwapChain->GetBuffer(0, &iid, (void**) texPtr));
|
||||
}
|
||||
|
||||
var attributes = stackalloc int[]
|
||||
{
|
||||
EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA,
|
||||
EGL_TEXTURE_TARGET, EGL_TEXTURE_2D,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
data.EglBackbuffer = eglCreatePbufferFromClientBuffer(
|
||||
_eglDisplay,
|
||||
EGL_D3D_TEXTURE_ANGLE,
|
||||
data.Backbuffer,
|
||||
_eglConfig,
|
||||
attributes);
|
||||
}
|
||||
|
||||
private void CreateSwapChain1(nint hWnd, WindowData data)
|
||||
{
|
||||
var desc = new DXGI_SWAP_CHAIN_DESC
|
||||
{
|
||||
BufferDesc =
|
||||
{
|
||||
Width = (uint) data.Reg.FramebufferSize.X,
|
||||
Height = (uint) data.Reg.FramebufferSize.Y,
|
||||
Format = Clyde._hasGLSrgb ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM
|
||||
},
|
||||
SampleDesc =
|
||||
{
|
||||
Count = 1
|
||||
},
|
||||
OutputWindow = hWnd,
|
||||
BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT,
|
||||
BufferCount = 2,
|
||||
SwapEffect = DXGI_SWAP_EFFECT_DISCARD,
|
||||
Windowed = 1
|
||||
};
|
||||
|
||||
fixed (IDXGISwapChain** swapPtr = &data.SwapChain)
|
||||
{
|
||||
ThrowIfFailed("CreateSwapChain", _factory->CreateSwapChain(
|
||||
(IUnknown*) _device,
|
||||
&desc,
|
||||
swapPtr
|
||||
));
|
||||
}
|
||||
|
||||
SetupBackbuffer(data);
|
||||
}
|
||||
|
||||
public override void WindowDestroyed(WindowReg reg)
|
||||
{
|
||||
var data = _windowData[reg.Id];
|
||||
|
||||
DestroyBackbuffer(data);
|
||||
data.SwapChain->Release();
|
||||
|
||||
_windowData.Remove(reg.Id);
|
||||
}
|
||||
|
||||
public bool TryInitialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
TryInitializeCore();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("clyde.ogl.angle", $"Failed to initialize custom ANGLE: {e}");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void EarlyInit()
|
||||
{
|
||||
// Early GL context init so that feature detection runs before window creation,
|
||||
// and so that we can know _hasGLSrgb in window creation.
|
||||
eglMakeCurrent(_eglDisplay, null, null, _eglContext);
|
||||
Clyde.InitOpenGL();
|
||||
}
|
||||
|
||||
private void TryInitializeCore()
|
||||
{
|
||||
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(null, EGL_EXTENSIONS));
|
||||
Logger.DebugS("clyde.ogl.angle", $"EGL client extensions: {extensions}!");
|
||||
|
||||
CreateD3D11Device();
|
||||
CreateEglContext();
|
||||
}
|
||||
|
||||
private void CreateEglContext()
|
||||
{
|
||||
_eglDevice = eglCreateDeviceANGLE(EGL_D3D11_DEVICE_ANGLE, _device, null);
|
||||
if (_eglDevice == (void*) EGL_NO_DEVICE_EXT)
|
||||
throw new Exception("eglCreateDeviceANGLE failed.");
|
||||
|
||||
_eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, _eglDevice, null);
|
||||
if (_eglDisplay == null)
|
||||
throw new Exception("eglGetPlatformDisplayEXT failed.");
|
||||
|
||||
int major;
|
||||
int minor;
|
||||
if (eglInitialize(_eglDisplay, &major, &minor) == EGL_FALSE)
|
||||
throw new Exception("eglInitialize failed.");
|
||||
|
||||
var vendor = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_VENDOR));
|
||||
var version = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_VERSION));
|
||||
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_EXTENSIONS));
|
||||
|
||||
Logger.DebugS("clyde.ogl.angle", "EGL initialized!");
|
||||
Logger.DebugS("clyde.ogl.angle", $"EGL vendor: {vendor}!");
|
||||
Logger.DebugS("clyde.ogl.angle", $"EGL version: {version}!");
|
||||
Logger.DebugS("clyde.ogl.angle", $"EGL extensions: {extensions}!");
|
||||
|
||||
if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE)
|
||||
throw new Exception("eglBindAPI failed.");
|
||||
|
||||
var attribs = stackalloc int[]
|
||||
{
|
||||
// EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_ALPHA_SIZE, 8,
|
||||
EGL_STENCIL_SIZE, 8,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
var numConfigs = 0;
|
||||
if (eglChooseConfig(_eglDisplay, attribs, null, 0, &numConfigs) == EGL_FALSE)
|
||||
throw new Exception("eglChooseConfig failed.");
|
||||
|
||||
var configs = stackalloc void*[numConfigs];
|
||||
if (eglChooseConfig(_eglDisplay, attribs, configs, numConfigs, &numConfigs) == EGL_FALSE)
|
||||
throw new Exception("eglChooseConfig failed.");
|
||||
|
||||
if (numConfigs == 0)
|
||||
throw new Exception("No compatible EGL configurations returned!");
|
||||
|
||||
Logger.DebugS("clyde.ogl.angle", $"{numConfigs} EGL configs possible!");
|
||||
|
||||
for (var i = 0; i < numConfigs; i++)
|
||||
{
|
||||
Logger.DebugS("clyde.ogl.angle", DumpEglConfig(_eglDisplay, configs[i]));
|
||||
}
|
||||
|
||||
_eglConfig = configs[0];
|
||||
|
||||
int supportedRenderableTypes;
|
||||
eglGetConfigAttrib(_eglDisplay, _eglConfig, EGL_RENDERABLE_TYPE, &supportedRenderableTypes);
|
||||
|
||||
_es3 = (supportedRenderableTypes & EGL_OPENGL_ES3_BIT) != 0;
|
||||
|
||||
var createAttribs = stackalloc int[]
|
||||
{
|
||||
EGL_CONTEXT_CLIENT_VERSION, _es3 ? 3 : 2,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
_eglContext = eglCreateContext(_eglDisplay, _eglConfig, null, createAttribs);
|
||||
if (_eglContext == (void*) EGL_NO_CONTEXT)
|
||||
throw new Exception("eglCreateContext failed!");
|
||||
|
||||
Logger.DebugS("clyde.ogl.angle", "EGL context created!");
|
||||
|
||||
Clyde._openGLVersion = _es3 ? RendererOpenGLVersion.GLES3 : RendererOpenGLVersion.GLES2;
|
||||
}
|
||||
|
||||
private void CreateD3D11Device()
|
||||
{
|
||||
IDXGIDevice1* dxgiDevice = null;
|
||||
|
||||
try
|
||||
{
|
||||
var iid = IID_IDXGIFactory1;
|
||||
|
||||
fixed (IDXGIFactory1** ptr = &_factory)
|
||||
{
|
||||
ThrowIfFailed(nameof(CreateDXGIFactory1), CreateDXGIFactory1(&iid, (void**) ptr));
|
||||
}
|
||||
|
||||
// Try to find the correct adapter if specified.
|
||||
var adapterName = Clyde._cfg.GetCVar(CVars.DisplayAdapter);
|
||||
|
||||
if (adapterName != "")
|
||||
{
|
||||
_adapter = TryFindAdapterWithName(adapterName);
|
||||
|
||||
if (_adapter == null)
|
||||
{
|
||||
Logger.WarningS("clyde.ogl.angle",
|
||||
$"Unable to find display adapter with requested name: {adapterName}");
|
||||
}
|
||||
|
||||
Logger.DebugS("clyde.ogl.angle", $"Found display adapter with name: {adapterName}");
|
||||
}
|
||||
|
||||
Span<D3D_FEATURE_LEVEL> featureLevels = stackalloc D3D_FEATURE_LEVEL[]
|
||||
{
|
||||
// 11_0 can do GLES3
|
||||
D3D_FEATURE_LEVEL_11_0,
|
||||
// 9_3 can do GLES2
|
||||
D3D_FEATURE_LEVEL_9_3,
|
||||
// If we get a 9_1 FL we can't do D3D11 based ANGLE,
|
||||
// but ANGLE can do it manually via the D3D9 renderer.
|
||||
// In this case, abort custom swap chain and let ANGLE handle everything.
|
||||
D3D_FEATURE_LEVEL_9_1
|
||||
};
|
||||
|
||||
fixed (ID3D11Device** device = &_device)
|
||||
fixed (D3D_FEATURE_LEVEL* fl = &featureLevels[0])
|
||||
{
|
||||
ThrowIfFailed("D3D11CreateDevice", D3D11CreateDevice(
|
||||
(IDXGIAdapter*) _adapter,
|
||||
_adapter == null ? D3D_DRIVER_TYPE_HARDWARE : D3D_DRIVER_TYPE_UNKNOWN,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
fl,
|
||||
(uint) featureLevels.Length,
|
||||
D3D11_SDK_VERSION,
|
||||
device,
|
||||
null,
|
||||
null
|
||||
));
|
||||
}
|
||||
|
||||
// Get adapter from the device.
|
||||
|
||||
iid = IID_IDXGIDevice1;
|
||||
ThrowIfFailed("QueryInterface", _device->QueryInterface(&iid, (void**) &dxgiDevice));
|
||||
|
||||
fixed (IDXGIAdapter1** ptrAdapter = &_adapter)
|
||||
{
|
||||
iid = IID_IDXGIAdapter1;
|
||||
ThrowIfFailed("GetParent", dxgiDevice->GetParent(&iid, (void**) ptrAdapter));
|
||||
}
|
||||
|
||||
_deviceFl = _device->GetFeatureLevel();
|
||||
|
||||
DXGI_ADAPTER_DESC1 desc;
|
||||
ThrowIfFailed("GetDesc1", _adapter->GetDesc1(&desc));
|
||||
|
||||
var descName = new ReadOnlySpan<char>(desc.Description, 128).TrimEnd('\0');
|
||||
|
||||
Logger.DebugS("clyde.ogl.angle", "Successfully created D3D11 device!");
|
||||
Logger.DebugS("clyde.ogl.angle", $"D3D11 Device Adapter: {descName.ToString()}");
|
||||
Logger.DebugS("clyde.ogl.angle", $"D3D11 Device FL: {_deviceFl}");
|
||||
|
||||
if (_deviceFl == D3D_FEATURE_LEVEL_9_1)
|
||||
{
|
||||
throw new Exception(
|
||||
"D3D11 device has too low FL (need at least 9_3). Aborting custom swap chain!");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (dxgiDevice != null)
|
||||
dxgiDevice->Release();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
// Shut down ANGLE.
|
||||
if (_eglDisplay != null)
|
||||
eglTerminate(_eglDisplay);
|
||||
|
||||
if (_eglDevice != null)
|
||||
eglReleaseDeviceANGLE(_eglDevice);
|
||||
|
||||
// Shut down D3D11/DXGI
|
||||
if (_factory != null)
|
||||
_factory->Release();
|
||||
|
||||
if (_adapter != null)
|
||||
_adapter->Release();
|
||||
|
||||
if (_device != null)
|
||||
_device->Release();
|
||||
}
|
||||
|
||||
public override void SwapAllBuffers()
|
||||
{
|
||||
foreach (var data in _windowData.Values)
|
||||
{
|
||||
data.SwapChain->Present(_swapInterval, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public override void WindowResized(WindowReg reg, Vector2i oldSize)
|
||||
{
|
||||
var data = _windowData[reg.Id];
|
||||
DestroyBackbuffer(data);
|
||||
|
||||
ThrowIfFailed("ResizeBuffers", data.SwapChain->ResizeBuffers(
|
||||
2,
|
||||
(uint) reg.FramebufferSize.X, (uint) reg.FramebufferSize.Y,
|
||||
Clyde._hasGLSrgb ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
0));
|
||||
|
||||
SetupBackbuffer(data);
|
||||
|
||||
if (reg.IsMainWindow)
|
||||
eglMakeCurrent(_eglDisplay, data.EglBackbuffer, data.EglBackbuffer, _eglContext);
|
||||
}
|
||||
|
||||
private IDXGIAdapter1* TryFindAdapterWithName(string name)
|
||||
{
|
||||
uint idx = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
IDXGIAdapter1* adapter;
|
||||
var hr = _factory->EnumAdapters1(idx++, &adapter);
|
||||
if (hr == DXGI_ERROR_NOT_FOUND)
|
||||
break;
|
||||
|
||||
ThrowIfFailed("EnumAdapters1", hr);
|
||||
|
||||
DXGI_ADAPTER_DESC1 desc;
|
||||
ThrowIfFailed("GetDesc1", adapter->GetDesc1(&desc));
|
||||
|
||||
var descName = new ReadOnlySpan<char>(desc.Description, 128);
|
||||
|
||||
if (descName.StartsWith(name))
|
||||
return adapter;
|
||||
|
||||
adapter->Release();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void* GetProcAddress(string name)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[128];
|
||||
var len = Encoding.UTF8.GetBytes(name, buf);
|
||||
buf[len] = 0;
|
||||
|
||||
fixed (byte* ptr = &buf[0])
|
||||
{
|
||||
return eglGetProcAddress(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
public override void BindWindowRenderTarget(WindowId rtWindowId)
|
||||
{
|
||||
var data = _windowData[rtWindowId];
|
||||
var result = eglMakeCurrent(_eglDisplay, data.EglBackbuffer, data.EglBackbuffer, _eglContext);
|
||||
if (result == EGL_FALSE)
|
||||
throw new Exception("eglMakeCurrent failed.");
|
||||
|
||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
|
||||
Clyde.CheckGlError();
|
||||
}
|
||||
|
||||
private static void ThrowIfFailed(string methodName, HRESULT hr)
|
||||
{
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
}
|
||||
}
|
||||
|
||||
private static string DumpEglConfig(void* display, void* config)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append($"cfg: {Get(EGL_CONFIG_ID):000} | ");
|
||||
sb.AppendFormat(
|
||||
"R/G/B/A/D/S: {0}/{1}/{2}/{3}/{4:00}/{5} | ",
|
||||
Get(EGL_RED_SIZE), Get(EGL_GREEN_SIZE), Get(EGL_BLUE_SIZE), Get(EGL_ALPHA_SIZE),
|
||||
Get(EGL_DEPTH_SIZE), Get(EGL_STENCIL_SIZE));
|
||||
|
||||
// COLOR_BUFFER_TYPE
|
||||
sb.Append($"CBT: {Get(EGL_COLOR_BUFFER_TYPE)} | ");
|
||||
sb.Append($"CC: {Get(EGL_CONFIG_CAVEAT)} | ");
|
||||
sb.Append($"CONF: {Get(EGL_CONFORMANT)} | ");
|
||||
sb.Append($"NAT: {Get(EGL_NATIVE_VISUAL_ID)} | ");
|
||||
sb.Append($"SAMPLES: {Get(EGL_SAMPLES)} | ");
|
||||
sb.Append($"SAMPLE_BUFFERS: {Get(EGL_SAMPLE_BUFFERS)} | ");
|
||||
sb.Append($"ORIENTATION: {Get(EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE)} | ");
|
||||
sb.Append($"RENDERABLE: {Get(EGL_RENDERABLE_TYPE)}");
|
||||
|
||||
return sb.ToString();
|
||||
|
||||
int Get(int attrib)
|
||||
{
|
||||
int value;
|
||||
if (eglGetConfigAttrib(display, config, attrib, &value) == EGL_FALSE)
|
||||
throw new Exception("eglGetConfigAttrib failed!");
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class WindowData
|
||||
{
|
||||
public WindowReg Reg = default!;
|
||||
|
||||
public IDXGISwapChain* SwapChain;
|
||||
public ID3D11Texture2D* Backbuffer;
|
||||
public void* EglBackbuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
118
Robust.Client/Graphics/Clyde/GLContext/GLContextBase.cs
Normal file
118
Robust.Client/Graphics/Clyde/GLContext/GLContextBase.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using OpenToolkit;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages OpenGL contexts for the windowing system.
|
||||
/// </summary>
|
||||
private abstract class GLContextBase
|
||||
{
|
||||
protected readonly Clyde Clyde;
|
||||
|
||||
public IBindingsContext BindingsContext { get; }
|
||||
|
||||
public GLContextBase(Clyde clyde)
|
||||
{
|
||||
Clyde = clyde;
|
||||
BindingsContext = new BindingsContextImpl(this);
|
||||
}
|
||||
|
||||
public GLContextSpec? GetNewWindowSpec()
|
||||
{
|
||||
return SpecWithOpenGLVersion(Clyde._openGLVersion);
|
||||
}
|
||||
|
||||
public virtual bool EarlyContextInit => false;
|
||||
|
||||
public abstract GLContextSpec? SpecWithOpenGLVersion(RendererOpenGLVersion version);
|
||||
|
||||
public abstract void UpdateVSync();
|
||||
public abstract void WindowCreated(GLContextSpec? spec, WindowReg reg);
|
||||
public abstract void WindowDestroyed(WindowReg reg);
|
||||
|
||||
public abstract void Shutdown();
|
||||
|
||||
public abstract GLContextSpec[] SpecsToTry { get; }
|
||||
public abstract bool RequireWindowGL { get; }
|
||||
public abstract bool HasBrokenWindowSrgb { get; }
|
||||
|
||||
protected static GLContextSpec GetVersionSpec(RendererOpenGLVersion version)
|
||||
{
|
||||
var spec = new GLContextSpec { OpenGLVersion = version };
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case RendererOpenGLVersion.GL33:
|
||||
spec.Major = 3;
|
||||
spec.Minor = 3;
|
||||
spec.Profile = GLContextProfile.Core;
|
||||
spec.CreationApi = GLContextCreationApi.Native;
|
||||
break;
|
||||
|
||||
case RendererOpenGLVersion.GL31:
|
||||
spec.Major = 3;
|
||||
spec.Minor = 1;
|
||||
spec.Profile = GLContextProfile.Compatibility;
|
||||
spec.CreationApi = GLContextCreationApi.Native;
|
||||
break;
|
||||
|
||||
case RendererOpenGLVersion.GLES3:
|
||||
spec.Major = 3;
|
||||
spec.Minor = 0;
|
||||
spec.Profile = GLContextProfile.Es;
|
||||
// Initializing ES on Windows EGL so that we can use ANGLE.
|
||||
spec.CreationApi = OperatingSystem.IsWindows()
|
||||
? GLContextCreationApi.Egl
|
||||
: GLContextCreationApi.Native;
|
||||
break;
|
||||
|
||||
case RendererOpenGLVersion.GLES2:
|
||||
spec.Major = 2;
|
||||
spec.Minor = 0;
|
||||
spec.Profile = GLContextProfile.Es;
|
||||
// Initializing ES on Windows EGL so that we can use ANGLE.
|
||||
spec.CreationApi = OperatingSystem.IsWindows()
|
||||
? GLContextCreationApi.Egl
|
||||
: GLContextCreationApi.Native;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
return spec;
|
||||
}
|
||||
|
||||
public abstract void SwapAllBuffers();
|
||||
public abstract void WindowResized(WindowReg reg, Vector2i oldSize);
|
||||
|
||||
public abstract unsafe void* GetProcAddress(string name);
|
||||
|
||||
public abstract void BindWindowRenderTarget(WindowId rtWindowId);
|
||||
|
||||
public virtual void BeforeSharedWindowCreateUnbind()
|
||||
{
|
||||
}
|
||||
|
||||
private sealed class BindingsContextImpl : IBindingsContext
|
||||
{
|
||||
private readonly GLContextBase _context;
|
||||
|
||||
public BindingsContextImpl(GLContextBase context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public unsafe IntPtr GetProcAddress(string procName)
|
||||
{
|
||||
return (nint)_context.GetProcAddress(procName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
296
Robust.Client/Graphics/Clyde/GLContext/GLContextEgl.cs
Normal file
296
Robust.Client/Graphics/Clyde/GLContext/GLContextEgl.cs
Normal file
@@ -0,0 +1,296 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using OpenToolkit;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using static Robust.Client.Graphics.Clyde.Egl;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
/// <summary>
|
||||
/// Context manager that uses EGL directly so that we get better control over multi-window management.
|
||||
/// </summary>
|
||||
private sealed unsafe class GLContextEgl : GLContextBase
|
||||
{
|
||||
// TODO: Currently this class uses ANGLE and Windows-specific initialization code.
|
||||
// It could be made more general purpose later if anybody ever gets adventurous with like, Wayland.
|
||||
|
||||
private readonly Dictionary<WindowId, WindowData> _windowData = new();
|
||||
|
||||
private void* _eglDisplay;
|
||||
private void* _eglContext;
|
||||
private void* _eglConfig;
|
||||
|
||||
public override bool HasBrokenWindowSrgb => Clyde._isGLES && OperatingSystem.IsWindows();
|
||||
|
||||
public GLContextEgl(Clyde clyde) : base(clyde)
|
||||
{
|
||||
}
|
||||
|
||||
public override GLContextSpec? SpecWithOpenGLVersion(RendererOpenGLVersion version)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void UpdateVSync()
|
||||
{
|
||||
var interval = Clyde._vSync ? 1 : 0;
|
||||
|
||||
eglSwapInterval(_eglDisplay, interval);
|
||||
}
|
||||
|
||||
public void InitializePublic()
|
||||
{
|
||||
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(null, EGL_EXTENSIONS));
|
||||
Logger.DebugS("clyde.ogl.egl", $"EGL client extensions: {extensions}!");
|
||||
}
|
||||
|
||||
public override void WindowCreated(GLContextSpec? spec, WindowReg reg)
|
||||
{
|
||||
var data = new WindowData
|
||||
{
|
||||
Reg = reg
|
||||
};
|
||||
_windowData[reg.Id] = data;
|
||||
|
||||
if (reg.IsMainWindow)
|
||||
Initialize(data);
|
||||
|
||||
var attribs = stackalloc int[]
|
||||
{
|
||||
EGL_GL_COLORSPACE, EGL_GL_COLORSPACE_SRGB,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
// Set up window surface.
|
||||
var hWNd = Clyde._windowing!.WindowGetWin32Window(reg)!.Value;
|
||||
data.EglSurface = eglCreateWindowSurface(_eglDisplay, _eglConfig, (void*) hWNd, attribs);
|
||||
if (data.EglSurface == (void*) EGL_NO_SURFACE)
|
||||
throw new Exception("eglCreateWindowSurface failed.");
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
var window = Clyde._windowing!.WindowGetX11Id(reg)!.Value;
|
||||
data.EglSurface = eglCreateWindowSurface(_eglDisplay, _eglConfig, (void*) window, attribs);
|
||||
if (data.EglSurface == (void*) EGL_NO_SURFACE)
|
||||
throw new Exception("eglCreateWindowSurface failed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("EGL is not currently supported outside Windows ANGLE or X11 Linux");
|
||||
}
|
||||
|
||||
if (reg.IsMainWindow)
|
||||
{
|
||||
var result = eglMakeCurrent(_eglDisplay, data.EglSurface, data.EglSurface, _eglContext);
|
||||
if (result == EGL_FALSE)
|
||||
throw new Exception("eglMakeCurrent failed.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void WindowDestroyed(WindowReg reg)
|
||||
{
|
||||
var data = _windowData[reg.Id];
|
||||
eglDestroySurface(_eglDisplay, data.EglSurface);
|
||||
}
|
||||
|
||||
private void Initialize(WindowData mainWindow)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
// Setting up ANGLE without manually selecting a D3D11 device requires a windows DC.
|
||||
mainWindow.DC = GetDC(Clyde._windowing!.WindowGetWin32Window(mainWindow.Reg)!.Value);
|
||||
|
||||
_eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, (void*) mainWindow.DC, null);
|
||||
if (_eglDisplay == null)
|
||||
throw new Exception("eglGetPlatformDisplayEXT failed.");
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
var xDisplay = Clyde._windowing!.WindowGetX11Display(mainWindow.Reg)!.Value;
|
||||
_eglDisplay = eglGetDisplay((void*) xDisplay);
|
||||
if (mainWindow.EglSurface == (void*) EGL_NO_SURFACE)
|
||||
throw new Exception("eglCreateWindowSurface failed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("EGL is not currently supported outside Windows ANGLE or X11 Linux");
|
||||
}
|
||||
|
||||
int major;
|
||||
int minor;
|
||||
if (eglInitialize(_eglDisplay, &major, &minor) == EGL_FALSE)
|
||||
throw new Exception("eglInitialize failed.");
|
||||
|
||||
var vendor = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_VENDOR));
|
||||
var version = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_VERSION));
|
||||
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_EXTENSIONS));
|
||||
|
||||
Logger.DebugS("clyde.ogl.egl", "EGL initialized!");
|
||||
Logger.DebugS("clyde.ogl.egl", $"EGL vendor: {vendor}!");
|
||||
Logger.DebugS("clyde.ogl.egl", $"EGL version: {version}!");
|
||||
Logger.DebugS("clyde.ogl.egl", $"EGL extensions: {extensions}!");
|
||||
|
||||
if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE)
|
||||
throw new Exception("eglBindAPI failed.");
|
||||
|
||||
var attribs = stackalloc int[]
|
||||
{
|
||||
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_ALPHA_SIZE, 8,
|
||||
EGL_STENCIL_SIZE, 8,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
var numConfigs = 0;
|
||||
if (eglChooseConfig(_eglDisplay, attribs, null, 0, &numConfigs) == EGL_FALSE)
|
||||
throw new Exception("eglChooseConfig failed.");
|
||||
|
||||
var configs = stackalloc void*[numConfigs];
|
||||
if (eglChooseConfig(_eglDisplay, attribs, configs, numConfigs, &numConfigs) == EGL_FALSE)
|
||||
throw new Exception("eglChooseConfig failed.");
|
||||
|
||||
if (numConfigs == 0)
|
||||
throw new Exception("No compatible EGL configurations returned!");
|
||||
|
||||
Logger.DebugS("clyde.ogl.egl", $"{numConfigs} EGL configs possible!");
|
||||
|
||||
for (var i = 0; i < numConfigs; i++)
|
||||
{
|
||||
Logger.DebugS("clyde.ogl.egl", DumpEglConfig(_eglDisplay, configs[i]));
|
||||
}
|
||||
|
||||
_eglConfig = configs[0];
|
||||
|
||||
var createAttribs = stackalloc int[]
|
||||
{
|
||||
EGL_CONTEXT_CLIENT_VERSION, 3,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
_eglContext = eglCreateContext(_eglDisplay, _eglConfig, null, createAttribs);
|
||||
if (_eglContext == (void*) EGL_NO_CONTEXT)
|
||||
throw new Exception("eglCreateContext failed!");
|
||||
|
||||
Logger.DebugS("clyde.ogl.egl", "EGL context created!");
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
if (_eglDisplay != null)
|
||||
{
|
||||
eglMakeCurrent(_eglDisplay, null, null, null);
|
||||
eglTerminate(_eglDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
public override GLContextSpec[] SpecsToTry => Array.Empty<GLContextSpec>();
|
||||
public override bool RequireWindowGL => false;
|
||||
|
||||
public override void SwapAllBuffers()
|
||||
{
|
||||
foreach (var data in _windowData.Values)
|
||||
{
|
||||
eglSwapBuffers(_eglDisplay, data.EglSurface);
|
||||
}
|
||||
}
|
||||
|
||||
public override void WindowResized(WindowReg reg, Vector2i oldSize)
|
||||
{
|
||||
// Nada..?
|
||||
}
|
||||
|
||||
public override void* GetProcAddress(string name)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[128];
|
||||
var len = Encoding.UTF8.GetBytes(name, buf);
|
||||
buf[len] = 0;
|
||||
|
||||
fixed (byte* ptr = &buf[0])
|
||||
{
|
||||
return eglGetProcAddress(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
public override void BindWindowRenderTarget(WindowId rtWindowId)
|
||||
{
|
||||
var data = _windowData[rtWindowId];
|
||||
var result = eglMakeCurrent(_eglDisplay, data.EglSurface, data.EglSurface, _eglContext);
|
||||
if (result == EGL_FALSE)
|
||||
throw new Exception("eglMakeCurrent failed.");
|
||||
}
|
||||
|
||||
private static string DumpEglConfig(void* display, void* config)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append($"cfg: {Get(EGL_CONFIG_ID):00} | ");
|
||||
sb.AppendFormat(
|
||||
"R/G/B/A/D/S: {0}/{1}/{2}/{3}/{4:00}/{5} | ",
|
||||
Get(EGL_RED_SIZE), Get(EGL_GREEN_SIZE), Get(EGL_BLUE_SIZE), Get(EGL_ALPHA_SIZE),
|
||||
Get(EGL_DEPTH_SIZE), Get(EGL_STENCIL_SIZE));
|
||||
|
||||
// COLOR_BUFFER_TYPE
|
||||
sb.Append($"CBT: {Get(EGL_COLOR_BUFFER_TYPE)} | ");
|
||||
sb.Append($"CC: {Get(EGL_CONFIG_CAVEAT)} | ");
|
||||
sb.Append($"CONF: {Get(EGL_CONFORMANT)} | ");
|
||||
sb.Append($"NAT: {Get(EGL_NATIVE_VISUAL_ID)} | ");
|
||||
sb.Append($"SAMPLES: {Get(EGL_SAMPLES)} | ");
|
||||
sb.Append($"SAMPLE_BUFFERS: {Get(EGL_SAMPLE_BUFFERS)}");
|
||||
|
||||
return sb.ToString();
|
||||
|
||||
int Get(int attrib)
|
||||
{
|
||||
int value;
|
||||
if (eglGetConfigAttrib(display, config, attrib, &value) == EGL_FALSE)
|
||||
throw new Exception("eglGetConfigAttrib failed!");
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class WindowData
|
||||
{
|
||||
public WindowReg Reg = default!;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
// Windows DC for this window.
|
||||
// Only used for main window.
|
||||
public nint DC;
|
||||
|
||||
public void* EglSurface;
|
||||
}
|
||||
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern nint GetDC(nint hWnd);
|
||||
|
||||
private sealed class EglBindingsContext : IBindingsContext
|
||||
{
|
||||
public IntPtr GetProcAddress(string procName)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[128];
|
||||
buf.Clear();
|
||||
Encoding.UTF8.GetBytes(procName, buf);
|
||||
|
||||
fixed (byte* b = &buf.GetPinnableReference())
|
||||
{
|
||||
return (nint) eglGetProcAddress(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
341
Robust.Client/Graphics/Clyde/GLContext/GLContextWindow.cs
Normal file
341
Robust.Client/Graphics/Clyde/GLContext/GLContextWindow.cs
Normal file
@@ -0,0 +1,341 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
/// <summary>
|
||||
/// GL Context(s) provided by the windowing system (GLFW, SDL2...)
|
||||
/// </summary>
|
||||
private sealed class GLContextWindow : GLContextBase
|
||||
{
|
||||
private readonly Dictionary<WindowId, WindowData> _windowData = new();
|
||||
|
||||
public override GLContextSpec[] SpecsToTry
|
||||
{
|
||||
get
|
||||
{
|
||||
// Compat mode: only GLES2.
|
||||
if (Clyde._cfg.GetCVar(CVars.DisplayCompat))
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
GetVersionSpec(RendererOpenGLVersion.GLES3),
|
||||
GetVersionSpec(RendererOpenGLVersion.GLES2)
|
||||
};
|
||||
}
|
||||
|
||||
var requestedVersion = (RendererOpenGLVersion) Clyde._cfg.GetCVar(CVars.DisplayOpenGLVersion);
|
||||
if (requestedVersion != RendererOpenGLVersion.Auto)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
GetVersionSpec(requestedVersion)
|
||||
};
|
||||
}
|
||||
|
||||
return new[]
|
||||
{
|
||||
GetVersionSpec(RendererOpenGLVersion.GL33),
|
||||
GetVersionSpec(RendererOpenGLVersion.GL31),
|
||||
GetVersionSpec(RendererOpenGLVersion.GLES3),
|
||||
GetVersionSpec(RendererOpenGLVersion.GLES2),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override bool RequireWindowGL => true;
|
||||
// ANGLE does not support main window sRGB.
|
||||
public override bool HasBrokenWindowSrgb => Clyde._isGLES && OperatingSystem.IsWindows();
|
||||
|
||||
public GLContextWindow(Clyde clyde) : base(clyde)
|
||||
{
|
||||
}
|
||||
|
||||
public override GLContextSpec? SpecWithOpenGLVersion(RendererOpenGLVersion version)
|
||||
{
|
||||
return GetVersionSpec(version);
|
||||
}
|
||||
|
||||
public override void UpdateVSync()
|
||||
{
|
||||
if (Clyde._mainWindow == null)
|
||||
return;
|
||||
|
||||
Clyde._windowing!.GLMakeContextCurrent(Clyde._mainWindow);
|
||||
Clyde._windowing.GLSwapInterval(Clyde._vSync ? 1 : 0);
|
||||
}
|
||||
|
||||
public override void WindowCreated(GLContextSpec? spec, WindowReg reg)
|
||||
{
|
||||
reg.RenderTarget.MakeGLFence = true;
|
||||
|
||||
var data = new WindowData
|
||||
{
|
||||
Reg = reg
|
||||
};
|
||||
|
||||
_windowData[reg.Id] = data;
|
||||
|
||||
if (reg.IsMainWindow)
|
||||
{
|
||||
Clyde._openGLVersion = spec!.Value.OpenGLVersion;
|
||||
UpdateVSync();
|
||||
}
|
||||
else
|
||||
{
|
||||
Clyde._windowing!.GLMakeContextCurrent(Clyde._mainWindow);
|
||||
|
||||
CreateWindowRenderTexture(data);
|
||||
InitWindowBlitThread(data);
|
||||
}
|
||||
}
|
||||
|
||||
public override void WindowDestroyed(WindowReg reg)
|
||||
{
|
||||
var data = _windowData[reg.Id];
|
||||
data.BlitDoneEvent?.Set();
|
||||
|
||||
_windowData.Remove(reg.Id);
|
||||
}
|
||||
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
// Nada, window system shutdown handles it.
|
||||
}
|
||||
|
||||
public override void SwapAllBuffers()
|
||||
{
|
||||
BlitSecondaryWindows();
|
||||
|
||||
Clyde._windowing!.WindowSwapBuffers(Clyde._mainWindow!);
|
||||
}
|
||||
|
||||
public override void WindowResized(WindowReg reg, Vector2i oldSize)
|
||||
{
|
||||
if (reg.IsMainWindow)
|
||||
return;
|
||||
|
||||
// Recreate render texture for the window.
|
||||
var data = _windowData[reg.Id];
|
||||
data.RenderTexture!.Dispose();
|
||||
CreateWindowRenderTexture(data);
|
||||
}
|
||||
|
||||
public override unsafe void* GetProcAddress(string name)
|
||||
{
|
||||
return Clyde._windowing!.GLGetProcAddress(name);
|
||||
}
|
||||
|
||||
public override void BindWindowRenderTarget(WindowId rtWindowId)
|
||||
{
|
||||
var data = _windowData[rtWindowId];
|
||||
if (data.Reg.IsMainWindow)
|
||||
{
|
||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
|
||||
Clyde.CheckGlError();
|
||||
}
|
||||
else
|
||||
{
|
||||
var loaded = Clyde.RtToLoaded(data.RenderTexture!);
|
||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, loaded.FramebufferHandle.Handle);
|
||||
}
|
||||
}
|
||||
|
||||
public override void BeforeSharedWindowCreateUnbind()
|
||||
{
|
||||
Clyde._windowing!.GLMakeContextCurrent(null);
|
||||
}
|
||||
|
||||
private void BlitSecondaryWindows()
|
||||
{
|
||||
// Only got main window.
|
||||
if (Clyde._windows.Count == 1)
|
||||
return;
|
||||
|
||||
if (!Clyde._hasGLFenceSync && Clyde._cfg.GetCVar(CVars.DisplayForceSyncWindows))
|
||||
{
|
||||
GL.Finish();
|
||||
}
|
||||
|
||||
if (Clyde.EffectiveThreadWindowBlit)
|
||||
{
|
||||
foreach (var window in _windowData.Values)
|
||||
{
|
||||
if (window.Reg.IsMainWindow)
|
||||
continue;
|
||||
|
||||
window.BlitDoneEvent!.Reset();
|
||||
window.BlitStartEvent!.Set();
|
||||
window.BlitDoneEvent.Wait();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var window in _windowData.Values)
|
||||
{
|
||||
if (window.Reg.IsMainWindow)
|
||||
continue;
|
||||
|
||||
Clyde._windowing!.GLMakeContextCurrent(window.Reg);
|
||||
BlitThreadDoSecondaryWindowBlit(window);
|
||||
}
|
||||
|
||||
Clyde._windowing!.GLMakeContextCurrent(Clyde._mainWindow!);
|
||||
}
|
||||
}
|
||||
|
||||
private void BlitThreadDoSecondaryWindowBlit(WindowData window)
|
||||
{
|
||||
var rt = window.RenderTexture!;
|
||||
|
||||
if (Clyde._hasGLFenceSync)
|
||||
{
|
||||
// 0xFFFFFFFFFFFFFFFFUL is GL_TIMEOUT_IGNORED
|
||||
var sync = rt.LastGLSync;
|
||||
GL.WaitSync(sync, WaitSyncFlags.None, unchecked((long) 0xFFFFFFFFFFFFFFFFUL));
|
||||
Clyde.CheckGlError();
|
||||
}
|
||||
|
||||
GL.Viewport(0, 0, window.Reg.FramebufferSize.X, window.Reg.FramebufferSize.Y);
|
||||
Clyde.CheckGlError();
|
||||
|
||||
Clyde.SetTexture(TextureUnit.Texture0, window.RenderTexture!.Texture);
|
||||
|
||||
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
|
||||
Clyde.CheckGlError();
|
||||
|
||||
window.BlitDoneEvent?.Set();
|
||||
Clyde._windowing!.WindowSwapBuffers(window.Reg);
|
||||
}
|
||||
|
||||
private void BlitThreadInit(WindowData reg)
|
||||
{
|
||||
Clyde._windowing!.GLMakeContextCurrent(reg.Reg);
|
||||
Clyde._windowing.GLSwapInterval(0);
|
||||
|
||||
if (!Clyde._isGLES)
|
||||
GL.Enable(EnableCap.FramebufferSrgb);
|
||||
|
||||
var vao = GL.GenVertexArray();
|
||||
GL.BindVertexArray(vao);
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, Clyde.WindowVBO.ObjectHandle);
|
||||
// Vertex Coords
|
||||
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 0);
|
||||
GL.EnableVertexAttribArray(0);
|
||||
// Texture Coords.
|
||||
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 2 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(1);
|
||||
|
||||
var program = Clyde._compileProgram(
|
||||
Clyde._winBlitShaderVert,
|
||||
Clyde._winBlitShaderFrag,
|
||||
new (string, uint)[]
|
||||
{
|
||||
("aPos", 0),
|
||||
("tCoord", 1),
|
||||
},
|
||||
includeLib: false);
|
||||
|
||||
GL.UseProgram(program.Handle);
|
||||
var loc = GL.GetUniformLocation(program.Handle, "tex");
|
||||
Clyde.SetTexture(TextureUnit.Texture0, reg.RenderTexture!.Texture);
|
||||
GL.Uniform1(loc, 0);
|
||||
}
|
||||
|
||||
private void InitWindowBlitThread(WindowData reg)
|
||||
{
|
||||
if (Clyde.EffectiveThreadWindowBlit)
|
||||
{
|
||||
reg.BlitStartEvent = new ManualResetEventSlim();
|
||||
reg.BlitDoneEvent = new ManualResetEventSlim();
|
||||
reg.BlitThread = new Thread(() => BlitThread(reg))
|
||||
{
|
||||
Name = $"WinBlitThread ID:{reg.Reg.Id}",
|
||||
IsBackground = true
|
||||
};
|
||||
|
||||
// System.Console.WriteLine("A");
|
||||
reg.BlitThread.Start();
|
||||
// Wait for thread to finish init.
|
||||
reg.BlitDoneEvent.Wait();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Binds GL context.
|
||||
BlitThreadInit(reg);
|
||||
|
||||
Clyde._windowing!.GLMakeContextCurrent(Clyde._mainWindow!);
|
||||
}
|
||||
}
|
||||
|
||||
private void BlitThread(WindowData reg)
|
||||
{
|
||||
BlitThreadInit(reg);
|
||||
|
||||
reg.BlitDoneEvent!.Set();
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
reg.BlitStartEvent!.Wait();
|
||||
if (reg.Reg.IsDisposed)
|
||||
{
|
||||
BlitThreadCleanup(reg);
|
||||
return;
|
||||
}
|
||||
|
||||
reg.BlitStartEvent!.Reset();
|
||||
|
||||
// Do channel blit.
|
||||
BlitThreadDoSecondaryWindowBlit(reg);
|
||||
}
|
||||
}
|
||||
catch (AggregateException e)
|
||||
{
|
||||
// ok channel closed, we exit.
|
||||
e.Handle(ec => ec is ChannelClosedException);
|
||||
}
|
||||
}
|
||||
|
||||
private static void BlitThreadCleanup(WindowData reg)
|
||||
{
|
||||
reg.BlitDoneEvent!.Dispose();
|
||||
reg.BlitStartEvent!.Dispose();
|
||||
}
|
||||
|
||||
private void CreateWindowRenderTexture(WindowData reg)
|
||||
{
|
||||
reg.RenderTexture?.Dispose();
|
||||
|
||||
reg.RenderTexture = Clyde.CreateRenderTarget(reg.Reg.FramebufferSize, new RenderTargetFormatParameters
|
||||
{
|
||||
ColorFormat = RenderTargetColorFormat.Rgba8Srgb,
|
||||
HasDepthStencil = true
|
||||
});
|
||||
// Necessary to correctly sync multi-context blitting.
|
||||
reg.RenderTexture.MakeGLFence = true;
|
||||
}
|
||||
|
||||
private sealed class WindowData
|
||||
{
|
||||
public WindowReg Reg = default!;
|
||||
|
||||
public RenderTexture? RenderTexture;
|
||||
// Used EXCLUSIVELY to run the two rendering commands to blit to the window.
|
||||
public Thread? BlitThread;
|
||||
public ManualResetEventSlim? BlitStartEvent;
|
||||
public ManualResetEventSlim? BlitDoneEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user