Compare commits

...

235 Commits

Author SHA1 Message Date
Vera Aguilera Puerto
fdcaa83b63 AudioSystem uses TryGet for ITransformComponent. 2021-10-01 12:48:24 +02:00
metalgearsloth
cb1b93e89c Revert this because it makes contacts look like shit 2021-09-30 22:49:37 +10:00
ike709
7d271a83b1 Updates cefglue (#2083)
Co-authored-by: ike709 <ike709@github.com>
2021-09-29 21:35:08 +02:00
metalgearsloth
ebaba35e8b Aggressively sleep physics bodies (#2078)
* Aggressively sleep physics bodies

* Fix tests

* Also buff angular damping
2021-09-29 20:06:56 +10:00
Vera Aguilera Puerto
35f4a4b011 Adds OwnerUid property to components. 2021-09-29 11:23:48 +02:00
metalgearsloth
0eebfe6931 Optimise anchored entity states for PVS (#2067) 2021-09-29 10:47:30 +02:00
metalgearsloth
9dafe46552 Don't generate physics contacts for non-predicted bodies (#2070)
These are all just handled on the server anyway so it's a significant waste of performance on the client for busy scenes.
2021-09-29 10:45:43 +02:00
metalgearsloth
b5d3772ef4 Fix items getting stuck awake 2021-09-29 13:59:12 +10:00
metalgearsloth
9baa2e6469 Slight grid fixtures optimisation (#2075) 2021-09-29 00:11:20 +10:00
Vera Aguilera Puerto
87f5c059c4 Remove IComponentManager entirely. 2021-09-28 13:40:48 +02:00
Vera Aguilera Puerto
fbcdd63988 Gets rid of most ComponentManager usages. (#2076)
- Obsoletes more `ComponentManager` fields/properties.
2021-09-28 13:36:18 +02:00
Vera Aguilera Puerto
07542ff6eb Further IComponentManager warning cleanup. 2021-09-28 12:56:12 +02:00
Vera Aguilera Puerto
56dd3ff03b Clean up BaseServer slightly.
- It doesn't CullRemovedComponents twice anymore.
- Remove IComponentManager from it.
2021-09-28 12:42:25 +02:00
Vera Aguilera Puerto
15778d3af4 Fix some warnings regarding IComponentManager. 2021-09-28 12:41:49 +02:00
Vera Aguilera Puerto
0fce2e438a Add EntityUid overloads for SoundSystem, AudioSystem. 2021-09-28 11:41:15 +02:00
Vera Aguilera Puerto
7d39e130f4 Fix bug involving the MapCoordinates SpawnEntity overload.
When you tried to spawn an anchored entity, it wouldn't spawn it in the position you specified, as the entity is anchored...
Now, we forcibly disable anchoring just for moving the entity, and restore the old anchor state when we're done.
Yes, this is a bad solution. No, I don't regret it: We need to get our shit together and have a way to forcibly teleport/move entities that are anchored for things like this.
2021-09-27 20:16:23 +02:00
Vera Aguilera Puerto
85249e7be7 Add Filter overloads for EntityUid. 2021-09-27 14:59:22 +02:00
metalgearsloth
e91ce4c5ad Fix animation crash 2021-09-27 15:29:43 +10:00
metalgearsloth
09fb921f38 Fix occluder edges for grid rotation (#2054) 2021-09-27 14:53:09 +10:00
metalgearsloth
1acad96eff Optimise animations a bit (#2006)
* Optimise animations

L'il bit of ECS at the time.

* Add pointlight event too

* System methods

* Bool flag

* Fix perf problem

* Use RQ
2021-09-27 14:51:10 +10:00
metalgearsloth
3dac144bef Remie is in engine now (#2066) 2021-09-27 14:38:41 +10:00
metalgearsloth
bccda8d87c Fix deleting grid crashing 2021-09-27 09:57:07 +10:00
metalgearsloth
5af8732054 Optimise ServerMapManager GetStateData
Free 4% performance on PVS
2021-09-26 17:10:48 +10:00
metalgearsloth
6f057e0c85 Set capacity for containermanager getcompstate 2021-09-26 16:52:02 +10:00
Paul
d18743a3cc improves vectorserializer copypasta 2021-09-26 02:13:40 +02:00
ike709
c785311a74 Adds support for "1x2" Vector2i serializing (#2065) 2021-09-26 02:04:47 +02:00
Vera Aguilera Puerto
949a584325 Fix missing using directive when EXCEPTION_TOLERANCE is enabled. 2021-09-25 18:31:18 +02:00
Vera Aguilera Puerto
c7461221b9 Merge ComponentManager into EntityManager. (#2064)
* Merge ComponentManager into EntityManager.

* Obsolete IComponentManager.
2021-09-25 18:23:11 +02:00
Pieter-Jan Briers
cd2942a0da Fix splitcontainer layout calculations. 2021-09-25 18:18:33 +02:00
Vera Aguilera Puerto
0b8cb5e3b6 Add proxy methods to SharedContainerSystem. (#2061) 2021-09-25 18:18:11 +02:00
metalgearsloth
aa3992255e Use approximate bounds for sprite rendering (#2052) 2021-09-23 23:53:37 +10:00
metalgearsloth
6d0cf375d0 Hotfix centroids
Not the permanent solution given it doesn't handle offset vertices but good enough for now.
2021-09-23 13:19:38 +10:00
Pieter-Jan Briers
52ad027854 Increase display.blur_light_factor to 0.001f 2021-09-21 08:09:02 +02:00
metalgearsloth
60ac47fad7 Fix rendertexture for rotated eyes (#2053) 2021-09-20 18:19:05 +02:00
metalgearsloth
8b462cc099 Remove 2 unused field dependencies 2021-09-21 00:18:33 +10:00
metalgearsloth
e918e40cd2 Fix ShapeTest warnings 2021-09-21 00:01:49 +10:00
metalgearsloth
39656f6810 Fix fixture mass setter crash 2021-09-20 22:30:31 +10:00
metalgearsloth
68576ace72 Accurate grid bounds (#2027)
* Fright night

* Shitty bounds working

* No more fixture leak

* Optimise TryFindGridAt

Should be O(1) now instead of the previous O(n) for number of fixtures

* ambush

* Merge stuffies

* Merge to master

* Fixes I guess

* Fix client sync

* Fix grid deserialization

* Jank test

* Fix deferral shitfuckery

* Optimise and remove

* Fixes

* Just werk dam u

* Optimisations

* Bunch of fixes

* FINALLY IT'S DONE

* Fixes

* Fix

* Comment
2021-09-20 21:07:31 +10:00
Vera Aguilera Puerto
ac21e24f33 Proper xmldocs for Resolves. 2021-09-20 11:06:42 +02:00
Vera Aguilera Puerto
843b8e69ef EntitySystem Resolve helper methods. (#2047)
* EntitySystem Resolve helper methods.

* Debug assert for component ownership

* Fix assert
2021-09-20 11:03:24 +02:00
metalgearsloth
a827bbec2b Add a debug system for showing anchored ents (#2030) 2021-09-20 11:00:30 +02:00
Visne
a5be8e723e Remove some unused obsolete stuff (#2049) 2021-09-20 10:59:50 +02:00
metalgearsloth
bac3ff969b Don't sleep items parented to map (#2031) 2021-09-20 10:58:44 +02:00
Javier Guardia Fernández
bfabc8e51a Fix deleting empty containers on the client deleting their previous contents (#2036) 2021-09-20 10:58:14 +02:00
Paul Ritter
c116cd6d8b new xaml error for invalid root node type (#2038)
Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
2021-09-20 10:57:43 +02:00
Visne
30f39dfb66 Remove MarginContainer (#2050) 2021-09-20 10:43:12 +02:00
Pieter-Jan Briers
6b81c283f5 Light blur experiment. 2021-09-20 10:40:46 +02:00
Pieter-Jan Briers
c91170b50e Explicit GL debugger detection to fix ANGLE console spam. 2021-09-20 10:40:30 +02:00
Pieter-Jan Briers
831dd0e5bb Flush render queue after WorldSpaceBelowFOV()
Fixes FOV appearing below atmos in SS14.
2021-09-20 10:14:49 +02:00
Swept
028dea2819 Updates documentation link in comment 2021-09-20 01:25:57 +00:00
Pieter-Jan Briers
9117f8c776 These IRenderHandle APIs are going public, deal with it. 2021-09-19 23:37:47 +02:00
mirrorcult
a46c539462 Merge pull request #2048 from Visne/vbox-hbox-remove
Remove VBoxContainer/HBoxContainer completely
2021-09-19 13:03:17 -07:00
Visne
c9cbeadc9e Trigger tests 2021-09-19 21:37:42 +02:00
Visne
8f1386cb19 Remove VBoxContainer/HBoxContainer completely 2021-09-19 18:41:10 +02:00
Vera Aguilera Puerto
3b902e0557 IMetaDataComponent implements IComponent 2021-09-19 10:46:09 +02:00
ike709
3142dcbfda Adds some OpenDream things to the sandbox whitelist (#2042) 2021-09-18 22:40:22 +02:00
Vera Aguilera Puerto
29ffd07712 MapCoordinates Offset overload that takes two floats. 2021-09-18 13:01:30 +02:00
Vera Aguilera Puerto
afd687e392 Add Offset method to MapCoordinates. 2021-09-18 12:49:54 +02:00
Vera Aguilera Puerto
72341a25af VV attribute for ViewSubscriptions in PlayerSession. 2021-09-18 11:14:44 +02:00
Vera Aguilera Puerto
6d03840fbd Makes BoundUserInterfaces partially ECS. (#2033) 2021-09-18 10:41:06 +02:00
metalgearsloth
ea2d63b404 Add test for lookup anchoring (#2035)
* Add test for lookup anchoring

* Updates

* Fix test
2021-09-17 01:43:41 +10:00
metalgearsloth
42a137d106 Raise anchoring broadcasted 2021-09-17 00:06:02 +10:00
metalgearsloth
8f8c1eccc3 hotfix eyes 2021-09-16 17:42:30 +10:00
metalgearsloth
b91fa6ba75 Reduce position approx tolerance 2021-09-16 14:03:33 +10:00
metalgearsloth
e270f4f06c Reduce velocity approx tolerance 2021-09-16 13:57:18 +10:00
metalgearsloth
56b7957878 Receive testbed from content (#2034) 2021-09-16 13:02:43 +10:00
metalgearsloth
209eb5fea0 Fix 90% of grid rotation bugs (#2003)
* View box2rotated

* Changes

* Stash someday

* Sync

* Grid rotation in a commit

* SIMD comment

* Minor TryFindGridAt optimisation

* More fixes

* Optimise rays a tad

* Reduce code duplication

* Fix anchoring

* More fixes woopsie

* Eye matching parent

* Centre of mass stuffsies

* Remove TODO

* Add TODO

* Revert anchor crash

* Fix master merge-in whoopsie

* Fixes

* Woops

* Fixed viewport transform

* Re-fix rendering
2021-09-16 12:59:16 +10:00
Javier Guardia Fernández
417954e66c Simplify how some serialization expression trees return values (#2040) 2021-09-15 21:00:42 +02:00
Visne
e4fb860985 Fix licenses (#2041) 2021-09-16 00:18:51 +10:00
Pieter-Jan Briers
d70481aedd Update Linguini
This fixes several compiler warnings.
2021-09-15 16:17:52 +02:00
Pieter-Jan Briers
52eb581b56 Fix some compiler warnings. 2021-09-15 15:55:27 +02:00
Pieter-Jan Briers
918fa8e3b9 Fix handling of key names GLFW returns as null. 2021-09-15 13:37:24 +02:00
Visne
1c7f19bf67 Add OptionButton margin (#2020) 2021-09-15 01:49:49 +02:00
Javier Guardia Fernández
0cbff8dee1 Change serialization reading to only do type checks once per type (#2018)
* Change serialization reading to only do type checks once per type

* Optimize for sealed types in arrays

* Oops the context

* Fix ISelfSerialize node type and null values

* Add int read test

* Remove nullability from constructor and property of DeserializedValue

* Add clearing readers to serialization shutdown

* Fix struct populate default values
Remove some notnull constraints

* Replace robust gen with normal il generator
2021-09-13 18:14:52 +02:00
metalgearsloth
670ff54ef0 Fix SetAsBox 2021-09-13 19:46:31 +10:00
metalgearsloth
8f519c52b6 Fix multi-viewer PVS crash 2021-09-13 15:59:18 +10:00
Javier Guardia Fernández
fbd29afbd8 Fix data definitions sometimes not being found in tests (#2026) 2021-09-12 11:47:50 +02:00
Javier Guardia Fernández
2bbdb23716 Fix tests getting stuck until terminated (#2025) 2021-09-12 11:42:46 +02:00
Javier Guardia Fernández
e2dda67eef Add test for IPopulateDefaultValues (#2024) 2021-09-12 10:07:59 +02:00
metalgearsloth
699615df97 Remove Snapus Gridus (#2014) 2021-09-12 16:34:51 +10:00
Javier Guardia Fernández
e804994a5b Fix serialization manager shutdown (#2011) 2021-09-11 00:00:31 +02:00
metalgearsloth
24f7ecccc0 Remove AppearanceTestComponent (#2015) 2021-09-10 23:59:42 +02:00
Javier Guardia Fernández
21d43350f0 Add benchmark for serializing arrays of strings, ints and definitions (#2017)
* Add benchmark for serializing arrays of strings, ints and definitions

* Add sealed elements array benchmark
2021-09-10 17:57:16 +02:00
Javier Guardia Fernández
63eb7847a1 Add test for serializing nullable ints (#2016) 2021-09-10 17:50:02 +02:00
metalgearsloth
4a4f07a10c StopPlaying won't throw if already disposed (#2013) 2021-09-09 12:53:33 +02:00
Javier Guardia Fernández
1c816941ec Change serialization writing with serializers to use expression trees (#2010)
* Change serialization writing with serializers to use expression trees

* Add test
2021-09-08 13:05:34 +02:00
Javier Guardia Fernández
2143c9abc3 Change serialization reading with serializers to use expression trees (#2009)
* Change serialization reading with serializers to use expression trees

* Add test

* Eliminate lambda capture
2021-09-08 12:07:17 +02:00
Vera Aguilera Puerto
4b6ceed586 Adds FileNotFoundException to the sandbox whitelist. 2021-09-08 09:52:20 +02:00
metalgearsloth
49c6c0c9d8 Fix anchoring crashes 2021-09-08 10:55:44 +10:00
metalgearsloth
54f6143be3 Quick audio fix nothing to see here 2021-09-07 18:17:58 +10:00
metalgearsloth
b83e09c214 Add method to apply impulse from a point (#1992) 2021-09-07 08:25:28 +02:00
metalgearsloth
caa9b442d5 Use arrays for polygon vertices (#1958)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-09-07 08:24:58 +02:00
Javier Guardia Fernández
69921da034 Change serialization copying with serializers to use expression trees (#2008) 2021-09-07 08:09:26 +02:00
metalgearsloth
552893f1b7 Audio enhancements (#1997)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-09-07 08:08:49 +02:00
metalgearsloth
c5c10caaf1 Chunk streaming (#1985)
* The real lookup flags

* Don't get anchored entities for PVS query

* Finish that implementation

* Immediate anchoring changes

* Woops some anchoring slippy

* Fix stupidity

* More gradual

* Remove thread-unsafe stuff

In case anyone notices I'm stupid

* Some day I'll stash instead lol

* Fix thread issue

* Werk

* Cvar for range too

* Explanation

* More comments

* Fix merge
2021-09-07 07:59:22 +02:00
metalgearsloth
98de046977 Avoid unnecessary TransformComponent cast in PVS (#2007)
* Avoid unnecessary TransformComponent cast in PVS

Haven't profiled if it's faster but it supports EntityUid better so whenever we deprecate IEntity we would've had to do this anyway.

* Avoid unnecessary MapPosition call

LocalToChunkIndices already checks for this and saves the additional conversion from EC -> map -> EC again

* Doc fixes
2021-09-07 07:57:54 +02:00
Pieter-Jan Briers
6328bddb52 Fix GridContainer looking at Size in its ArrangeOverride()
This fixes tests.
2021-09-07 07:47:17 +02:00
Pieter-Jan Briers
003c910c66 Make some icky reflection APIs internal. 2021-09-07 07:43:24 +02:00
Pieter-Jan Briers
1d7f4b4501 Improve comments on BoxContainer a bit. 2021-09-07 02:27:56 +02:00
Pieter-Jan Briers
2f6fb22473 Remove obsolete layout APIs. 2021-09-07 01:38:13 +02:00
Pieter-Jan Briers
96d66c57c1 Comments for Control.Layout.cs. 2021-09-07 01:24:10 +02:00
metalgearsloth
35741524a3 Significantly reduce debug allocations (#2005)
* Significantly reduce debug allocations

I know it's debug but it's number 1 on entity spawning

* Nothing to see here
2021-09-06 13:32:09 +02:00
metalgearsloth
d5855fa805 Sync PointLight data between client and server. (#1995)
* Sync PointLight data

* ECS GetComponentState

* Nothing to see here
2021-09-06 17:31:06 +10:00
metalgearsloth
36a9d06381 Fix Robust tests (#2004)
Not the greatest of solutions but at least it's fixed for now.
2021-09-06 01:27:37 +10:00
Pieter-Jan Briers
3bfb3a5ef7 Fix GLES3 sRGB detection.
Forgot to set the variable, epic.
2021-09-04 21:07:20 +02:00
metalgearsloth
83b031cab4 Optimise GetTileRef (#1998)
* Optimise GetTileRef

Used in movement code a lot to check weightless and most of the time they're parented to the grid.

* More optimisations
2021-09-04 12:09:04 +02:00
Pieter-Jan Briers
6197352fca Try GLES3 anyways on compat mode. 2021-09-01 18:29:58 +02:00
Pieter-Jan Briers
0fd210481a Multithread SerializationManager initialize. 2021-09-01 13:38:34 +02:00
Pieter-Jan Briers
74eea847e2 Compact LOH on initial startup GC.
LOH gets fragmented heavily, currently, so this helps.
2021-09-01 01:29:20 +02:00
Pieter-Jan Briers
6938787863 Fix compat mode, fullscreen, more work on ANGLE swap chain. 2021-09-01 01:13:32 +02:00
Pieter-Jan Briers
4ba5654253 Allow modifying selection/cursor colors on LineEdit 2021-08-30 23:34:28 +02:00
Pieter-Jan Briers
15ff120b97 Add Control.StylePropertyDefault() helper method. 2021-08-30 23:34:14 +02:00
Pieter-Jan Briers
942a550687 Fix clearColor not being passed through by RenderInRenderTarget.
This fixes background colors for non-main WindowRoots
2021-08-30 23:24:21 +02:00
Pieter-Jan Briers
acff35f4bf Fix multi-window rendering deadlock. 2021-08-30 23:07:26 +02:00
Pieter-Jan Briers
4e7039e09b StyleBoxFlat can now draw simple rectangular borders 2021-08-30 23:01:51 +02:00
Pieter-Jan Briers
4a0b3793e4 Add IUserInterfaceManager.GetWindowRoot 2021-08-30 23:01:38 +02:00
Pieter-Jan Briers
928ae19249 Fix styling not applying to root controls. 2021-08-30 15:17:43 +02:00
Pieter-Jan Briers
a7f85b53f8 Background color for UIRoot. 2021-08-30 02:14:22 +02:00
Pieter-Jan Briers
5fac83c697 Fix GLFW_SCALE_TO_MONITOR not being specified for self-managed context windows. 2021-08-30 01:45:56 +02:00
Pieter-Jan Briers
577b2b0f62 Remove OutputPath overrides for shared.
Pretty sure this will reduce recompilation when switching between debug and release in rider, so sounds like a good idea to me.
2021-08-30 01:38:28 +02:00
Pieter-Jan Briers
cd3a7ef91e ANGLE+DXGI experiment, window/GL init rewrite (#1982) 2021-08-30 01:35:07 +02:00
Vera Aguilera Puerto
051d47f4ff Adds ComponentGetState ECS event (#1988) 2021-08-29 17:23:40 +02:00
metalgearsloth
b982b94c83 Port TestPoint from Box2D (#1991)
Rather than having the methods on the shapes themselves (whenever we eventually get them as structs) I just put them on a shapemanager instead as it seemed cleaner?
2021-08-29 17:10:46 +02:00
Vera Aguilera Puerto
84b7742656 Adds by-ref and struct event support to ComponentEvents (#1987) 2021-08-29 16:52:38 +02:00
metalgearsloth
1585cd111f Don't dirty appearance if state is identical (#1990)
Avoids unnecessary visualizer calls and bugs from duplicate animations or the likes.
2021-08-29 16:00:06 +02:00
metalgearsloth
766f6dc93d EntityLookup flags for PVS optimisations (#1983)
* The real lookup flags

* Don't get anchored entities for PVS query

* Finish that implementation

* Immediate anchoring changes

* Woops some anchoring slippy

* Address review
2021-08-29 14:03:45 +10:00
Vera Aguilera Puerto
59af9451be Various improvements to midi
- MidiEvent control is now an int
- Instead of hardcoding the channel count, we check the count on the synth itself.
- Do nothing for event 0x51
2021-08-26 15:01:34 +02:00
metalgearsloth
80e4f69f29 PVS Anchoring take 2 (#1959)
* PVS is now aware of entity anchoring.
Misc PVS perf improvements.

* Fix master merge

* Optimise chunks intersecting

* Ensure anchored entity chunks are always sent

* Remove silly pool by me

* Hotfixes?

* Chunk sending

* Optimise assert

* Assert updates

* Remove AME debug

* Fixes

* Add back in including map eye

* Update Robust.Server/GameStates/EntityViewCulling.cs

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>

* Update Robust.Shared/Map/MapChunk.cs

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>

Co-authored-by: Acruid <shatter66@gmail.com>
Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
2021-08-26 12:48:44 +10:00
Pieter-Jan Briers
98060f2056 Fix closing child window minimizing parent on windows. 2021-08-26 00:30:56 +02:00
Vera Aguilera Puerto
f0bfb6a69a Adds deflate compression to MsgState. (#1981) 2021-08-25 23:54:15 +02:00
Pieter-Jan Briers
ac38748280 Fix GridContraction_Test doing bad entity access and causing test failure. 2021-08-25 23:30:38 +02:00
Pieter-Jan Briers
18fe34c041 Do not send all entities on fromTick = 0.
This doesn't seem to be necessary anymore and it massively improves load performance.
2021-08-25 23:19:34 +02:00
Pieter-Jan Briers
6a0cb6a5af Rework entity storage to remove CullDeletedEntities.
Now we only use the dictionary for storing entities, similar to components.

This means creating/deleting entities while enumerating them will throw, but that's probably fine since GetEntities() is barely used nowadays, and we have QueueDelete().
2021-08-25 22:47:33 +02:00
Pieter-Jan Briers
310aebaee2 Make prediction CVars NOT ARCHIVE.
I want to disable them in OpenDream and this would make that impossible.
2021-08-25 22:47:32 +02:00
Paul Ritter
f25d67da55 dynamically adjusts divisions in worlddrawhandles drawcircle (#1980)
Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
2021-08-25 22:23:59 +02:00
metalgearsloth
eac36e917f Fix showrays (#1979) 2021-08-26 00:12:56 +10:00
ShadowCommander
c64fa8f2cc Fix Box2Serializer and tests 2021-08-24 22:58:43 -07:00
Vera Aguilera Puerto
63c1707581 Adds new helpful filter methods. (#1962)
- AddWhereAttachedEntity and RemoveWhereAttachedEntity methods
- AddInGrid and AddInMap methods
- BroadcastGrid and BroadcastMap static methods
2021-08-24 16:11:38 +02:00
metalgearsloth
914a7fb130 Fix box2 serializer (#1966)
There's some existing content with invalid bounds so this is going to be fun.
2021-08-24 18:30:42 +10:00
Pieter-Jan Briers
41d3495e29 Some stubs for JS execution queuing.
Definitely gonna be necessary but eeeeeeh feeling lazy.
2021-08-24 10:05:35 +02:00
Pieter-Jan Briers
2ae426b8e5 Allow file filters in save file dialogs. 2021-08-24 09:24:49 +02:00
Pieter-Jan Briers
eb45e16739 BrowserControl now allows hooking the BeforeBrowse API.
This is necessary to be able to cancel navigation (OpenDream Topic()).
2021-08-24 08:32:44 +02:00
Pieter-Jan Briers
79c952a871 Make MouseFilterMode.Stop default for BrowserControl 2021-08-24 08:01:22 +02:00
Pieter-Jan Briers
827c4b32fd Double check that CefManager is initialized in BrowserControl.
Otherwise it would throw with a nullref somewhere in CefGlue.
2021-08-24 07:35:28 +02:00
Pieter-Jan Briers
033e20cbf1 Add Closed (past tense) event to OSWindow.
Change a bunch of window event stuff up.
2021-08-24 04:49:04 +02:00
Pieter-Jan Briers
3ff46d40fa UsedImplicitly for IConsoleCommand 2021-08-24 04:42:09 +02:00
Pieter-Jan Briers
5ab6aba20b More implicit use attributes.
This means that entity systems and IoC managers should no longer need [UsedImplicitly] to suppress "object never instantiated" warnings.
2021-08-24 03:13:23 +02:00
Pieter-Jan Briers
e6be04cb83 OSWindow control to make OS windows much easier. 2021-08-24 02:05:25 +02:00
Pieter-Jan Briers
627b2f4d0d Change WindowCreateParameters.HideCloseButton to a more general purpose Styles system. 2021-08-24 02:05:25 +02:00
Pieter-Jan Briers
ffff42cc95 Add owner window parameter to window creation.
And also CenterOwner placement rule.

Currently owner windows are only implemented on Win32. Linux and macOS will need implementing still.
2021-08-24 02:05:25 +02:00
Pieter-Jan Briers
ce420ac8ab Set GLFW_SCALE_TO_MONITOR true on window creation. 2021-08-24 02:05:25 +02:00
Paul Ritter
d3cdfbe3cb ratio's rects (#1970)
Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
2021-08-23 20:33:38 +02:00
Paul Ritter
726bfd58f6 adds some helpers to robustrandom (#1969)
Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
2021-08-23 20:32:51 +02:00
Paul Ritter
05827d01d0 Datafield now implies readonly VVAccess (#1968)
Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
2021-08-23 20:32:25 +02:00
metalgearsloth
6cb7641c07 Fix GetEntitiesInRange
A lot of lookup needs cleaning up but this is technically brokeded.
2021-08-23 22:40:47 +10:00
metalgearsloth
d72185933a Add support for grid chunk removals (#1941)
* Add support for grid chunk removals

Also allows grids to be removed when they have no more chunks remaining.

* No more crashing pog

* Slightly better

* Minor optimisations and fix bounds

* Avoid creating new chunks for anchoring

* chucky

* comment

* Tests

* Remove some logs

* Remove another log

* Review
2021-08-23 16:00:07 +10:00
metalgearsloth
80a9a82278 Fix client-side physics bodies getting stuck awake (#1946)
* Fix client-side physics bodies getting stuck awake

Wasn't a huge perf hit but it could leak a bit. Also there's a bunch of E/C stuff in here and I'll deal with it later.

* Fix broadphase leak

* Fix robust server sim
2021-08-23 15:01:08 +10:00
metalgearsloth
99c8ce2cc8 Attach to parent before inserting into container (#1965)
tl;dr is that throwing on content needs to be able to have IsInContainer return the correct result when checking for it in EntInsertedIntoContainer
2021-08-22 21:46:20 -07:00
Pieter-Jan Briers
f137a6e3a5 Add InheritChildMeasure bool to LayoutContainer 2021-08-22 11:32:15 +02:00
Pieter-Jan Briers
73e62414a7 Update Lidgren and NetSerializer submodules 2021-08-22 11:08:43 +02:00
Pieter-Jan Briers
ef7ef3ca9f Replace usages of NET5_0 preprocessor with NET5_0_OR_GREATER 2021-08-22 11:08:05 +02:00
Pieter-Jan Briers
da26e86f5e Merge branch 'robust-client-CEF' 2021-08-22 10:51:44 +02:00
Pieter-Jan Briers
d2a1815d7b Allow GameControllerOptions to change the startup branding. 2021-08-22 10:43:41 +02:00
Pieter-Jan Briers
4894888339 Dev window has UI tab now with UI tree. 2021-08-22 02:07:42 +02:00
Pieter-Jan Briers
3fd55733bb Add child added/removed/moved events to Control.
Intended for UI dev window
2021-08-22 02:07:42 +02:00
Pieter-Jan Briers
bebd638412 Make SplitContainer properties VV. 2021-08-22 02:07:42 +02:00
Pieter-Jan Briers
328b3cc715 Add IUserInterfaceManager.OnPostDrawUIRoot.
Intended for UI dev window highlighting.
2021-08-22 02:07:42 +02:00
Pieter-Jan Briers
b523cfddb7 Fix some layout calculation bugs.
1. Fix a copy paste typo in ApplySizeConstraints().
2. Fix negative sizes passed to MeasureCore() causing exceptions.
2021-08-22 02:07:42 +02:00
Pieter-Jan Briers
4fea277541 Make some SplitContainer stuff [ViewVariables] 2021-08-22 02:07:42 +02:00
Pieter-Jan Briers
3417f00650 Give main window UI root a good name 2021-08-22 02:07:42 +02:00
Pieter-Jan Briers
db4b190498 guidump command now shows all UI roots. 2021-08-22 02:07:38 +02:00
Pieter-Jan Briers
7d2bbc2ca7 Add an OnResized event to controls 2021-08-22 01:29:24 +02:00
Pieter-Jan Briers
553278fdb3 Fix x:Class directives in XamlUI. 2021-08-22 01:29:10 +02:00
Vera Aguilera Puerto
e5013d9e3f Add support for struct directed events, add by-ref directed/broadcast events. (#1931)
* Add support for struct directed events.
- Turn transform events into readonly structs

* TransformComponent events have readonly fields.

* Reduce boxing allocations, add DirectedRegistration readonly struct.

* Use typeof in a few places instead of GetType

* Add overloads to allow passing directed events by ref.

* Raise transform events by ref.

* Fix occluder AnchorStateChangedEvent subscription

* Fix broadphase subscriptions

* Fix bug oops

* Fix transform system raising moveevents by value

* Don't reflect the test entity systems.

* Directed EventBus uses ref everywhere, internally
Deduplicates a bunch of code. Whoo!

* Add broadcast by-ref events

* Fix by-ref events bug using Unsafe, add new tests for sorted by-ref events.

* Port broadcast events to by-ref.

* Speed, more correctness, reduce generics bloat.

* Clean up subscription code some.

* Remove bad test.

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-08-21 11:37:02 +02:00
Vera Aguilera Puerto
679f8f24c9 Cleans up EntityCoordinates, split coordinates into their own files. (#1956) 2021-08-20 15:48:43 +02:00
metalgearsloth
1a547b946c Fix anchoring parent changes
Problem was coordinates were updated first and then it was clearing the snapgrid cell.
2021-08-20 14:14:17 +10:00
metalgearsloth
5594bd7203 Revert "PVS is now aware of entity anchoring. (#1845)"
This reverts commit 820d988e0a.

# Conflicts:
#	Robust.Server/GameStates/EntityViewCulling.cs
2021-08-19 22:34:06 +10:00
metalgearsloth
8cbe48c94f Use all chunks for PVS culling
Temporary attempt at fixing the server issue with PVS.
2021-08-19 22:30:05 +10:00
metalgearsloth
b04b9fc9cd Fix timer crash 2021-08-19 22:18:31 +10:00
Acruid
820d988e0a PVS is now aware of entity anchoring. (#1845)
* PVS is now aware of entity anchoring.
Misc PVS perf improvements.

* Fix master merge

* Optimise chunks intersecting

* Ensure anchored entity chunks are always sent

* Remove silly pool by me

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2021-08-19 21:48:44 +10:00
metalgearsloth
1ad5122b5d Fix y-sorting (#1952) 2021-08-18 17:49:54 +02:00
metalgearsloth
456ac03870 Add RevoluteJoint (#1953) 2021-08-18 17:48:52 +02:00
metalgearsloth
f647d00dc4 Add short-circuit for TryDistance (#1951)
* Add short-circuit for TryDistance

90% of the uses probably have the same parent hence we can avoid the more expensive ToMap calls for these.

* InRange too
2021-08-17 08:56:16 +02:00
metalgearsloth
2fd3bbf58b Optimise TryFindGridAt (#1945) 2021-08-16 21:06:31 +02:00
metalgearsloth
5ed49d51d3 Add vertices simplifier (#1935) 2021-08-15 19:15:23 +02:00
metalgearsloth
758d5eedef Add WorldPoint to StartCollideEvent (#1943)
Some stuff may want the actual collision point.
2021-08-15 16:45:42 +02:00
metalgearsloth
96d15929e6 Optimise GetMapChunks (#1948)
Don't need to check every chunk's bounds when you can just do dictionary lookups. Should be an okay bonus for PVS and grid rendering on larger grids. Didn't make tests yet because all of the existing ones are mocked and painful to change.
2021-08-15 16:45:30 +02:00
metalgearsloth
2a062dbf53 Cleanup TimerComponent when none are running (#1949)
The ToList was showing up on a profiler.
2021-08-15 16:45:00 +02:00
metalgearsloth
6d66bc66e4 Minor grid traversal optimisation (#1950)
Avoids running duplicate events, mainly for client, but also avoids the additional GetEntity call as well.
2021-08-15 16:29:14 +02:00
Pieter-Jan Briers
5cc056100b Remove unused MsgEntity parameter packing helpers.
These were originally for when messages were passed around as an object[] args list, I believe. This isn't used so this code should just go honestly.
2021-08-12 14:35:56 +02:00
Visne
5eee22b034 Make no rotation work when eye is rotated (#1927) 2021-08-12 13:12:02 +02:00
DrSmugleaf
7f5beab259 Add the component type to the unknown component linter error message 2021-08-12 12:21:52 +02:00
metalgearsloth
46aead639b Allow VirtualControllers to be retrieved by content (#1937)
Useful in SS14 for MoverController to cache some work that SharedTileFrictionController can re-use.
2021-08-12 17:55:34 +10:00
Visne
488d060595 Fix map deletion not working because of deleted entity referencing map (#1933)
* Fix map deletion not working because of deleted entity referencing map

* Change fix

* Update Robust.Shared/GameObjects/Components/CollisionWakeComponent.cs

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2021-08-12 14:00:12 +10:00
metalgearsloth
b1bab1f38e Significantly reduce default volume range
Now it's actually unified instead of being 25 sometimes and 62.5 (the godot default) at other times.
2021-08-11 19:53:15 +10:00
metalgearsloth
feba2a23ad Fix grid collisions (#1934)
* Fix grid collisions

* Fix this silliness

* Add test for grid collisions

* Fix FlagSerializer not working with 1 << 31

Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
2021-08-10 00:04:19 +10:00
Javier Guardia Fernández
e1ff29744e Fix FlagSerializer not working with 1 << 31 (#1940) 2021-08-09 23:56:43 +10:00
Javier Guardia Fernández
f6216e63b0 Add tests for serializing flags (#1939) 2021-08-09 13:07:42 +02:00
Javier Guardia Fernández
0b3ecf2168 Add benchmarks for serializing flags (#1938)
* Add benchmarks for serializing flags

* Add type definitions
2021-08-09 13:03:24 +02:00
Vera Aguilera Puerto
9f29bf6349 Merge branch 'master' into robust-client-CEF 2021-08-09 12:02:15 +02:00
Vera Aguilera Puerto
d62a64029d Merge branch 'master' into robust-client-CEF 2021-08-09 11:13:56 +02:00
Vera Aguilera Puerto
17662aaad9 Merge branch 'master' into robust-client-CEF 2021-08-09 08:08:29 +02:00
Pieter-Jan Briers
47af668cda Fix overriding unregistered-but-known config vars.
The default value would be set to 0 as a placeholder from when the config var is first tracked as unregistered, which means that when we try to override it tries to parse it as an int always.
2021-07-29 20:29:08 +02:00
Vera Aguilera Puerto
757143be84 Merge branch 'master' into robust-client-CEF 2021-07-29 13:36:16 +02:00
Vera Aguilera Puerto
30fcc6b729 Merge branch 'master' into robust-client-CEF 2021-07-27 09:02:29 +02:00
Vera Aguilera Puerto
49e2d567cd Merge branch 'master' into robust-client-CEF 2021-07-26 12:14:11 +02:00
Vera Aguilera Puerto
9bf69db0ef Merge branch 'master' into robust-client-CEF 2021-07-24 10:06:54 +02:00
Pieter-Jan Briers
695b4ce8f2 Merge branch 'master' into robust-client-CEF 2021-07-13 17:22:20 +02:00
Vera Aguilera Puerto
d7ecc0883f Merge branch 'master' into robust-client-CEF 2021-07-08 10:00:46 +02:00
Vera Aguilera Puerto
44b17edff6 Fix failing unit test. 2021-07-06 10:38:48 +02:00
Pieter-Jan Briers
6366c2383a Request handling API. 2021-07-06 02:07:17 +02:00
Pieter-Jan Briers
ddbbe22bda Make RobustCefApp internal. 2021-07-05 23:41:01 +02:00
Pieter-Jan Briers
6880af0d8b Lifetime management for BrowserControl
Correctly shuts down all resources when removed fromt ree.
2021-07-05 23:35:16 +02:00
Vera Aguilera Puerto
6f258b1822 Fix multi process mode on Linux. 2021-07-05 23:10:19 +02:00
Pieter-Jan Briers
2cf3ed8a70 External window lifetime management. 2021-07-05 21:58:43 +02:00
Pieter-Jan Briers
c6f10a1321 Don't make a RobustCefApp in CEF sub processes. 2021-07-05 20:26:16 +02:00
Vera Aguilera Puerto
41ab9b106f Add RobustCefApp, CefManager no longer inherits CefApp. 2021-07-05 20:22:00 +02:00
Pieter-Jan Briers
0d97569576 Implement mouse modifiers. 2021-07-05 20:18:54 +02:00
Pieter-Jan Briers
0a21d402b2 Apparently I didn't disable disable GPU properly. 2021-07-05 20:16:50 +02:00
Pieter-Jan Briers
c6827fe8ae Merge remote-tracking branch 'origin/robust-client-CEF' into robust-client-CEF 2021-07-05 18:23:05 +02:00
Pieter-Jan Briers
0c94956be1 Various degrees of work on input stuff. 2021-07-05 18:21:47 +02:00
Pieter-Jan Briers
1e7a193d5e Raise scroll speed 2021-07-05 11:44:42 +02:00
Vera Aguilera Puerto
eb4ea8aa63 Adds a few APIs to IBrowserControl and implements them where needed 2021-07-05 11:23:44 +02:00
Pieter-Jan Briers
af1a3d965d Actually shut down loaded modules on client 2021-07-05 10:27:21 +02:00
Pieter-Jan Briers
26fed40ad4 Remove fixed TODO comment 2021-07-05 10:24:10 +02:00
Pieter-Jan Briers
c6c6309eff Disable disable GPU 2021-07-05 10:05:10 +02:00
Pieter-Jan Briers
7188eeb0c8 Add external window creation API 2021-07-05 09:37:04 +02:00
Pieter-Jan Briers
d74b22a4e8 Engine API for fetching win32 HWND. 2021-07-05 09:05:59 +02:00
Pieter-Jan Briers
75de717b4e Enable CEF multi-process mode. 2021-07-05 08:00:16 +02:00
Pieter-Jan Briers
a00fc75b61 BrowserControl less inefficient rendering 2021-07-05 07:59:56 +02:00
Pieter-Jan Briers
dcbb6608df Improve clyde SetSubImage.
1. better perf.
2. code cleaned up
3. span-based API.
2021-07-05 07:55:52 +02:00
Pieter-Jan Briers
e4d0e50000 Add Robust.Client.CEF to InternalsVisibleTo 2021-07-05 07:46:59 +02:00
Pieter-Jan Briers
e5a2ab284b Merge branch 'master' into robust-client-CEF 2021-07-04 19:56:41 +02:00
Vera Aguilera Puerto
3ac1506f17 Add correct license header to BitmapBuffer 2021-07-04 18:22:04 +02:00
Vera Aguilera Puerto
c85bb81606 Add initial CEF integration to engine. 2021-07-02 23:31:37 +02:00
441 changed files with 14916 additions and 5073 deletions

3
.gitmodules vendored
View File

@@ -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/

View File

@@ -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,
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -0,0 +1,41 @@
preset raw;
uniform highp vec2 direction;
uniform highp vec2 size;
uniform highp float radius;
varying highp vec2 pos;
varying highp vec4 blurPos1;
varying highp vec4 blurPos2;
void vertex()
{
highp float aspect = size.y / size.x;
highp float horRadius = aspect * radius;
highp vec2 offset = vec2(horRadius, radius) * direction;
VERTEX = apply_mvp(VERTEX);
pos = (VERTEX + vec2(1.0)) / 2.0;
blurPos1.xy = pos + offset;
blurPos1.zw = pos - offset;
blurPos2.xy = pos + offset * 2.0;
blurPos2.zw = pos - offset * 2.0;
}
void fragment()
{
// Very simple gaussian blur.
highp vec4 sum = zTexture(pos) * 0.375;
sum += zTexture(blurPos1.xy) * 0.25;
sum += zTexture(blurPos1.zw) * 0.25;
sum += zTexture(blurPos2.xy) * 0.0625;
sum += zTexture(blurPos2.zw) * 0.0625;
COLOR = sum;
}

View File

@@ -4,6 +4,8 @@ namespace Robust.Benchmarks
{
internal class Program
{
// --allCategories=ctg1,ctg2
// --anyCategories=ctg1,ctg2
public static void Main(string[] args)
{
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

View 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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
}
}

View File

@@ -5,7 +5,7 @@ namespace Robust.Benchmarks.Serialization.Definitions
[DataDefinition]
public class DataDefinitionWithString
{
[field: DataField("string")]
[DataField("string")]
public string StringField { get; init; } = default!;
}
}

View File

@@ -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!;
}
}

View File

@@ -3,6 +3,7 @@ using Robust.Shared.Serialization.Manager;
namespace Robust.Benchmarks.Serialization.Initialize
{
[MemoryDiagnoser]
public class SerializationInitializeBenchmark : SerializationBenchmark
{
[IterationCleanup]

View File

@@ -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);
}
}
}

View 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);
}
}
}

View File

@@ -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);
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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,
};
}
}

View 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();
}
}
}
}

View 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();
}
}
}

View 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);
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace Robust.Client.CEF
{
public interface IBrowserWindow : IBrowserControl, IDisposable
{
bool Closed { get; }
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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");
}
}
}

View 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()
{
}
}
}
}

View 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>

View 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
{
}
}
}

View 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;
}
}

View 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);
}
}
}

View 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;
}
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
using Microsoft.CodeAnalysis;
namespace Robust.Client.NameGenerator
{
public class InvalidXamlRootTypeException : Exception
{
public readonly INamedTypeSymbol ExpectedType;
public readonly INamedTypeSymbol ExpectedBaseType;
public readonly INamedTypeSymbol Actual;
public InvalidXamlRootTypeException(INamedTypeSymbol actual, INamedTypeSymbol expectedType, INamedTypeSymbol expectedBaseType)
{
Actual = actual;
ExpectedType = expectedType;
ExpectedBaseType = expectedBaseType;
}
}
}

View File

@@ -112,6 +112,12 @@ namespace Robust.Client.AutoGenerated
compiler.Transform(parsed);
var initialRoot = (XamlAstObjectNode) parsed.Root;
var names = NameVisitor.GetNames(initialRoot);
var rootType = (INamedTypeSymbol)initialRoot.Type.GetClrType().Id;
var rootTypeString = rootType.ToString();
if (classSymbol.ToString() != rootTypeString && classSymbol.BaseType?.ToString() != rootTypeString)
throw new InvalidXamlRootTypeException(rootType, classSymbol, classSymbol.BaseType);
var fieldAccess = classSymbol.IsSealed ? "private" : "protected";
//var names = NameVisitor.GetNames((XamlAstObjectNode)XDocumentXamlParser.Parse(xamlFile).Root);
var namedControls = names.Select(info => " " +
@@ -204,6 +210,21 @@ namespace {nameSpace}
var sourceCode = GenerateSourceCode(typeSymbol, txt, comp);
context.AddSource($"{typeSymbol.Name}.g.cs", SourceText.From(sourceCode, Encoding.UTF8));
}
catch (InvalidXamlRootTypeException invRootType)
{
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"RXN0005",
$"XAML-File {xamlFileName} has the wrong root type!",
$"{xamlFileName}: Expected root type '{invRootType.ExpectedType}' or '{invRootType.ExpectedBaseType}', but got '{invRootType.Actual}'.",
"Usage",
DiagnosticSeverity.Error,
true),
Location.Create(xamlFileName, new TextSpan(0, 0),
new LinePositionSpan(new LinePosition(0, 0), new LinePosition(0, 0)))));
continue;
}
catch (Exception e)
{
context.ReportDiagnostic(
@@ -257,7 +278,7 @@ namespace {nameSpace}
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"RXN0004",
"RXN0006",
missingPartialKeywordMessage,
missingPartialKeywordMessage,
"Usage",

View File

@@ -94,7 +94,7 @@ namespace Robust.Client.Audio.Midi
get => _volume;
set
{
if (MathHelper.CloseTo(_volume, value))
if (MathHelper.CloseToPercent(_volume, value))
return;
_cfgMan.SetCVar(CVars.MidiVolume, value);

View File

@@ -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();
}

View File

@@ -24,7 +24,6 @@ namespace Robust.Client.Console.Commands
var entityUid = EntityUid.Parse(args[0]);
var componentName = args[1];
var compManager = IoCManager.Resolve<IComponentManager>();
var compFactory = IoCManager.Resolve<IComponentFactory>();
var entityManager = IoCManager.Resolve<IEntityManager>();
@@ -33,7 +32,7 @@ namespace Robust.Client.Console.Commands
component.Owner = entity;
compManager.AddComponent(entity, component);
entityManager.AddComponent(entity, component);
}
}
@@ -55,12 +54,12 @@ namespace Robust.Client.Console.Commands
var entityUid = EntityUid.Parse(args[0]);
var componentName = args[1];
var compManager = IoCManager.Resolve<IComponentManager>();
var entManager = IoCManager.Resolve<IEntityManager>();
var compFactory = IoCManager.Resolve<IComponentFactory>();
var registration = compFactory.GetRegistration(componentName);
compManager.RemoveComponent(entityUid, registration.Type);
entManager.RemoveComponent(entityUid, registration.Type);
}
}
}

View File

@@ -460,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");
@@ -476,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);
}
@@ -487,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;
}
@@ -504,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;
}
@@ -627,8 +632,9 @@ namespace Robust.Client.Console.Commands
}
});
tabContainer.AddChild(new HSplitContainer
tabContainer.AddChild(new SplitContainer
{
Orientation = SplitContainer.SplitOrientation.Horizontal,
Children =
{
new PanelContainer

View File

@@ -0,0 +1,19 @@
#if DEBUG
using Robust.Client.Debugging;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
namespace Robust.Client.Console.Commands
{
public sealed class DebugAnchoredCommand : IConsoleCommand
{
public string Command => "showanchored";
public string Description => $"Shows anchored entities on a particular tile";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugAnchoringSystem>().Enabled ^= true;
}
}
}
#endif

View File

@@ -0,0 +1,96 @@
#if DEBUG
using System.Text;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Client.Debugging
{
public sealed class DebugAnchoringSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
private Label? _label;
private (GridId GridId, TileRef Tile)? _hovered;
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
if (_enabled)
{
_label = new Label();
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(_label);
}
else
{
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.RemoveChild(_label!);
_label = null;
_hovered = null;
}
}
}
private bool _enabled;
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
if (!Enabled) return;
if (_label == null)
{
DebugTools.Assert($"Debug Label for {nameof(DebugAnchoringSystem)} is null!");
return;
}
var mouseSpot = _inputManager.MouseScreenPosition;
var spot = _eyeManager.ScreenToMap(mouseSpot);
if (!_mapManager.TryFindGridAt(spot, out var grid))
{
_label.Text = string.Empty;
_hovered = null;
return;
}
var tile = grid.GetTileRef(spot);
_label.Position = mouseSpot.Position + new Vector2(32, 0);
if (_hovered?.GridId == grid.Index && _hovered?.Tile == tile) return;
_hovered = (grid.Index, tile);
var text = new StringBuilder();
foreach (var ent in grid.GetAnchoredEntities(spot))
{
if (!EntityManager.TryGetEntity(ent, out var entity))
{
text.AppendLine($"uid: {ent}, invalid");
}
else
{
text.AppendLine($"uid: {ent}, {entity.Name}");
}
}
_label.Text = text.ToString();
}
}
}
#endif

View File

@@ -158,6 +158,7 @@ namespace Robust.Client.Debugging
_hoverStartScreen = mouseScreenPos.Position;
var viewport = _eyeManager.GetWorldViewport();
var viewBounds = _eyeManager.GetWorldViewbounds();
if (viewport.IsEmpty()) return;
@@ -165,7 +166,7 @@ namespace Robust.Client.Debugging
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;
@@ -175,12 +176,18 @@ namespace Robust.Client.Debugging
var worldBox = physBody.GetWorldAABB();
if (worldBox.IsEmpty()) continue;
var pTransform = physBody.GetTransform();
foreach (var fixture in physBody.Fixtures)
{
var shape = fixture.Shape;
var sleepPercent = physBody.Awake ? 0.0f : 1.0f;
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, sleepPercent);
drawing.SetTransform(in Matrix3.Identity);
var aabb = shape.ComputeAABB(pTransform, 0);
worldHandle.DrawRect(aabb, Color.Blue, false);
}
foreach (var joint in physBody.Joints)

View File

@@ -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();

View File

@@ -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>

View File

@@ -28,8 +28,6 @@ namespace Robust.Client.GameObjects
RegisterClass<OccluderTreeComponent>();
RegisterClass<EyeComponent>();
RegisterClass<AppearanceComponent>();
RegisterClass<AppearanceTestComponent>();
RegisterClass<SnapGridComponent>();
RegisterClass<AnimationPlayerComponent>();
RegisterClass<TimerComponent>();

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Client.Animations;
using Robust.Shared.GameObjects;
using static Robust.Client.Animations.AnimationPlaybackShared;
@@ -12,11 +11,17 @@ namespace Robust.Client.GameObjects
/// </summary>
public sealed class AnimationPlayerComponent : Component
{
// TODO: Give this component a friend someday. Way too much content shit to change atm ._.
public override string Name => "AnimationPlayer";
private readonly Dictionary<string, AnimationPlayback> _playingAnimations
public int PlayingAnimationCount => PlayingAnimations.Count;
internal readonly Dictionary<string, AnimationPlayback> PlayingAnimations
= new();
internal bool HasPlayingAnimation = false;
/// <summary>
/// Start playing an animation.
/// </summary>
@@ -26,50 +31,31 @@ namespace Robust.Client.GameObjects
/// </param>
public void Play(Animation animation, string key)
{
EntitySystem.Get<AnimationPlayerSystem>().AddComponent(this);
var playback = new AnimationPlayback(animation);
_playingAnimations.Add(key, playback);
PlayingAnimations.Add(key, playback);
}
public bool HasRunningAnimation(string key)
{
return _playingAnimations.ContainsKey(key);
return PlayingAnimations.ContainsKey(key);
}
public void Stop(string key)
{
_playingAnimations.Remove(key);
PlayingAnimations.Remove(key);
}
internal void Update(float frameTime)
/// <summary>
/// Temporary method until the event is replaced with eventbus.
/// </summary>
internal void AnimationComplete(string key)
{
if (_playingAnimations.Count == 0)
{
return;
}
List<string>? toRemove = null;
// TODO: Get rid of this ToArray() allocation.
foreach (var (key, playback) in _playingAnimations.ToArray())
{
var keep = UpdatePlayback(Owner, playback, frameTime);
if (!keep)
{
toRemove ??= new List<string>();
toRemove.Add(key);
}
}
if (toRemove != null)
{
foreach (var key in toRemove)
{
_playingAnimations.Remove(key);
AnimationCompleted?.Invoke(key);
}
}
AnimationCompleted?.Invoke(key);
}
[Obsolete("Use AnimationCompletedEvent instead")]
public event Action<string>? AnimationCompleted;
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -80,7 +80,7 @@ namespace Robust.Client.GameObjects
var position = Owner.Transform.Coordinates;
foreach (var neighbor in grid.GetInDir(position, dir))
{
if (Owner.EntityManager.ComponentManager.TryGetComponent(neighbor, out ClientOccluderComponent? comp) && comp.Enabled)
if (Owner.EntityManager.TryGetComponent(neighbor, out ClientOccluderComponent? comp) && comp.Enabled)
{
Occluding |= oclDir;
break;

View File

@@ -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;
if (MathHelper.CloseToPercent(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
@@ -239,7 +163,7 @@ namespace Robust.Client.GameObjects
}
}
internal sealed class PointLightUpdateEvent : EntityEventArgs
public sealed class PointLightUpdateEvent : EntityEventArgs
{
}

View File

@@ -228,6 +228,6 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Calculate sprite bounding box in world-space coordinates.
/// </summary>
Box2 CalculateBoundingBox();
Box2 CalculateBoundingBox(Vector2 worldPos);
}
}

View File

@@ -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));
}
}
}
}
}

View File

@@ -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,27 +1246,29 @@ 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
{
angle = CalcRectWorldAngle(worldRotation, numDirs);
angle = CalcRectWorldAngle(worldRotation + eyeRotation, numDirs) - eyeRotation;
}
var sWorldRotation = angle;
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,17 +1281,17 @@ 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);
RenderLayer(drawingHandle, layer, worldRotation, overrideDirection);
RenderLayer(drawingHandle, layer, eyeRotation, worldRotation, overrideDirection);
}
}
private void RenderLayer(DrawingHandleWorld drawingHandle, Layer layer, Angle worldRotation, Direction? overrideDirection)
private void RenderLayer(DrawingHandleWorld drawingHandle, Layer layer, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection)
{
var texture = GetRenderTexture(layer, worldRotation, overrideDirection);
var texture = GetRenderTexture(layer, worldRotation + eyeRotation, overrideDirection);
if (layer.Shader != null)
{
@@ -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)

View File

@@ -15,11 +15,13 @@ namespace Robust.Client.GameObjects
private static Box2 SpriteAabbFunc(in SpriteComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
var worldRot = value.Owner.Transform.WorldRotation;
var bounds = new Box2Rotated(value.CalculateBoundingBox(worldPos), worldRot, worldPos);
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var localAABB = tree?.Owner.Transform.InvWorldMatrix.TransformBox(bounds) ?? bounds.CalcBoundingBox();
return new Box2(pos, pos);
return localAABB;
}
private static Box2 LightAabbFunc(in PointLightComponent value)
@@ -28,30 +30,34 @@ namespace Robust.Client.GameObjects
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var boxSize = value.Radius * 2;
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var localPos = tree?.Owner.Transform.InvWorldMatrix.Transform(worldPos) ?? worldPos;
return Box2.CenteredAround(pos, (boxSize, boxSize));
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);
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var localAABB = tree?.Owner.Transform.InvWorldMatrix.TransformBox(bounds) ?? bounds.CalcBoundingBox();
return new Box2(pos, pos);
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;
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var localPos = tree?.Owner.Transform.InvWorldMatrix.Transform(worldPos.Value) ?? worldPos.Value;
return Box2.CenteredAround(pos, (boxSize, boxSize));
return Box2.CenteredAround(localPos, (boxSize, boxSize));
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
@@ -85,6 +86,10 @@ namespace Robust.Client.GameObjects
SendMessage(new CloseBoundInterfaceMessage(), uiKey);
_openInterfaces.Remove(uiKey);
boundUserInterface.Dispose();
var playerSession = IoCManager.Resolve<IPlayerManager>().LocalPlayer?.Session;
if(playerSession != null)
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new BoundUIClosedEvent(uiKey, Owner.Uid, playerSession));
}
internal void SendMessage(BoundUserInterfaceMessage message, object uiKey)

View File

@@ -1,15 +1,98 @@
using System.Collections.Generic;
using Robust.Client.Animations;
using Robust.Shared.GameObjects;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
internal sealed class AnimationPlayerSystem : EntitySystem
public sealed class AnimationPlayerSystem : EntitySystem
{
private readonly List<AnimationPlayerComponent> _activeAnimations = new();
public override void FrameUpdate(float frameTime)
{
foreach (var animationPlayerComponent in EntityManager.ComponentManager.EntityQuery<AnimationPlayerComponent>(true))
for (var i = _activeAnimations.Count - 1; i >= 0; i--)
{
animationPlayerComponent.Update(frameTime);
var anim = _activeAnimations[i];
if (!Update(anim, frameTime)) continue;
_activeAnimations.RemoveSwap(i);
anim.HasPlayingAnimation = false;
}
}
internal void AddComponent(AnimationPlayerComponent component)
{
if (component.HasPlayingAnimation) return;
_activeAnimations.Add(component);
component.HasPlayingAnimation = true;
}
private bool Update(AnimationPlayerComponent component, float frameTime)
{
if (component.PlayingAnimationCount == 0 ||
component.Deleted)
return true;
var remie = new RemQueue<string>();
foreach (var (key, playback) in component.PlayingAnimations)
{
var keep = AnimationPlaybackShared.UpdatePlayback(component.Owner, playback, frameTime);
if (!keep)
{
remie.Add(key);
}
}
foreach (var key in remie)
{
component.PlayingAnimations.Remove(key);
EntityManager.EventBus.RaiseLocalEvent(component.Owner.Uid, new AnimationCompletedEvent {Uid = component.Owner.Uid, Key = key});
component.AnimationComplete(key);
}
return false;
}
/// <summary>
/// Start playing an animation.
/// </summary>
public void Play(EntityUid uid, Animation animation, string key)
{
var component = EntityManager.EnsureComponent<AnimationPlayerComponent>(EntityManager.GetEntity(uid));
Play(component, animation, key);
}
/// <summary>
/// Start playing an animation.
/// </summary>
public void Play(AnimationPlayerComponent component, Animation animation, string key)
{
AddComponent(component);
var playback = new AnimationPlaybackShared.AnimationPlayback(animation);
component.PlayingAnimations.Add(key, playback);
}
public bool HasRunningAnimation(EntityUid uid, string key)
{
return EntityManager.TryGetComponent(uid, out AnimationPlayerComponent? component) &&
component.PlayingAnimations.ContainsKey(key);
}
public bool HasRunningAnimation(AnimationPlayerComponent component, string key)
{
return component.PlayingAnimations.ContainsKey(key);
}
public void Stop(AnimationPlayerComponent component, string key)
{
component.PlayingAnimations.Remove(key);
}
}
public sealed class AnimationCompletedEvent : EntityEventArgs
{
public EntityUid Uid { get; init; }
public string Key { get; init; } = string.Empty;
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
@@ -96,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)
@@ -139,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)
{
@@ -153,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)
@@ -217,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);
@@ -263,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);
@@ -310,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);
@@ -325,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;
}
@@ -338,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();
@@ -362,6 +443,12 @@ namespace Robust.Client.GameObjects
return Play(filename, entity, audioParams);
}
public IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityUid uid, AudioParams? audioParams = null)
{
return EntityManager.TryGetEntity(uid, out var entity)
? Play(filename, entity, audioParams) : null;
}
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
{

View File

@@ -52,7 +52,7 @@ namespace Robust.Client.GameObjects
while (_dirtyEntities.TryDequeue(out var entity))
{
if (EntityManager.EntityExists(entity)
&& ComponentManager.TryGetComponent(entity, out ClientOccluderComponent? occluder)
&& EntityManager.TryGetComponent(entity, out ClientOccluderComponent? occluder)
&& occluder.UpdateGeneration != _updateGeneration)
{
occluder.Update();
@@ -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();
}
@@ -100,7 +100,7 @@ namespace Robust.Client.GameObjects
{
foreach (var entity in candidates)
{
if (ComponentManager.HasComponent<ClientOccluderComponent>(entity))
if (EntityManager.HasComponent<ClientOccluderComponent>(entity))
{
_dirtyEntities.Enqueue(entity);
}

View File

@@ -48,7 +48,7 @@ namespace Robust.Client.GameObjects
container.Insert(ev.Entity);
}
private void HandleComponentState(EntityUid uid, ContainerManagerComponent component, ComponentHandleState args)
private void HandleComponentState(EntityUid uid, ContainerManagerComponent component, ref ComponentHandleState args)
{
if (args.Current is not ContainerManagerComponentState cast)
return;
@@ -59,6 +59,7 @@ namespace Robust.Client.GameObjects
{
if (!cast.ContainerSet.Any(data => data.Id == id))
{
container.EmptyContainer(true);
container.Shutdown();
toDelete ??= new List<string>();
toDelete.Add(id);

View File

@@ -60,7 +60,6 @@ namespace Robust.Client.GameObjects
public override void Shutdown()
{
base.Shutdown();
UnsubscribeNetworkEvent<SendGridTileLookupMessage>();
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.RemoveChild(_label);
}

View File

@@ -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));
}

View File

@@ -2,10 +2,12 @@
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;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
#nullable enable
@@ -20,12 +22,12 @@ 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 IMapManager _mapManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
private bool _isLerping = false;
/// <inheritdoc />
public override void Initialize()
@@ -55,6 +57,9 @@ namespace Robust.Client.GameObjects
public override void FrameUpdate(float frameTime)
{
var currentEye = _eyeManager.CurrentEye;
// TODO: Content should have its own way of handling this. We should have a default behavior that they can overwrite.
/*
var inputSystem = EntitySystemManager.GetEntitySystem<InputSystem>();
var direction = 0;
@@ -74,20 +79,26 @@ 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 playerTransform = _playerManager.LocalPlayer?.ControlledEntity?.Transform;
if (playerTransform == null) return;
var gridId = playerTransform.GridID;
var parent = gridId != GridId.Invalid && EntityManager.TryGetEntity(_mapManager.GetGrid(gridId).GridEntityId, out var gridEnt) ?
gridEnt.Transform
: _mapManager.GetMapEntity(playerTransform.MapID).Transform;
if (!_isLerping)
{
// TODO: Detect parent change and start lerping
var parentRotation = parent.WorldRotation;
currentEye.Rotation = -parentRotation;
}
foreach (var eyeComponent in EntityManager.ComponentManager.EntityQuery<EyeComponent>(true))
foreach (var eyeComponent in EntityManager.EntityQuery<EyeComponent>(true))
{
eyeComponent.UpdateEyePosition();
}

View File

@@ -64,25 +64,21 @@ namespace Robust.Client.GameObjects
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;
if (!_entityManager.TryGetComponent<PhysicsComponent>(gridEnt.Uid, out var body)) continue;
foreach (var (_, chunk) in mapGrid.GetMapChunks())
var transform = body.GetTransform();
foreach (var fixture in body.Fixtures)
{
var chunkBounds = chunk.CalcWorldBounds(worldPos, worldRot);
var aabb = chunkBounds.CalcBoundingBox();
// Calc world bounds for chunk.
if (!aabb.Intersects(in viewport))
for (var i = 0; i < fixture.Shape.ChildCount; i++)
{
continue;
}
var aabb = fixture.Shape.ComputeAABB(transform, i);
args.WorldHandle.DrawRect(chunkBounds, Color.Green.WithAlpha(0.2f), true);
args.WorldHandle.DrawRect(aabb, Color.Red, false);
args.WorldHandle.DrawRect(aabb, Color.Green.WithAlpha(0.2f));
args.WorldHandle.DrawRect(aabb, Color.Red.WithAlpha(0.5f), false);
}
}
}
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
internal sealed class GridFixtureSystem : SharedGridFixtureSystem
{
}
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
internal sealed class MapSystem : SharedMapSystem
{
}
}

View 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;
}
}
}

View File

@@ -37,6 +37,18 @@ 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;
@@ -49,22 +61,6 @@ namespace Robust.Client.GameObjects
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;
}
}
public override void Initialize()
{
base.Initialize();
@@ -108,7 +104,7 @@ namespace Robust.Client.GameObjects
QueueSpriteUpdate(component);
}
private void AnythingMoved(MoveEvent args)
private void AnythingMoved(ref MoveEvent args)
{
AnythingMovedSubHandler(args.Sender.Transform);
}
@@ -146,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);
}
@@ -179,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);
}
@@ -267,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();
@@ -274,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;
@@ -335,7 +336,6 @@ 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);
// If we're on a new map then clear the old one.

View File

@@ -14,7 +14,6 @@ namespace Robust.Client.GameObjects
public class SpriteSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly RenderingTreeSystem _treeSystem = default!;
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
@@ -39,9 +38,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)
@@ -51,7 +48,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) =>
{

View File

@@ -1,11 +1,15 @@
using JetBrains.Annotations;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Client.GameObjects
{
[UsedImplicitly]
public sealed class UserInterfaceSystem : EntitySystem
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
public override void Initialize()
{
base.Initialize();
@@ -24,7 +28,18 @@ namespace Robust.Client.GameObjects
private void MessageReceived(BoundUIWrapMessage ev)
{
var cmp = ComponentManager.GetComponent<ClientUserInterfaceComponent>(ev.Entity);
var uid = ev.Entity;
var cmp = EntityManager.GetComponent<ClientUserInterfaceComponent>(uid);
var message = ev.Message;
// This should probably not happen at this point, but better make extra sure!
if(_playerManager.LocalPlayer != null)
message.Session = _playerManager.LocalPlayer.Session;
message.Entity = uid;
message.UiKey = ev.UiKey;
// Raise as object so the correct type is used.
RaiseLocalEvent(uid, (object)message);
cmp.MessageReceived(ev);
}

View File

@@ -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,7 +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 IComponentManager _componentManager = default!;
[Dependency] private readonly IClientEntityManager _entityManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
#if EXCEPTION_TOLERANCE
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
@@ -334,8 +335,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.
@@ -353,7 +352,7 @@ namespace Robust.Client.GameStates
}
// TODO: handle component deletions/creations.
foreach (var (netId, comp) in _componentManager.GetNetComponents(entity.Uid))
foreach (var (netId, comp) in _entityManager.GetNetComponents(entity.Uid))
{
DebugTools.AssertNotNull(netId);
@@ -364,7 +363,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,14 +381,16 @@ 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>();
outputData.Add(createdEntity, compData);
foreach (var (netId, component) in _componentManager.GetNetComponents(createdEntity))
foreach (var (netId, component) in _entityManager.GetNetComponents(createdEntity))
{
var state = component.GetComponentState(player);
var state = _entityManager.GetComponentState(bus, component, player);
if(state.GetType() == typeof(ComponentState))
continue;
@@ -472,14 +474,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, _entities.EventBus, kvStates.Value.Item1,
kvStates.Value.Item2);
}
@@ -547,7 +547,7 @@ namespace Robust.Client.GameStates
return created;
}
private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityEventBus bus, EntityState? curState,
private void HandleEntityState(IEntity entity, IEventBus bus, EntityState? curState,
EntityState? nextState)
{
var compStateWork = new Dictionary<ushort, (ComponentState? curState, ComponentState? nextState)>();
@@ -559,21 +559,21 @@ namespace Robust.Client.GameStates
{
if (compChange.Deleted)
{
if (compMan.TryGetComponent(entityUid, compChange.NetID, out var comp))
if (_entityManager.TryGetComponent(entityUid, compChange.NetID, out var comp))
{
compMan.RemoveComponent(entityUid, comp);
_entityManager.RemoveComponent(entityUid, comp);
}
}
else
{
//Right now we just assume every state from an unseen entity is added
if (compMan.HasComponent(entityUid, compChange.NetID))
if (_entityManager.HasComponent(entityUid, compChange.NetID))
continue;
var newComp = (Component) _compFactory.GetComponent(compChange.NetID);
newComp.Owner = entity;
compMan.AddComponent(entity, newComp, true);
_entityManager.AddComponent(entity, newComp, true);
compStateWork[compChange.NetID] = (compChange.State, null);
}
@@ -605,11 +605,12 @@ namespace Robust.Client.GameStates
foreach (var (netId, (cur, next)) in compStateWork)
{
if (compMan.TryGetComponent(entityUid, (ushort) netId, out var component))
if (_entityManager.TryGetComponent(entityUid, (ushort) netId, out var component))
{
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)

View File

@@ -1,19 +1,19 @@
using Robust.Shared.Enums;
using Robust.Client.Graphics;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using System;
using Robust.Shared.Containers;
using Robust.Shared.Timing;
namespace Robust.Client.GameStates
{
internal class NetInterpOverlay : Overlay
{
[Dependency] private readonly IComponentManager _componentManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -32,13 +32,13 @@ namespace Robust.Client.GameStates
handle.UseShader(_shader);
var worldHandle = (DrawingHandleWorld) handle;
var viewport = _eyeManager.GetWorldViewport();
foreach (var boundingBox in _componentManager.EntityQuery<IPhysBody>(true))
foreach (var boundingBox in _entityManager.EntityQuery<IPhysBody>(true))
{
// all entities have a TransformComponent
var transform = ((IComponent)boundingBox).Owner.Transform;
var transform = boundingBox.Owner.Transform;
// if not on the same map, continue
if (transform.MapID != _eyeManager.CurrentMap || !transform.IsMapTransform)
if (transform.MapID != _eyeManager.CurrentMap || boundingBox.Owner.IsInContainer())
continue;
// This entity isn't lerping, no need to draw debug info for it

View File

@@ -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)
{

View File

@@ -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.

View File

@@ -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();
@@ -831,7 +917,7 @@ namespace Robust.Client.Graphics.Clyde
{
if (SourceHandle == null) return;
if (!disposing || Thread.CurrentThread != _master._gameThread)
if (!_master.IsMainThread())
{
// We can't run this code inside another thread so tell Clyde to clear it up later.
_master.DeleteBufferedSourceOnMainThread(SourceHandle.Value, FilterHandle);
@@ -850,6 +936,11 @@ namespace Robust.Client.Graphics.Clyde
SourceHandle = null;
}
private bool _isDisposed()
{
return SourceHandle == null;
}
private void _checkDisposed()
{
if (SourceHandle == null)

View File

@@ -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,
}
}
}

View File

@@ -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;

View 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,
}
}
}

View File

@@ -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
{
@@ -10,6 +9,8 @@ namespace Robust.Client.Graphics.Clyde
// OpenGL feature detection go here.
private bool _hasGLKhrDebug;
private bool _glDebuggerPresent;
// As per the extension specification, when implemented as extension in an ES context,
// function names have to be suffixed by "KHR"
// This keeps track of whether that's necessary.
@@ -24,6 +25,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;
@@ -57,6 +59,8 @@ namespace Robust.Client.Graphics.Clyde
{
var extensions = GetGLExtensions();
CheckGLDebuggerStatus(extensions);
_sawmillOgl.Debug("OpenGL capabilities:");
if (!_isGLES)
@@ -72,6 +76,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 +104,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}");
@@ -127,6 +153,19 @@ namespace Robust.Client.Graphics.Clyde
}
}
private void CheckGLDebuggerStatus(HashSet<string> extensions)
{
if (!extensions.Contains("GL_EXT_debug_tool"))
return;
const int GL_DEBUG_TOOL_EXT = 0x6789;
const int GL_DEBUG_TOOL_NAME_EXT = 0x678A;
_glDebuggerPresent = GL.IsEnabled((EnableCap)GL_DEBUG_TOOL_EXT);
var name = GL.GetString((StringName)GL_DEBUG_TOOL_NAME_EXT);
_sawmillOgl.Debug($"OpenGL debugger present: {name}");
}
private void RegisterBlockCVars()
{
string[] cvars =
@@ -141,7 +180,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)

View File

@@ -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,7 +20,7 @@ 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(Viewport viewport, Box2 worldBounds, IEye eye)
private void _drawGrids(Viewport viewport, Box2Rotated worldBounds, IEye eye)
{
var mapId = eye.Position.MapId;
if (!_mapManager.MapExists(mapId))
@@ -39,7 +40,6 @@ namespace Robust.Client.Graphics.Clyde
gridProgram.SetUniform(UniIModUV, new Vector4(0, 0, 1, 1));
gridProgram.SetUniform(UniIModulate, Color.White);
var compMan = _entityManager.ComponentManager;
foreach (var mapGrid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
{
var grid = (IMapGridInternal) mapGrid;
@@ -49,19 +49,11 @@ namespace Robust.Client.Graphics.Clyde
continue;
}
var transform = compMan.GetComponent<ITransformComponent>(grid.GridEntityId);
var transform = _entityManager.GetComponent<ITransformComponent>(grid.GridEntityId);
gridProgram.SetUniform(UniIModelMatrix, transform.WorldMatrix);
var worldPos = transform.WorldPosition;
var worldRot = transform.WorldRotation;
foreach (var (_, chunk) in grid.GetMapChunks())
foreach (var chunk in grid.GetMapChunks(worldBounds))
{
// Calc world bounds for chunk.
if (!chunk.CalcWorldAABB(worldPos, worldRot).Intersects(in worldBounds))
{
continue;
}
if (_isChunkDirty(grid, chunk))
{
_updateChunkMesh(grid, chunk);
@@ -211,11 +203,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)
{

View File

@@ -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,7 +201,7 @@ namespace Robust.Client.Graphics.Clyde
}
private void DrawEntities(Viewport viewport, Box2 worldBounds, IEye eye)
private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 worldAABB, IEye eye)
{
var mapId = eye.Position.MapId;
if (mapId == MapId.Nullspace || !_mapManager.HasMapEntity(mapId))
@@ -209,16 +209,11 @@ namespace Robust.Client.Graphics.Clyde
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(mapId, widerBounds, _drawingSpriteList);
ProcessSpriteEntities(mapId, worldBounds, _drawingSpriteList);
var worldOverlays = new List<Overlay>();
@@ -266,7 +261,7 @@ namespace Robust.Client.Graphics.Clyde
null,
viewport,
new UIBox2i((0, 0), viewport.Size),
worldBounds);
worldAABB);
overlayIndex = j;
continue;
}
@@ -274,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;
@@ -327,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)
{
@@ -367,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) =>
{
@@ -385,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, true);
}
}
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.
@@ -414,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);
@@ -457,16 +458,17 @@ 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"))
@@ -477,10 +479,11 @@ namespace Robust.Client.Graphics.Clyde
// We will also render worldspace overlays here so we can do them under / above entities as necessary
using (DebugGroup("Entities"))
{
DrawEntities(viewport, worldBounds, eye);
DrawEntities(viewport, worldBounds, worldAABB, eye);
}
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowFOV, worldBounds);
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowFOV, worldAABB);
FlushRenderQueue();
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawFov)
{
@@ -513,24 +516,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();

View File

@@ -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}'!");

View File

@@ -4,8 +4,8 @@ using System.Buffers;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Shared;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -44,6 +44,7 @@ namespace Robust.Client.Graphics.Clyde
private ClydeHandle _fovShaderHandle;
private ClydeHandle _fovLightShaderHandle;
private ClydeHandle _wallBleedBlurShaderHandle;
private ClydeHandle _lightBlurShaderHandle;
private ClydeHandle _mergeWallLayerShaderHandle;
// Sampler used to sample the FovTexture with linear filtering, used in the lighting FOV pass
@@ -199,6 +200,7 @@ namespace Robust.Client.Graphics.Clyde
_fovShaderHandle = LoadShaderHandle("/Shaders/Internal/fov.swsl");
_fovLightShaderHandle = LoadShaderHandle("/Shaders/Internal/fov-lighting.swsl");
_wallBleedBlurShaderHandle = LoadShaderHandle("/Shaders/Internal/wall-bleed-blur.swsl");
_lightBlurShaderHandle = LoadShaderHandle("/Shaders/Internal/light-blur.swsl");
_mergeWallLayerShaderHandle = LoadShaderHandle("/Shaders/Internal/wall-merge.swsl");
}
@@ -317,7 +319,7 @@ 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)
{
@@ -332,9 +334,10 @@ namespace Robust.Client.Graphics.Clyde
return;
}
var (lights, count, expandedBounds) = GetLightsToRender(mapId, worldBounds);
var (lights, count, expandedBounds) = GetLightsToRender(mapId, worldBounds, worldAABB);
eye.GetViewMatrix(out var eyeTransform, eye.Scale);
UpdateOcclusionGeometry(mapId, expandedBounds, eye.Position.Position);
UpdateOcclusionGeometry(mapId, expandedBounds, eyeTransform);
DrawFov(viewport, eye);
@@ -431,13 +434,13 @@ namespace Robust.Client.Graphics.Clyde
lightShader.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
}
if (!MathHelper.CloseTo(lastRange, component.Radius))
if (!MathHelper.CloseToPercent(lastRange, component.Radius))
{
lastRange = component.Radius;
lightShader.SetUniformMaybe("lightRange", lastRange);
}
if (!MathHelper.CloseTo(lastPower, component.Energy))
if (!MathHelper.CloseToPercent(lastPower, component.Energy))
{
lastPower = component.Energy;
lightShader.SetUniformMaybe("lightPower", lastPower);
@@ -449,7 +452,7 @@ namespace Robust.Client.Graphics.Clyde
lightShader.SetUniformMaybe("lightColor", lastColor);
}
if (_enableSoftShadows && !MathHelper.CloseTo(lastSoftness, component.Softness))
if (_enableSoftShadows && !MathHelper.CloseToPercent(lastSoftness, component.Softness))
{
lastSoftness = component.Softness;
lightShader.SetUniformMaybe("lightSoftness", lastSoftness);
@@ -484,6 +487,9 @@ namespace Robust.Client.Graphics.Clyde
CheckGlError();
if (_cfg.GetCVar(CVars.DisplayBlurLight))
BlurLights(viewport, eye);
BlurOntoWalls(viewport, eye);
MergeWallLayer(viewport);
@@ -498,39 +504,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;
@@ -555,7 +563,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++)
{
@@ -566,6 +574,69 @@ namespace Robust.Client.Graphics.Clyde
return (_lightsToRenderList, state.count, expandedBounds);
}
private void BlurLights(Viewport viewport, IEye eye)
{
using var _ = DebugGroup(nameof(BlurLights));
GL.Disable(EnableCap.Blend);
CheckGlError();
CalcScreenMatrices(viewport.Size, out var proj, out var view);
SetProjViewBuffer(proj, view);
var shader = _loadedShaders[_lightBlurShaderHandle].Program;
shader.Use();
SetupGlobalUniformsImmediate(shader, viewport.LightRenderTarget.Texture);
var size = viewport.LightRenderTarget.Size;
shader.SetUniformMaybe("size", (Vector2)size);
shader.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
GL.Viewport(0, 0, size.X, size.Y);
CheckGlError();
// Initially we're pulling from the light render target.
// So we set it out of the loop so
// _wallBleedIntermediateRenderTarget2 gets bound at the end of the loop body.
SetTexture(TextureUnit.Texture0, viewport.LightRenderTarget.Texture);
// Have to scale the blurring radius based on viewport size and camera zoom.
const float refCameraHeight = 14;
var facBase = _cfg.GetCVar(CVars.DisplayBlurLightFactor);
var cameraSize = eye.Zoom.Y * viewport.Size.Y * (1 / viewport.RenderScale.Y) / EyeManager.PixelsPerMeter;
// 7e-3f is just a magic factor that makes it look ok.
var factor = facBase * (refCameraHeight / cameraSize);
// Multi-iteration gaussian blur.
for (var i = 3; i > 0; i--)
{
var scale = (i + 1) * factor;
// Set factor.
shader.SetUniformMaybe("radius", scale);
BindRenderTargetFull(viewport.LightBlurTarget);
// Blur horizontally to _wallBleedIntermediateRenderTarget1.
shader.SetUniformMaybe("direction", Vector2.UnitX);
_drawQuad(Vector2.Zero, viewport.Size, Matrix3.Identity, shader);
SetTexture(TextureUnit.Texture0, viewport.LightBlurTarget.Texture);
BindRenderTargetFull(viewport.LightRenderTarget);
// Blur vertically to _wallBleedIntermediateRenderTarget2.
shader.SetUniformMaybe("direction", Vector2.UnitY);
_drawQuad(Vector2.Zero, viewport.Size, Matrix3.Identity, shader);
SetTexture(TextureUnit.Texture0, viewport.LightRenderTarget.Texture);
}
GL.Enable(EnableCap.Blend);
CheckGlError();
// We didn't trample over the old _currentMatrices so just roll it back.
SetProjViewBuffer(_currentMatrixProj, _currentMatrixView);
}
private void BlurOntoWalls(Viewport viewport, IEye eye)
{
using var _ = DebugGroup(nameof(BlurOntoWalls));
@@ -748,7 +819,7 @@ namespace Robust.Client.Graphics.Clyde
_drawQuad(a, b, Matrix3.Identity, shader);
}
private void UpdateOcclusionGeometry(MapId map, Box2 expandedBounds, Vector2 eyePosition)
private void UpdateOcclusionGeometry(MapId map, Box2 expandedBounds, Matrix3 eyeTransform)
{
// This method generates two sets of occlusion geometry:
// 3D geometry used during depth projection.
@@ -782,9 +853,7 @@ namespace Robust.Client.Graphics.Clyde
foreach (var comp in occluderSystem.GetOccluderTrees(map, expandedBounds))
{
// TODO: I know this doesn't work with rotated grids but when I come back to these I'm adding tests
// because rotation bugs are common.
var treeBounds = expandedBounds.Translated(-comp.Owner.Transform.WorldPosition);
var treeBounds = comp.Owner.Transform.InvWorldMatrix.TransformBox(expandedBounds);
comp.Tree.QueryAabb((in OccluderComponent sOccluder) =>
{
@@ -798,16 +867,16 @@ namespace Robust.Client.Graphics.Clyde
var worldTransform = transform.WorldMatrix;
var box = occluder.BoundingBox;
var (tlX, tlY) = worldTransform.Transform(box.TopLeft);
var (trX, trY) = worldTransform.Transform(box.TopRight);
var (brX, brY) = worldTransform.Transform(box.BottomRight);
var (blX, blY) = worldTransform.Transform(box.BottomLeft);
var tl = worldTransform.Transform(box.TopLeft);
var tr = worldTransform.Transform(box.TopRight);
var br = worldTransform.Transform(box.BottomRight);
var bl = worldTransform.Transform(box.BottomLeft);
// Faces.
var faceN = new Vector4(tlX, tlY, trX, trY);
var faceE = new Vector4(trX, trY, brX, brY);
var faceS = new Vector4(brX, brY, blX, blY);
var faceW = new Vector4(blX, blY, tlX, tlY);
var faceN = new Vector4(tl.X, tl.Y, tr.X, tr.Y);
var faceE = new Vector4(tr.X, tr.Y, br.X, br.Y);
var faceS = new Vector4(br.X, br.Y, bl.X, bl.Y);
var faceW = new Vector4(bl.X, bl.Y, tl.X, tl.Y);
//
// Buckle up.
@@ -840,10 +909,10 @@ namespace Robust.Client.Graphics.Clyde
//
// Calculate delta positions from camera.
var (dTlX, dTlY) = (tlX, tlY) - eyePosition;
var (dTrX, dTrY) = (trX, trY) - eyePosition;
var (dBlX, dBlY) = (blX, blY) - eyePosition;
var (dBrX, dBrY) = (brX, brY) - eyePosition;
var (dTlX, dTlY) = eyeTransform.Transform(tl);
var (dTrX, dTrY) = eyeTransform.Transform(tr);
var (dBlX, dBlY) = eyeTransform.Transform(bl);
var (dBrX, dBrY) = eyeTransform.Transform(br);
// Get which neighbors are occluding.
var no = (occluder.Occluding & OccluderDir.North) != 0;
@@ -904,10 +973,10 @@ namespace Robust.Client.Graphics.Clyde
}
// Generate mask geometry.
arrayMaskBuffer[ami + 0] = new Vector2(tlX, tlY);
arrayMaskBuffer[ami + 1] = new Vector2(trX, trY);
arrayMaskBuffer[ami + 2] = new Vector2(brX, brY);
arrayMaskBuffer[ami + 3] = new Vector2(blX, blY);
arrayMaskBuffer[ami + 0] = new Vector2(tl.X, tl.Y);
arrayMaskBuffer[ami + 1] = new Vector2(tr.X, tr.Y);
arrayMaskBuffer[ami + 2] = new Vector2(br.X, br.Y);
arrayMaskBuffer[ami + 3] = new Vector2(bl.X, bl.Y);
// Generate mask indices.
QuadBatchIndexWrite(indexMaskBuffer, ref imi, (ushort)ami);
@@ -967,6 +1036,11 @@ namespace Robust.Client.Graphics.Clyde
lightMapSampleParameters,
$"{viewport.Name}-{nameof(viewport.LightRenderTarget)}");
viewport.LightBlurTarget = CreateRenderTarget(lightMapSize,
new RenderTargetFormatParameters(lightMapColorFormat),
lightMapSampleParameters,
$"{viewport.Name}-{nameof(viewport.LightBlurTarget)}");
viewport.WallBleedIntermediateRenderTarget1 = CreateRenderTarget(lightMapSizeQuart, lightMapColorFormat,
lightMapSampleParameters,
$"{viewport.Name}-{nameof(viewport.WallBleedIntermediateRenderTarget1)}");

View File

@@ -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)
@@ -133,13 +133,20 @@ namespace Robust.Client.Graphics.Clyde
var oldProj = _clyde._currentMatrixProj;
var oldView = _clyde._currentMatrixView;
var oldModel = _clyde._currentMatrixModel;
var newModel = oldModel;
position += (oldModel.R0C2, oldModel.R1C2);
newModel.R0C2 = 0;
newModel.R1C2 = 0;
SetModelTransform(newModel);
// 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;
var ofsX = position.X - _clyde._currentRenderTarget.Size.X / 2f;
var ofsY = position.Y - _clyde._currentRenderTarget.Size.Y / 2f;
var view = Matrix3.Identity;
view.R0C0 = scale.X;
@@ -153,6 +160,7 @@ namespace Robust.Client.Graphics.Clyde
// Draw the entity.
sprite.Render(
DrawingHandleWorld,
Angle.Zero,
overrideDirection == null
? entity.Transform.WorldRotation
: Angle.Zero,
@@ -160,6 +168,7 @@ namespace Robust.Client.Graphics.Clyde
// Reset to screen space
SetProjView(oldProj, oldView);
SetModelTransform(oldModel);
}
public void DrawLine(Vector2 a, Vector2 b, Color color)
@@ -309,6 +318,11 @@ namespace Robust.Client.Graphics.Clyde
_renderHandle.DrawLine(@from, to, color * Modulate);
}
public override void RenderInRenderTarget(IRenderTarget target, Action a, Color clearColor = default)
{
_renderHandle.RenderInRenderTarget(target, a, clearColor);
}
public override void DrawRect(UIBox2 rect, Color color, bool filled = true)
{
if (filled)
@@ -331,6 +345,11 @@ namespace Robust.Client.Graphics.Clyde
_renderHandle.DrawTextureScreen(texture, rect.TopLeft, rect.TopRight,
rect.BottomLeft, rect.BottomRight, color, subRegion);
}
public override void DrawEntity(IEntity entity, Vector2 position, Vector2 scale, Direction? overrideDirection)
{
_renderHandle.DrawEntity(entity, position, scale, overrideDirection);
}
}
private sealed class DrawingHandleWorldImpl : DrawingHandleWorld
@@ -354,18 +373,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);
@@ -385,6 +403,11 @@ namespace Robust.Client.Graphics.Clyde
_renderHandle.DrawLine(@from, to, color * Modulate);
}
public override void RenderInRenderTarget(IRenderTarget target, Action a, Color clearColor = default)
{
_renderHandle.RenderInRenderTarget(target, a, clearColor);
}
public override void DrawRect(Box2 rect, Color color, bool filled = true)
{
if (filled)

View File

@@ -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
@@ -208,9 +205,11 @@ namespace Robust.Client.Graphics.Clyde
return;
}
DebugTools.Assert(renderTarget.FramebufferHandle != default);
DebugTools.Assert(!renderTarget.IsWindow, "Cannot delete window-backed render targets directly.");
GL.DeleteFramebuffer(renderTarget.FramebufferHandle.Handle);
renderTarget.FramebufferHandle = default;
CheckGlError();
_renderTargets.Remove(handle);
DeleteTexture(renderTarget.TextureHandle);
@@ -248,8 +247,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 +265,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.
@@ -370,7 +366,7 @@ namespace Robust.Client.Graphics.Clyde
protected override void Dispose(bool disposing)
{
if (disposing)
if (Clyde.IsMainThread())
{
Clyde.DeleteRenderTexture(Handle);
}
@@ -386,11 +382,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)
{
}
}

View File

@@ -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)
{

View File

@@ -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)

View File

@@ -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,9 +603,14 @@ 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)
if (_clyde.IsMainThread())
{
// Main thread, do direct GL deletion.
_clyde.DeleteTexture(TextureId);

View File

@@ -92,6 +92,8 @@ namespace Robust.Client.Graphics.Clyde
// Lighting is drawn into this. This then gets sampled later while rendering world-space stuff.
public RenderTexture LightRenderTarget = default!;
public RenderTexture LightBlurTarget = default!;
// Unused, to be removed.
public RenderTexture WallMaskRenderTarget = default!;

View File

@@ -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 IClydeWindow CreateWindow(WindowCreateParameters parameters)
{
DebugTools.AssertNotNull(_windowing);
DebugTools.AssertNotNull(_glContext);
DebugTools.AssertNotNull(_mainWindow);
return _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

View File

@@ -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,7 +25,6 @@ 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!;
@@ -65,21 +65,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()
@@ -101,6 +92,8 @@ namespace Robust.Client.Graphics.Clyde
public bool InitializePostWindowing()
{
_gameThread = Thread.CurrentThread;
InitGLContextManager();
if (!InitMainWindowAndRenderer())
return false;
@@ -153,13 +146,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);
@@ -183,7 +194,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();
@@ -222,14 +233,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()
@@ -325,7 +328,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()
@@ -438,10 +442,8 @@ namespace Robust.Client.Graphics.Clyde
return;
}
if (!_hasGLKhrDebug)
{
if (!_hasGLKhrDebug || !_glDebuggerPresent)
return;
}
if (_isGLKhrDebugESExtension)
{
@@ -468,10 +470,9 @@ namespace Robust.Client.Graphics.Clyde
[Conditional("DEBUG")]
private void PushDebugGroupMaybe(string group)
{
if (!_hasGLKhrDebug)
{
// ANGLE spams console log messages when using debug groups, so let's only use them if we're debugging GL.
if (!_hasGLKhrDebug || !_glDebuggerPresent)
return;
}
if (_isGLKhrDebugESExtension)
{
@@ -486,10 +487,8 @@ namespace Robust.Client.Graphics.Clyde
[Conditional("DEBUG")]
private void PopDebugGroupMaybe()
{
if (!_hasGLKhrDebug)
{
if (!_hasGLKhrDebug || !_glDebuggerPresent)
return;
}
if (_isGLKhrDebugESExtension)
{
@@ -503,8 +502,14 @@ namespace Robust.Client.Graphics.Clyde
public void Shutdown()
{
_glContext?.Shutdown();
ShutdownWindowing();
_shutdownAudio();
}
private bool IsMainThread()
{
return Thread.CurrentThread == _gameThread;
}
}
}

View File

@@ -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)
@@ -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));
}
}
}

View 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);
}
}

View 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;
}
}
}
}
*/

View 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);
}
}
}
}
}

View 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);
}
}
}
}
}
}

View 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