154 Commits
Author SHA1 Message Date
Simon Lübeß fd2b990a8f Reuse running Visual Studio instances 2026-08-16 14:30:24 +02:00
Simon Lübeß 29b6f37c2a Fixed memory leak 2026-08-15 13:52:45 +02:00
Simon Lübeß 188dc4b3b6 Better names for found Rider installs + context menu for copy/open path 2026-08-15 13:52:33 +02:00
Simon Lübeß 62ad8bee35 Auto detect all Visual Studio and Rider installs 2026-08-14 23:34:04 +02:00
Simon Lübeß f8e533f28d Almost entirely moved ImGui dependency (and settings) from Engine to Editor 2026-08-09 16:49:54 +02:00
Simon Lübeß faa3040471 Find Rider installations 2026-08-09 16:14:40 +02:00
Simon Lübeß 05187e6b9f Locate Visual Studio using vswhere 2026-08-02 17:33:14 +02:00
Simon Lübeß eaffc1a135 Fixed string deserialization in release builds 2026-08-02 16:28:59 +02:00
Simon Lübeß ec9fccee12 Don't touch LastOpenedScenes when we didn't save 2026-08-02 15:59:38 +02:00
Simon Lübeß 164384e55e Made not having a project loaded less crashy 2026-08-02 15:58:02 +02:00
Simon Lübeß 9b14cdddb9 At least don't crash when no project is opened 2026-08-02 12:34:26 +02:00
Simon Lübeß 85b3fd5bc9 Fix release configurations 2026-08-02 12:33:54 +02:00
Simon Lübeß c56db0f069 Added winget install instructions to readme 2026-08-02 12:04:41 +02:00
Simon Lübeß 8ccbc565ad Removed unnecessary reflection option 2026-08-02 12:03:59 +02:00
Simon Lübeß a14d86012a Finally debugged and fixed cleanup error in Font 2026-08-02 12:00:40 +02:00
Simon Lübeß 8b64afee0c Cleanup and refactor Font and FontRenderer 2026-08-02 12:00:08 +02:00
Simon Lübeß 344aabae2b Who thought Point was a good size unit for 3D text? 2026-08-02 11:59:19 +02:00
Simon Lübeß dec0a004a5 Preprocess and delete glyph shape 2026-08-02 11:58:39 +02:00
Simon Lübeß 4e314d1de2 Commit asset configs for resources without importer 2026-08-02 11:46:17 +02:00
Simon Lübeß 4157477882 Correctly handle errors of File.GetLastWriteTimeUtc 2026-08-01 23:06:29 +02:00
Simon Lübeß c65fa58940 Save asset configs for file types without importer 2026-08-01 23:06:02 +02:00
Simon Lübeß b5071e3499 Updated freetype, harfbuzz and msdfgen 2026-08-01 23:04:44 +02:00
Simon Lübeß 2f5baa5f5e Font rendering rework
Handle fonts unscaled
2026-08-01 22:55:35 +02:00
Simon Lübeß ab39386b27 Show texture dimensions in AsserViewer
- fixed color picker for background
2026-07-22 21:25:16 +02:00
Simon Lübeß 3095c8f0bb Provide build config when building Helper 2026-07-19 16:54:30 +02:00
Simon Lübeß e4e86bd21d Fixed initialization of EditorCamera
`this = default` overwrote `_cameraRotationSpeed` with 0 thus breaking user input
2026-07-19 14:27:18 +02:00
Simon Lübeß 8382060f8a Comment out wait for debugger 2026-07-19 14:22:31 +02:00
Simon Lübeß e076eabfa1 Added ToString to Quaternion 2026-07-19 14:22:02 +02:00
Simon Lübeß c8fdba6121 Fixed beef workspace build 2026-04-13 21:03:28 +02:00
Simon Lübeß 6935e9daf9 Setup WatchedSources for BuildTool Module "GlitchyEngineHelper" 2026-04-13 20:53:31 +02:00
Simon Lübeß 1e1ec7aaa9 Some changes
- Changed ModuleInfo to class, save Length for Timestamp-Hashes
- Obey recursive-flag for WatchedSources
- Verbosely skip up-to-date modules
2026-04-13 20:48:21 +02:00
Simon Lübeß 6bec99ab06 Adhere to ExcludedSubpaths while scanning directory for change 2026-04-13 20:05:41 +02:00
Simon Lübeß aad44dd835 Simple unnecessary build prevention 2026-04-13 19:58:55 +02:00
Simon Lübeß 28ad9298af Removed DirectXTK dependency 2026-04-12 22:10:39 +02:00
Simon Lübeß 86977213ff Updated msdfgen 2026-04-12 14:28:26 +02:00
Simon Lübeß 4c20cca585 Build msdfgen with BuildTool 2026-04-12 14:02:54 +02:00
Simon Lübeß 4c855819c9 Added FreeType to BuildTool 2026-04-12 12:05:55 +02:00
Simon Lübeß aabcd5f9b2 Build Box2D with BuildTool 2026-04-11 17:28:04 +02:00
Simon Lübeß 3fd5452146 Fixed debug builds for ScriptCore and GEHelper 2026-04-11 09:39:13 +02:00
Simon Lübeß 10e2294f9c BuildTool: Stop after when Module failed 2026-04-11 09:39:13 +02:00
Simon Lübeß bca4281d84 Fixed ScriptGlue function discovery and slighty automated extension assignment 2026-04-11 09:38:43 +02:00
Simon Lübeß 28a9242023 Fixed compiler bugs
Stuff happens when you don't work on a project for half a year!
2026-04-11 09:35:45 +02:00
Simon Lübeß 1b8cc93834 Added All-Target to BuildTool 2026-04-10 22:02:13 +02:00
Simon Lübeß 1eeab94575 Start of buildtool 2025-10-23 11:09:48 +02:00
Simon Lübeß 419ed7c9d2 Fixed uninitialized struct 2025-10-22 20:00:04 +02:00
Simon Lübeß 986ffeade0 Moved submodule NetHostBeef 2025-08-24 16:59:50 +02:00
Simon Lübeß c94ffbef64 Made release buildable 2025-08-24 16:54:26 +02:00
Simon Lübeß 7df651103b Custom columns for benchmarking 2025-08-17 03:01:41 +02:00
Simon Lübeß 8f51d1c626 Basic benchmarking 2025-08-17 00:26:16 +02:00
Simon Lübeß fca126c960 Start of test and benchmark 2025-08-11 22:09:54 +02:00
Simon Lübeß 4992511112 No longer add scripts to csproj 2025-08-11 22:09:00 +02:00
Simon Lübeß b94b2afc55 Dev settings for using ScriptCore as either dll or csproj 2025-08-09 23:40:31 +02:00
Simon Lübeß d002dfe25e Automatically set ScriptCore.dll reference in Visual Studio Solution/Project 2025-08-09 19:18:49 +02:00
Simon Lübeß b4f41fc218 Ignore generated files 2025-08-09 10:59:09 +02:00
Simon Lübeß ac0f4dade6 Updated Template for new dotnet runtime
- Added Submodule 'Xml-Beef'
- Use .slnx as solution-file
- Automatically set correct path to ScriptCore in Project files
2025-08-09 01:13:05 +02:00
Simon Lübeß 52cbf9dcf4 Disable circle rendering 2025-08-08 19:12:42 +02:00
Simon Lübeß e57c1c876f Removed unnecessary null terminator 2025-08-08 19:10:40 +02:00
Simon Lübeß 3f83c200f2 Secured struct layout 2025-08-08 19:09:59 +02:00
Simon Lübeß 74243cd285 Added Entity event OnCollisionEnter2D 2025-08-08 19:09:34 +02:00
Simon Lübeß bf03e99e4a Improved style checker 2025-08-08 19:03:31 +02:00
Simon Lübeß d156618453 Fixed calling engine functions from scripts
- Removed mono references in generator
- Implemented StringView type in C#
2025-08-08 19:03:09 +02:00
Simon Lübeß 042ae83a0f Setup ScriptCore to also function as analyzer 2025-08-03 12:01:22 +02:00
Simon Lübeß cc38236184 Try catch in C# Glue Functions called by engine, removed test ThrowException method 2025-08-03 12:00:35 +02:00
Simon Lübeß 184b92e95c StyleChecker works, tests dont 2025-08-03 11:58:27 +02:00
Simon Lübeß 41dacf72b6 Implement test code analyzer 2025-08-01 21:43:22 +02:00
Simon Lübeß c01ccf571e Profiling for starting play mode, fixed unnecessary font loading 2025-07-30 19:00:22 +02:00
Simon Lübeß ed644b5b41 Better engine exception for scripting 2025-07-30 18:58:26 +02:00
Simon Lübeß 72e87ee9ad Extending ScriptGlue now possible 2025-07-27 17:43:00 +02:00
Simon Lübeß 566d39d928 Fixed memory leak 2025-07-27 16:43:34 +02:00
Simon Lübeß 89bb4f4430 Removed C# code generation from beef-side script glue 2025-07-27 16:43:12 +02:00
Simon Lübeß 08846a0c0b Added EngineResult glue type, some cleanup 2025-07-26 21:03:10 +02:00
Simon Lübeß c41425ad36 Include Beef-files in ScriptCore project 2025-07-18 12:41:33 +02:00
Simon Lübeß 28311165ad ScriptEngine work and cleanup 2025-07-18 12:40:44 +02:00
Simon Lübeß 271286fb7e Remove mono from Glue functions 2025-07-17 20:07:19 +02:00
Simon Lübeß f77b153512 Fixed DLL-Import for ImGui.NET 2025-07-17 15:13:21 +02:00
Simon Lübeß f7bcff5337 Script glue works! 2025-07-17 15:07:06 +02:00
Simon Lübeß 0a53ff48f9 More generating script glue 2025-07-17 10:16:58 +02:00
Simon Lübeß edfcfd3b20 Start of using CoreCLR and automatic scriptglue generation 2025-07-14 22:14:41 +02:00
Simon Lübeß 063cbfa415 Fixed assets leaking memory 2025-07-11 12:54:04 +02:00
Simon Lübeß 311deb5bc0 Fixed Assets being registered for two IDs, getting Asset for Id 0 now returns null. 2025-07-11 12:28:09 +02:00
Simon Lübeß c2d330fc8f Handle loading error and placeholder shaders 2025-07-10 14:59:37 +02:00
Simon Lübeß 1b63c922da Asset browser: Copy, cut, paste 2025-07-10 13:31:27 +02:00
Simon Lübeß 39a825634a Path.FindFreePath: Fixed bug when filename is stringview into target string 2025-07-10 13:29:32 +02:00
Simon Lübeß ecfe838ad4 Added basic central popup service and way to highlight settings. 2025-07-08 19:35:40 +02:00
Simon Lübeß 081f7b4a7e Asset Browser: Defer navigation to next frame, show history when right clicking forward/backward button 2025-07-06 19:36:58 +02:00
Simon Lübeß 5f5192b0f4 Fast navigation in "address bar" 2025-07-06 18:52:59 +02:00
Simon Lübeß 3e9c9e8202 Asset Browser: Selection rectangle works with scrolling 2025-07-06 15:15:21 +02:00
Simon Lübeß 1e614eb944 Asset Browser: Handle navigation hotkeys only when directory is hovered 2025-07-06 14:14:39 +02:00
Simon Lübeß 1c97a5e9c4 Asset Browser: No longer show parent directory entry, navigate forward/backword with XButtons 2025-07-06 12:55:09 +02:00
Simon Lübeß 4b91116b51 Asset Browser: Navigation abstraction, show current path 2025-07-06 12:47:17 +02:00
Simon Lübeß 941cc8b767 ImGui: Added typed and scoped style vars 2025-07-06 12:44:59 +02:00
Simon Lübeß 88f6acb336 Selection Rectangle improvements 2025-07-05 22:36:35 +02:00
Simon Lübeß 736c8ce205 Improved entry placement, word wrapping for file names 2025-07-04 10:45:42 +02:00
Simon Lübeß 5cffe34620 Fixed clear selection when navigating or clicking background 2025-07-04 10:44:51 +02:00
Simon Lübeß f3b4fbd628 Spread empty space between directory entries, better parent directory handling, fixed id collision 2025-07-04 01:03:51 +02:00
Simon Lübeß e3c7057dce Basic rectangle selection in content browser 2025-07-04 00:13:33 +02:00
Simon Lübeß b00119b41e Generate ToString for vectors, generate Boolean-Opeartors for boolVectors 2025-07-04 00:13:02 +02:00
Simon Lübeß 9dc6b585bd Use StringView for Path operations 2025-07-04 00:12:19 +02:00
Simon Lübeß 1b46ff779a Remove sleep from delete task 2025-07-03 11:02:30 +02:00
Simon Lübeß c931451f10 Delete hotkey, delte files in background, Alt Up to go back a folder, basic multi select files 2025-06-30 23:14:03 +02:00
Simon Lübeß 300a2f1a8a Refactored (unified) rendering of directory items 2025-06-29 22:22:44 +02:00
Simon Lübeß e9644018ff Show directores before files in asset browser 2025-06-29 22:09:34 +02:00
Simon Lübeß 173942114f The functional remains of drop source 2025-06-29 21:40:55 +02:00
Simon Lübeß 39c9109919 Implemented base class for Com Interface implementations 2025-06-26 23:39:05 +02:00
Simon Lübeß 2cc8889ac1 Refactored DragDrop 2025-06-26 22:35:38 +02:00
Simon Lübeß 5fb8401a1a Renamed property 2025-06-26 19:29:19 +02:00
Simon Lübeß 6ed5f6d943 Added copy filename option to content browser context menu 2025-06-26 18:00:08 +02:00
Simon Lübeß 95ccb597d6 Fixed copying with multiple conflict handling flags,
made canceling and pausing of tasks more robust
2025-06-26 17:51:32 +02:00
Simon Lübeß 05671649b4 Added String.SetF 2025-06-26 17:50:04 +02:00
Simon Lübeß 50bcfa2caf Correctly copy directory hierarchy 2025-06-26 12:55:55 +02:00
Simon Lübeß 108e24eb38 advanced async copy of files 2025-06-26 10:16:39 +02:00
Simon Lübeß bfd1754d14 Fixed memory leak 2025-06-22 20:15:04 +02:00
Simon Lübeß 6af2e49387 Basic copy for drag'n'drop 2025-06-20 21:51:46 +02:00
Simon Lübeß 68745f0515 Retrieve filenames from drop event 2025-06-17 18:08:19 +02:00
Simon Lübeß 60482059a4 Test handle Windows Explorer drag'n'drop in ImGui 2025-06-17 16:46:05 +02:00
Simon Lübeß 9b6bb2b8d8 Made Vectors CRepr 2025-06-17 14:43:26 +02:00
Simon Lübeß 07faafd28d Basic drag & drop 2025-06-17 14:43:06 +02:00
Simon Lübeß 2c0b000afc Improved Texel Grid for Texture Viewer 2025-04-18 14:46:34 +02:00
Simon Lübeß 9d0fb99e3d Fixed mouse picking after gizmo usage
(and discovered more gizmo bugs...)
2025-03-29 15:13:08 +01:00
Simon Lübeß 400045b005 Allow custom resolutions for game window 2025-03-29 15:03:25 +01:00
Simon Lübeß 571bf98be2 Fixed data cycle 2025-03-23 17:46:55 +01:00
Simon Lübeß c34f585bbf Fixed temporary fix 2025-03-22 23:41:28 +01:00
Simon Lübeß 5e50d75eb9 Handle EngineBuffers, Preview Names and variable editor types for materials 2025-03-22 23:40:43 +01:00
Simon Lübeß 77a7a04637 Fixed missing semicolons 2025-03-19 21:15:37 +01:00
Simon Lübeß 6d50208700 Start of material editor 2025-03-03 20:29:35 +01:00
Simon Lübeß 10e8b3c282 Simplified serialization/deserialization of EngineObjects + added support for Assets (actually any EngineObject!) 2025-03-01 00:48:31 +01:00
Simon Lübeß b5088329f5 Texture inheritance for materials, also script access 2025-03-01 00:03:38 +01:00
Simon Lübeß 824c19c0b2 Show texel border and more swizzle options 2025-02-24 23:00:46 +01:00
Simon Lübeß 0236130df1 Fixed crashes due to wrong use of append allocations 2025-02-24 23:00:09 +01:00
Simon Lübeß 28121d1b42 Added component-wise min and max (cmin and cmax) 2025-02-16 01:04:21 +01:00
Simon Lübeß bb52f4c068 Fixes and improvements for Texture Vierwer
- Fixed bug where non-square Textures are render wrong
- Use center of preview area as 0,0
- Reset and Fit to View buttons
2025-02-16 01:04:04 +01:00
Simon Lübeß 49c3e93e08 Manage all Assets with ContentManager 2025-02-16 01:02:41 +01:00
Simon Lübeß 25e1c53a08 Allow resetting material variable 2025-02-14 15:34:33 +01:00
Simon Lübeß 24295731f3 Start of inheriting Materials, create material instance for getMaterial from scripts 2025-02-14 12:39:35 +01:00
Simon Lübeß aaed4199d3 Interact with Materials from scripts 2025-02-14 11:54:07 +01:00
Simon Lübeß e5437c6ff6 Some fixes, improvements for asset drop target 2025-02-14 11:38:39 +01:00
Simon Lübeß 688ebad278 Fixed 2d camera projection for texture viewer 2025-02-11 23:10:05 +01:00
Simon Lübeß 83b12efcff Fixed out of bounds access 2025-02-11 23:09:44 +01:00
Simon Lübeß 7a306cac69 Separate handling for hlsl and fx files 2025-02-11 23:07:04 +01:00
Simon Lübeß 06b981e88a Support materials in 2D Quad Renderer!
- Lost Circle rendering along the way
- Cleaned up 2D shaders
2025-02-11 14:02:16 +01:00
Simon Lübeß 76087d6ba4 Materials kind of working and used in some places
- Also Texture viewer fixes
2025-02-09 01:39:35 +01:00
Simon Lübeß acfb052137 New material loading pipeline almost working 2025-01-31 13:18:49 +01:00
Simon Lübeß 2f0505241f Append allocation and generics fixes 2025-01-31 13:17:12 +01:00
Simon Lübeß 577cca5886 Start of new materials, disable async asset loading 2024-11-30 16:21:00 +01:00
Simon Lübeß cf1f2a9a7b Emit parameterless constructor for vectors 2024-11-30 16:20:29 +01:00
Simon Lübeß bcf75bf8cb Engine barely starts again 2024-11-21 18:16:41 +01:00
Simon Lübeß ad73abd4ea Fixed memory leak when deleting Effect 2024-08-24 20:09:22 +02:00
Simon Lübeß b98de9835d Only hold on to vertex input layout shader code 2024-08-23 23:29:43 +02:00
Simon Lübeß d788853dd3 Cleaned up Shader usings 2024-08-23 23:01:31 +02:00
Simon Lübeß d8b3b137dd Pass VertexShader to VertexLayout, don't reassign layout 2024-08-23 23:00:10 +02:00
Simon Lübeß ff1c6db222 Removed old shader loading stuff 2024-08-22 23:42:16 +02:00
Simon Lübeß 73a6cc0854 Use blocking load method for shaders 2024-08-22 23:25:17 +02:00
Simon Lübeß 66e62f42ec First working effects with new pipeline 2024-08-22 00:34:20 +02:00
Simon Lübeß 7b7a3dc645 Started rewriting shader compilation/processing 2024-08-18 23:30:47 +02:00
395 changed files with 22875 additions and 13361 deletions
+1
View File
@@ -12,3 +12,4 @@ GlitchyEditor/MonoDebugger.log
GlitchyEditor/Resources/Scripts/*
.cache
generated
+6 -3
View File
@@ -22,9 +22,6 @@
[submodule "GlitchyEngine/vendor/bon"]
path = GlitchyEngine/vendor/bon
url = https://github.com/EinScott/bon.git
[submodule "GlitchyEngineHelper/vendor/DirectXTK"]
path = GlitchyEngineHelper/vendor/DirectXTK
url = https://github.com/microsoft/DirectXTK.git
[submodule "GlitchyEngine/vendor/box2D"]
path = GlitchyEngine/vendor/box2D
url = https://github.com/aharabada/box2d-beef.git
@@ -34,3 +31,9 @@
[submodule "vendor/ImGui.NET"]
path = vendor/ImGui.NET
url = https://github.com/aharabada/ImGui.NET.git
[submodule "GlitchyEngine/vendor/NetHostBeef"]
path = GlitchyEngine/vendor/NetHostBeef
url = https://github.com/aharabada/NetHostBeef.git
[submodule "GlitchyEngine/vendor/Xml-Beef"]
path = GlitchyEngine/vendor/Xml-Beef
url = https://github.com/aharabada/Xml-Beef.git
+29 -2
View File
@@ -1,9 +1,36 @@
FileVersion = 1
Projects = {GlitchyEngine = {Path = "GlitchyEngine"}, GlitchLog = {Path = "GlitchLog"}, DirectX = {Path = "GlitchyEngine/vendor/directx/DirectX"}, LodePng = {Path = "vendor/lodepng-beef/lodepng-beef"}, FreeType = {Path = "GlitchyEngine/vendor/freetype"}, cgltf-beef = {Path = "GlitchyEngine/vendor/gltf/cgltf-beef"}, GlitchyEditor = {Path = "GlitchyEditor"}, msdfgen-beef = {Path = "GlitchyEngine/vendor/msdfgen/msdfgen-beef"}, ImGui = {Path = "GlitchyEngine/vendor/imgui/ImGui"}, ImGuiImplDX11 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplDX11"}, ImGuiImplWin32 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplWin32"}, ImGuizmo = {Path = "GlitchyEngine/vendor/imgui/ImGuizmo"}, GlitchyEngineHelper = {Path = "GlitchyEngineHelper"}, bon = {Path = "GlitchyEngine/vendor/bon"}, box2d-beef = {Path = "GlitchyEngine/vendor/box2D"}, "Beef.Linq" = {Path = "GlitchyEngine/vendor/Beef.Linq/src"}, ScriptCore = {Path = "ScriptCore"}}
WorkspaceFolders = {GlitchyEngine = ["GlitchyEngine", "GlitchLog", "GlitchyEngineHelper", "ScriptCore"], "GlitchyEngine/Dependencies" = ["cgltf-beef", "DirectX", "FreeType", "ImGui", "ImGuiImplDX11", "ImGuiImplWin32", "ImGuizmo", "LodePng", "msdfgen-beef", "bon", "box2d-beef", "Beef.Linq"]}
Locked = ["ImGui"]
[Workspace]
StartupProject = "GlitchyEditor"
[Projects]
GlitchyEngine = {Path = "GlitchyEngine"}
GlitchLog = {Path = "GlitchLog"}
DirectX = {Path = "GlitchyEngine/vendor/directx/DirectX"}
LodePng = {Path = "vendor/lodepng-beef/lodepng-beef"}
FreeType = {Path = "GlitchyEngine/vendor/freetype"}
cgltf-beef = {Path = "GlitchyEngine/vendor/gltf/cgltf-beef"}
GlitchyEditor = {Path = "GlitchyEditor"}
msdfgen-beef = {Path = "GlitchyEngine/vendor/msdfgen/msdfgen-beef"}
ImGui = {Path = "GlitchyEngine/vendor/imgui/ImGui"}
ImGuiImplDX11 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplDX11"}
ImGuiImplWin32 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplWin32"}
ImGuizmo = {Path = "GlitchyEngine/vendor/imgui/ImGuizmo"}
GlitchyEngineHelper = {Path = "GlitchyEngineHelper"}
bon = {Path = "GlitchyEngine/vendor/bon"}
box2d-beef = {Path = "GlitchyEngine/vendor/box2D"}
"Beef.Linq" = {Path = "GlitchyEngine/vendor/Beef.Linq/src"}
ScriptCore = {Path = "ScriptCore"}
NetHostBeef = {Path = "GlitchyEngine/vendor/NetHostBeef/NetHostBeef"}
"GlitchyEngine.Test" = {Path = "GlitchyEngine.Test"}
Xml-Beef = {Path = "GlitchyEngine/vendor/Xml-Beef"}
"GlitchyEngine.Benchmark" = {Path = "GlitchyEngine.Benchmark"}
[WorkspaceFolders]
GlitchyEngine = ["GlitchyEngine", "GlitchLog", "GlitchyEngineHelper", "ScriptCore", "GlitchyEngine.Test", "GlitchyEngine.Benchmark"]
"GlitchyEngine/Dependencies" = ["cgltf-beef", "DirectX", "FreeType", "ImGui", "ImGuiImplDX11", "ImGuiImplWin32", "ImGuizmo", "LodePng", "msdfgen-beef", "bon", "box2d-beef", "Beef.Linq", "NetHostBeef", "Xml-Beef"]
Beef = ["Beefy2D", "corlib"]
[Configs.Debug.Win64]
AllocStackTraceDepth = 12
+17 -2
View File
@@ -1,18 +1,33 @@
FileVersion = 1
Dependencies = {corlib = "*", GlitchLog = "*", GlitchyEngine = "*"}
[Project]
Name = "GlitchyEditor"
TargetType = "BeefGUIApplication"
StartupObject = "GlitchyEngine.Program"
ProcessorMacros = ["GE_EDITOR_IMGUI_DEMO"]
ProcessorMacros = ["GE_EDITOR_IMGUI_DEMO", "GE_GRAPHICS_DX11"]
[Dependencies]
corlib = "*"
GlitchLog = "*"
GlitchyEngine = "*"
Xml-Beef = "*"
ImGuiImplDX11 = "*"
ImGuiImplWin32 = "*"
ImGuizmo = "*"
[Platform.Windows]
IconFile = "$(ProjectDir)/Resources/Textures/GlitchyEngineIcon.ico"
[Configs.Debug.Win64]
PostBuildCmds = ["CopyFilesIfNewer(\"$(WorkspaceDir)/bin/vswhere.exe\", \"$(TargetDir)\")"]
DebugCommandArguments = "\"D:\\Development\\Git\\SingleStateToOrbit\""
PreprocessorMacros = ["DEBUG", "IMGUI"]
[Configs.Release.Win64]
TargetDirectory = "$(BuildDir)/dist"
PostBuildCmds = ["CopyFilesIfNewer(\"$(WorkspaceDir)/bin/vswhere.exe\", \"$(TargetDir)\")"]
DebugCommandArguments = "\"D:\\Development\\Git\\SingleStateToOrbit\""
PreprocessorMacros = ["RELEASE", "IMGUI"]
[Configs.Paranoid.Win64]
PostBuildCmds = ["CopyFilesIfNewer(\"$(WorkspaceDir)/bin/vswhere.exe\", \"$(TargetDir)\")"]
@@ -0,0 +1,11 @@
{
AssetLoader = null,
Config = null,
Importer = null,
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = null,
AssetHandle = 9434890118063137951
}
@@ -0,0 +1,5 @@
{
Effect = 7757902626263730438,
Textures = [],
Constants = []
}
@@ -0,0 +1,13 @@
{
AssetLoader = null,
Config = null,
Importer = "MaterialImporter",
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = {
_compression = .None
},
AssetHandle = 14657247797693923776
}
@@ -0,0 +1,5 @@
{
Effect = 2172704887168568810,
Textures = [],
Constants = []
}
@@ -0,0 +1,13 @@
{
AssetLoader = null,
Config = null,
Importer = "MaterialImporter",
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = {
_compression = .None
},
AssetHandle = 15110879072477854834
}
@@ -0,0 +1,11 @@
{
AssetLoader = null,
Config = null,
Importer = null,
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = null,
AssetHandle = 12886429392536704724
}
@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunManager" selected=".NET Launch Settings Profile.ScriptCoreGenerator: Debug Code Generator">
<configuration name="Attach to Glitchy Engine" type="ConnectRemote" factoryName="Mono Remote" show_console_on_std_err="false" show_console_on_std_out="false" port="2550" address="localhost">
<option name="allowRunningInParallel" value="false" />
<option name="listenPortForConnections" value="false" />
<option name="projectPathOnTarget" />
<option name="selectedOptions">
<list />
</option>
<method v="2" />
</configuration>
<list>
<item itemvalue="Mono Remote.Attach to Glitchy Engine" />
</list>
</component>
</project>
@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// Allgemeine Informationen über eine Assembly werden über die folgenden
// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
// die einer Assembly zugeordnet sind.
[assembly: AssemblyTitle("[ProjectName]")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("[ProjectName]")]
[assembly: AssemblyCopyright("Copyright © 2023")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly
// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von
// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen.
[assembly: ComVisible(false)]
// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
[assembly: Guid("7a70819c-5317-402a-8553-95c5a001e370")]
// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
//
// Hauptversion
// Nebenversion
// Buildnummer
// Revision
//
// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden,
// indem Sie "*" wie unten gezeigt eingeben:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
@@ -1,54 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{7A70819C-5317-402A-8553-95C5A001E370}</ProjectGuid>
<!-- General configuration -->
<AssemblyTitle>[ProjectName]</AssemblyTitle>
<TargetFramework>net9.0</TargetFramework>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>[ProjectName]</RootNamespace>
<AssemblyName>[ProjectName]</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>default</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>default</LangVersion>
<Nullable>enable</Nullable>
<!-- Define the output path -->
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>./.cache/bin/</OutputPath>
<!-- Generates a runtimeconfig-file and copies nuget references to the output -->
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>
<ItemGroup>
<Reference Include="ScriptCore, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>[CoreLibPath]</HintPath>
<Reference Include="ScriptCore" OutputItemType="Analyzer" ReferenceOutputAssembly="true">
<HintPath>/ScriptCore.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<!-- Script Files -->
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
</Project>
@@ -0,0 +1,11 @@
{
AssetLoader = null,
Config = null,
Importer = null,
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = null,
AssetHandle = 17767079304205108419
}
@@ -1,25 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33205.214
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "[ProjectName]", "[ProjectName].csproj", "{7A70819C-5317-402A-8553-95C5A001E370}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7A70819C-5317-402A-8553-95C5A001E370}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7A70819C-5317-402A-8553-95C5A001E370}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7A70819C-5317-402A-8553-95C5A001E370}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7A70819C-5317-402A-8553-95C5A001E370}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B5D43D8D-D80B-41AA-BE40-1DC507D87148}
EndGlobalSection
EndGlobal
@@ -0,0 +1,3 @@
<Solution>
<Project Path="[ProjectName].csproj" />
</Solution>
@@ -0,0 +1,11 @@
{
AssetLoader = null,
Config = null,
Importer = null,
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = null,
AssetHandle = 15274144675004602184
}
@@ -1,5 +1,11 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */,
AssetHandle = 5360037389314667000
AssetLoader = null,
Config = null,
Importer = null,
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = null,
AssetHandle = 928998782684229984
}
@@ -0,0 +1,13 @@
{
AssetLoader = null,
Config = null,
Importer = "ShaderImporter",
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = {
_compression = .None
},
AssetHandle = 3971197750817239312
}
@@ -1,5 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */,
AssetHandle = 15597986932192091061
}
@@ -1,3 +1,5 @@
// TODO: General pipeline for post processing effects
Texture2D Texture : register(t0);
SamplerState TextureSampler : register(s0);
@@ -0,0 +1,13 @@
{
AssetLoader = null,
Config = null,
Importer = "ShaderImporter",
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = {
_compression = .None
},
AssetHandle = 315199643377983302
}
@@ -1,5 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */,
AssetHandle = 7082287996527160060
}
@@ -1,5 +1,11 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */,
AssetHandle = 17297254839128368932
AssetLoader = null,
Config = null,
Importer = null,
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = null,
AssetHandle = 8508337008960456447
}
@@ -0,0 +1,74 @@
#pragma EngineBuffer[ Name = "SceneConstants"; Binding = "Scene" ]
/**
* Contains constants that apply to the current scene.
*/
cbuffer SceneConstants : register(b0)
{
/**
* View projection matrix of the active camera.
*/
float4x4 ViewProjection;
}
/**
* Contains the data provided by the engine for a 2D vertex shader.
*/
struct VS_Input
{
// Geometry
/**
* Vertex position
*/
float2 Position : POSITION;
/**
* Vertex texture coordinate
*/
float2 Texcoord : TEXCOORD0;
// Instance data
/**
* model to world transform matrix of the current quad.
*/
float4x4 Transform : TRANSFORM;
/**
* Color of the current quad.
*/
float4 Color : COLOR;
/**
* model to world transform matrix of the current quad.
*/
float4 UVTransform : TEXCOORD1;
#ifdef EDITOR
/**
* The ECS ID of the entity associated with the current quad.
*/
uint EntityId : ENTITYID;
#endif
};
/**
* Contains the output data of the vertex shader that will be passed into the pixel shader.
*/
typedef struct PS_Input
{
/**
* The screen space position of the current vertex.
*/
float4 Position : SV_Position;
/**
* The transformed texture coordinates of the current vertex.
*/
float2 Texcoord : TEXCOORD;
/**
* The color of the current vertex.
*/
float4 Color : COLOR;
#ifdef EDITOR
/**
* The ECS ID of the entity associated with the current quad.
* This should always just be passed through.
*/
nointerpolation uint EntityId : ENTITYID;
#endif
} VS_Output;
@@ -0,0 +1,11 @@
{
AssetLoader = null,
Config = null,
Importer = null,
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = null,
AssetHandle = 15977579743485633133
}
@@ -1,5 +1,11 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */,
AssetHandle = 17917061300990589192
AssetLoader = null,
Config = null,
Importer = null,
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = null,
AssetHandle = 5789702360838490150
}
+9 -3
View File
@@ -1,5 +1,11 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */,
AssetHandle = 8336560834161185660
AssetLoader = null,
Config = null,
Importer = null,
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = null,
AssetHandle = 13875759478508290092
}
@@ -0,0 +1,28 @@
#include "GlitchyEngine.hlsl"
struct VS_IN
{
float3 Position : POSITION;
};
struct PS_IN
{
float4 Position : SV_POSITION;
};
PS_IN VS(VS_IN input)
{
PS_IN output;
float4 worldPosition = mul(Transform, float4(input.Position, 1));
output.Position = mul(ViewProjection, worldPosition);
return output;
}
float4 PS(PS_IN input) : SV_TARGET
{
return float4(1, 0, 1, 1);
}
#pragma Effect[VS = VS; PS = PS]
@@ -0,0 +1,11 @@
{
AssetLoader = null,
Config = null,
Importer = "ShaderImporter",
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = null,
AssetHandle = 652951988555027469
}
@@ -0,0 +1,28 @@
#include "GlitchyEngine.hlsl"
struct VS_IN
{
float3 Position : POSITION;
};
struct PS_IN
{
float4 Position : SV_POSITION;
};
PS_IN VS(VS_IN input)
{
PS_IN output;
float4 worldPosition = mul(Transform, float4(input.Position, 1));
output.Position = mul(ViewProjection, worldPosition);
return output;
}
float4 PS(PS_IN input) : SV_TARGET
{
return float4(0, 1, 1, 1);
}
#pragma Effect[VS = VS; PS = PS]
@@ -0,0 +1,11 @@
{
AssetLoader = null,
Config = null,
Importer = "ShaderImporter",
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = null,
AssetHandle = 3052446500956620342
}
@@ -0,0 +1,178 @@
#include "ShaderHelpers.hlsl"
Texture2DArray Texture : register(t0);
SamplerState Sampler : register(s0);
Texture2DArray<int4> IntTexture : register(t1);
SamplerState IntSampler : register(s1);
Texture2DArray<uint4> UIntTexture : register(t2);
SamplerState UIntSampler : register(s2);
#define SWIZZLE_ZERO 0
#define SWIZZLE_ONE 1
#define SWIZZLE_R 2
#define SWIZZLE_G 3
#define SWIZZLE_B 4
#define SWIZZLE_A 5
#define SWIZZLE_ONE_MINUS_R 6
#define SWIZZLE_ONE_MINUS_G 7
#define SWIZZLE_ONE_MINUS_B 8
#define SWIZZLE_ONE_MINUS_A 9
cbuffer Constants
{
float ColorOffset = 0.5;
float AlphaOffset = 0.0;
float ColorScale = 0.5;
float AlphaScale = 1.0;
float MipLevel = 0.0;
float ArraySlice = 0.0;
// 0: Float | 1: Int | 2: Uint
int Mode = 0;
int4 Swizzle = int4(SWIZZLE_R, SWIZZLE_G, SWIZZLE_B, SWIZZLE_A);
float2 Texels;
float4x4 WorldViewProjection;
bool ShowTexelBorders = true;
float TexelBorderCutoff = 10;
}
struct VS_Input
{
float2 Position : POSITION;
float2 Texcoord : TEXCOORD0;
};
struct PS_Input
{
float4 Position : SV_Position;
float2 Texcoord : TEXCOORD0;
};
PS_Input VS(VS_Input input)
{
PS_Input output;
output.Position = mul(WorldViewProjection, float4(input.Position, 0.0f, 1.0f));
output.Texcoord = input.Texcoord;
return output;
}
void SwizzleColor(int swizzleMode, float4 colorToSwizzle, inout float output)
{
switch (swizzleMode)
{
case SWIZZLE_ZERO:
output = 0.0;
break;
case SWIZZLE_ONE:
output = 1.0;
break;
case SWIZZLE_R:
output = colorToSwizzle.r;
break;
case SWIZZLE_G:
output = colorToSwizzle.g;
break;
case SWIZZLE_B:
output = colorToSwizzle.b;
break;
case SWIZZLE_A:
output = colorToSwizzle.a;
break;
case SWIZZLE_ONE_MINUS_R:
output = 1.0 - colorToSwizzle.r;
break;
case SWIZZLE_ONE_MINUS_G:
output = 1.0 - colorToSwizzle.g;
break;
case SWIZZLE_ONE_MINUS_B:
output = 1.0 - colorToSwizzle.b;
break;
case SWIZZLE_ONE_MINUS_A:
output = 1.0 - colorToSwizzle.a;
break;
default:
break;
}
}
/**
* Returns whether or not the current pixel lies on the border between two texels.
* @param texelPosition The position in texel coordinates [0...Texels)
* @param color The sampled color at the current position. This will be modified if the pixel is on a texel border.
*/
void ApplyTexelBorder(float2 texelPosition, inout float4 color)
{
// Calculate derivatives of texelPosition -> how many texels to we move if we move one pixel in x/y direction
float2 grad = fwidth(texelPosition);
float texelsPerPixel = min(grad.x, grad.y);
float pixelsPerTexel = 1.0 / texelsPerPixel;
// Calculate the distance between the screen pixel and the texel border in texels.
float2 fracPart = frac(texelPosition);
float2 distanceToBorder = min(fracPart, 1.0 - fracPart);
// One, if texel is closer to border than threshold.
// Threshold is half the size of a screen pixel in texel-space.
float2 borderTest = step(distanceToBorder, texelsPerPixel / 2.0);
//float2 borderTest = saturate(1.0 - distanceToBorder / grad);
float isOnLine = max(borderTest.x, borderTest.y);
float fade = saturate((pixelsPerTexel - TexelBorderCutoff / 2.0) / (TexelBorderCutoff / 2.0));
// TODO: This can flicker while moving/zooming, because we are by design on a texel border!
float luminance = ColorToLuminance(color.rgb);
// Ensure the border is always at least 0.5 luminance away from the texel.
float4 borderColor = float4(((luminance + 0.5) % 1).xxx, 1.0);
color = lerp(color, borderColor, isOnLine * fade * 0.8);
}
float4 PS(PS_Input input) : SV_Target0
{
float4 outputColor;
float2 texelPosition = input.Texcoord * Texels;
if (Mode == 0)
{
//float4 color = Texture.SampleLevel(Sampler, float3(input.Texcoord, ArraySlice), MipLevel);
// Quasi next neighbor
float4 color = Texture.Load(int4(texelPosition, ArraySlice, MipLevel));
outputColor = float4(ColorOffset.xxx, AlphaOffset) + color * float4(ColorScale.xxx, AlphaScale);
}
else if (Mode == 1)
{
int4 color = IntTexture.Load(int4(texelPosition, ArraySlice, MipLevel));
outputColor = float4(ColorOffset.xxx, AlphaOffset) + color * float4(ColorScale.xxx, AlphaScale);
}
else if (Mode == 2)
{
uint4 color = UIntTexture.Load(int4(texelPosition, ArraySlice, MipLevel));
outputColor = float4(ColorOffset.xxx, AlphaOffset) + color * float4(ColorScale.xxx, AlphaScale);
}
float4 swizzledOutput = float4(0, 0, 0, 1);
SwizzleColor(Swizzle.r, outputColor, swizzledOutput.r);
SwizzleColor(Swizzle.g, outputColor, swizzledOutput.g);
SwizzleColor(Swizzle.b, outputColor, swizzledOutput.b);
SwizzleColor(Swizzle.a, outputColor, swizzledOutput.a);
if (ShowTexelBorders)
{
ApplyTexelBorder(texelPosition, swizzledOutput);
}
return swizzledOutput;
}
#pragma Effect[VS=VS; PS=PS]
@@ -0,0 +1,13 @@
{
AssetLoader = null,
Config = null,
Importer = "ShaderImporter",
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = {
_compression = .None
},
AssetHandle = 17953005509950866729
}
@@ -1,111 +0,0 @@
Texture2DArray Texture : register(t0);
SamplerState Sampler : register(s0);
Texture2DArray<int4> IntTexture : register(t1);
SamplerState IntSampler : register(s1);
Texture2DArray<uint4> UIntTexture : register(t2);
SamplerState UIntSampler : register(s2);
#define SWIZZLE_NONE 0
#define SWIZZLE_R 1
#define SWIZZLE_G 2
#define SWIZZLE_B 3
#define SWIZZLE_A 4
cbuffer Constants
{
float ColorOffset = 0.5;
float AlphaOffset = 0.0;
float ColorScale = 0.5;
float AlphaScale = 1.0;
float MipLevel = 0.0;
float ArraySlice = 0.0;
// 0: Float | 1: Int | 2: Uint
int Mode = 0;
int4 Swizzle = int4(SWIZZLE_R, SWIZZLE_G, SWIZZLE_B, SWIZZLE_A);
float2 Texels;
float4x4 WorldViewProjection;
}
struct VS_Input
{
float2 Position : POSITION;
float2 Texcoord : TEXCOORD0;
};
struct PS_Input
{
float4 Position : SV_Position;
float2 Texcoord : TEXCOORD0;
};
PS_Input VS(VS_Input input)
{
PS_Input output;
output.Position = mul(WorldViewProjection, float4(input.Position, 0.0f, 1.0f));
output.Texcoord = input.Texcoord;
return output;
}
void SwizzleColor(int swizzleMode, float4 colorToSwizzle, inout float output)
{
switch (swizzleMode)
{
case SWIZZLE_R:
output = colorToSwizzle.r;
break;
case SWIZZLE_G:
output = colorToSwizzle.g;
break;
case SWIZZLE_B:
output = colorToSwizzle.b;
break;
case SWIZZLE_A:
output = colorToSwizzle.a;
break;
default:
break;
}
}
float4 PS(PS_Input input) : SV_Target0
{
float4 outputColor;
if (Mode == 0)
{
//float4 color = Texture.SampleLevel(Sampler, float3(input.Texcoord, ArraySlice), MipLevel);
// Quasi next neighbor
float4 color = Texture.Load(int4(input.Texcoord * Texels, ArraySlice, MipLevel));
outputColor = float4(ColorOffset.xxx, AlphaOffset) + color * float4(ColorScale.xxx, AlphaScale);
}
else if (Mode == 1)
{
int4 color = IntTexture.Load(int4(input.Texcoord * Texels, ArraySlice, MipLevel));
outputColor = float4(ColorOffset.xxx, AlphaOffset) + color * float4(ColorScale.xxx, AlphaScale);
}
else if (Mode == 2)
{
uint4 color = UIntTexture.Load(int4(input.Texcoord * Texels, ArraySlice, MipLevel));
outputColor = float4(ColorOffset.xxx, AlphaOffset) + color * float4(ColorScale.xxx, AlphaScale);
}
float4 swizzledOutput = float4(0, 0, 0, 1);
SwizzleColor(Swizzle.r, outputColor, swizzledOutput.r);
SwizzleColor(Swizzle.g, outputColor, swizzledOutput.g);
SwizzleColor(Swizzle.b, outputColor, swizzledOutput.b);
SwizzleColor(Swizzle.a, outputColor, swizzledOutput.a);
return swizzledOutput;
}
#pragma Effect[VS=VS; PS=PS]
@@ -1,5 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */,
AssetHandle = 1518912886664733757
}
@@ -72,7 +72,7 @@ float3x3 ConstructTangentSpace(float3 normal, float3 tangent, float3 sigma)
*/
float ColorToLuminance(float3 rgb)
{
return rgb.r * 0.212639 + rgb.g * 0.715169 + rgb.b * 0.072192;
return dot(rgb, float3(0.212639,0.715169,0.072192));
}
/**
@@ -87,4 +87,15 @@ float GetBitangentHandedness(float tangentz)
return (z & 1) > 0 ? 1.0 : -1.0;
}
/**
* Transforms the given texture coordinates using the provided UV transform.
* @param texcoords The texture coordinates to transform.
* @param uvTransform the UV transform. X, Y: UV offset | Z, W: UV scaling.
* @return The transformed uv coordinates.
*/
float2 TransformTexcoords(float2 texcoords, float4 uvTransform)
{
return uvTransform.xy + uvTransform.zw * texcoords;
}
#endif // __SHADER_HELPERS_HLSL__
@@ -1,5 +1,11 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */,
AssetHandle = 13663488523997149295
AssetLoader = null,
Config = null,
Importer = null,
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = null,
AssetHandle = 15853638422335091598
}
@@ -0,0 +1,13 @@
{
AssetLoader = null,
Config = null,
Importer = "ShaderImporter",
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = {
_compression = .None
},
AssetHandle = 16539769359681819317
}
@@ -1,5 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */,
AssetHandle = 9450907808112906391
}
@@ -0,0 +1,13 @@
{
AssetLoader = null,
Config = null,
Importer = "ShaderImporter",
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = {
_compression = .None
},
AssetHandle = 15706760057808399494
}
@@ -1,5 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */,
AssetHandle = 4086392147967882920
}
@@ -0,0 +1,13 @@
{
AssetLoader = null,
Config = null,
Importer = "ShaderImporter",
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = {
_compression = .None
},
AssetHandle = 10636491756936205086
}
@@ -1,5 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */,
AssetHandle = 3929078354809894230
}
@@ -0,0 +1,13 @@
{
AssetLoader = null,
Config = null,
Importer = "ShaderImporter",
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = {
_compression = .None
},
AssetHandle = 5888610203248969416
}
@@ -1,5 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */,
AssetHandle = 5414648614681927202
}
@@ -0,0 +1,68 @@
#define EDITOR
#include "GlitchyEngine2D.hlsl"
#include "ShaderHelpers.hlsl"
Texture2D<float3> Texture : register(t0);
SamplerState Sampler : register(s0);
cbuffer Material
{
float2 UnitRange;
}
VS_Output VS(VS_Input input)
{
VS_Output output;
output.Position = mul(ViewProjection, mul(input.Transform, float4(input.Position, 0.0f, 1.0f)));
output.Texcoord = TransformTexcoords(input.Texcoord, input.UVTransform);
output.Color = input.Color;
#ifdef EDITOR
output.EntityId = input.EntityId;
#endif
return output;
}
float median(float r, float g, float b)
{
return max(min(r, g), min(max(r, g), b));
}
float ScreenPxRange(float2 texcoord)
{
float2 screenTexSize = 1.0f / fwidth(texcoord);
return max(0.5f * dot(UnitRange, screenTexSize), 1.0f);
}
struct PS_Output
{
float4 Color : SV_Target0;
#ifdef EDITOR
uint EntityId : SV_TARGET1;
#endif
};
PS_Output PS(PS_Input input)
{
PS_Output output;
float3 msd = Texture.Sample(Sampler, input.Texcoord);
float sd = median(msd.r, msd.g, msd.b);
float screenPxDistance = ScreenPxRange(input.Texcoord) * (sd - 0.5);
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
clip(opacity - 0.001f);
output.Color = float4(input.Color.rgb, input.Color.a * opacity);
#ifdef EDITOR
output.EntityId = input.EntityId;
#endif
return output;
}
#pragma Effect[VS=VS; PS=PS]
@@ -0,0 +1,13 @@
{
AssetLoader = null,
Config = null,
Importer = "ShaderImporter",
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = {
_compression = .None
},
AssetHandle = 9062891433306436746
}
@@ -1,97 +0,0 @@
Texture2D<float3> Texture : register(t0);
SamplerState Sampler : register(s0);
cbuffer Constants
{
float4x4 ViewProjection;
float2 UnitRange;
float screenPixelRange = 2;
}
struct VS_Input
{
float2 Position : POSITION;
float2 Texcoord : TEXCOORD0;
float4x4 Tranform : TRANSFORM;
float4 Color : COLOR;
float4 UVTransform : TEXCOORD1;
};
struct PS_Input
{
float4 Position : SV_Position;
float2 TexCoord : TEXCOORD0;
float4 Color : COLOR;
};
PS_Input VS(VS_Input input)
{
PS_Input output;
output.Position = mul(ViewProjection, mul(input.Tranform, float4(input.Position, 0.0f, 1.0f)));
output.TexCoord = input.UVTransform.xy + input.UVTransform.zw * input.Texcoord;
output.Color = input.Color;
return output;
}
/*
in vec2 texCoord;
out vec4 color;
uniform sampler2D msdf;
uniform vec4 bgColor;
uniform vec4 fgColor;
float median(float r, float g, float b) {
return max(min(r, g), min(max(r, g), b));
}
void main() {
vec3 msd = texture(msdf, texCoord).rgb;
float sd = median(msd.r, msd.g, msd.b);
float screenPxDistance = screenPxRange()*(sd - 0.5);
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
color = mix(bgColor, fgColor, opacity);
}
*/
float median(float r, float g, float b)
{
return max(min(r, g), min(max(r, g), b));
}
float ScreenPxRange(float2 texcoord)
{
float2 screenTexSize = 1.0f / fwidth(texcoord);
return max(0.5f * dot(UnitRange, screenTexSize), 1.0f);
}
float4 PS(PS_Input input) : SV_Target0
{
float3 msd = Texture.Sample(Sampler, input.TexCoord);
float sd = median(msd.r, msd.g, msd.b);
float screenPxDistance = ScreenPxRange(input.TexCoord) * (sd - 0.5);
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
clip(opacity - 0.001f);
//return float4(input.Color.rgb, opacity * input.Color.a) * 0.0001f + float4(opacity.xxxx);
return input.Color * opacity; // TODO: This is not quite right // float4(input.Color.rgb, input.Color.a * opacity);
//return float4(input.Color.rgb, opacity * input.Color.a) * 0.0001f + float4(msd, 1.0f);
}
/*
// 2D
float4 PS(PS_Input input) : SV_Target0
{
float3 msd = Texture.Sample(Sampler, input.TexCoord).rgb;
float sd = median(msd.r, msd.g, msd.b);
float screenPxDistance = screenPixelRange*(sd - 0.5);
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
return float4(input.Color.rgb, opacity * input.Color.a);
}
*/
#pragma Effect[VS=VS; PS=PS]
@@ -1,5 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */,
AssetHandle = 17001330646928133106
}
@@ -0,0 +1,13 @@
{
AssetLoader = null,
Config = null,
Importer = "ShaderImporter",
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = {
_compression = .None
},
AssetHandle = 2172704887168568810
}
@@ -1,5 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */,
AssetHandle = 2913314865396454015
}
@@ -0,0 +1,13 @@
{
AssetLoader = null,
Config = null,
Importer = "ShaderImporter",
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = {
_compression = .None
},
AssetHandle = 1519310858892551656
}
@@ -1,5 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */,
AssetHandle = 9047671870217222298
}
@@ -1,41 +1,17 @@
#define EDITOR
#include "GlitchyEngine2D.hlsl"
#include "ShaderHelpers.hlsl"
Texture2D Texture : register(t0);
SamplerState Sampler : register(s0);
cbuffer Constants : register(b0)
VS_Output VS(VS_Input input)
{
float4x4 ViewProjection;
};
struct VS_Input
{
float2 Position : POSITION;
float2 Texcoord : TEXCOORD0;
float4x4 Transform : TRANSFORM;
float4 Color : COLOR;
float4 UVTransform : TEXCOORD1;
#ifdef EDITOR
uint EntityId : ENTITYID;
#endif
};
struct PS_Input
{
float4 Position : SV_Position;
float2 Texcoord : TEXCOORD;
float4 Color : COLOR;
#ifdef EDITOR
nointerpolation uint EntityId : ENTITYID;
#endif
};
PS_Input VS(VS_Input input)
{
PS_Input output;
VS_Output output;
output.Position = mul(ViewProjection, mul(input.Transform, float4(input.Position, 0.0f, 1.0f)));
output.Texcoord = input.UVTransform.xy + input.UVTransform.zw * input.Texcoord;
output.Texcoord = TransformTexcoords(input.Texcoord, input.UVTransform);
output.Color = input.Color;
// Premultiply Alpha
@@ -0,0 +1,13 @@
{
AssetLoader = null,
Config = null,
Importer = "ShaderImporter",
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = {
_compression = .None
},
AssetHandle = 7757902626263730438
}
@@ -1,5 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */,
AssetHandle = 14473519405124742382
}
@@ -0,0 +1,13 @@
{
AssetLoader = null,
Config = null,
Importer = "ShaderImporter",
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = {
_compression = .None
},
AssetHandle = 8094944104856236365
}
@@ -1,5 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */,
AssetHandle = 10276018270467402814
}
@@ -11,14 +11,14 @@ public class [EntityName] : Entity
/// <summary>
/// OnCreate is called once before the first call to OnUpdate.
/// </summary>
void OnCreate()
protected override void OnCreate()
{
}
/// <summary>
/// OnUpdate is called once every frame.
/// </summary>
void OnUpdate(float deltaTime)
protected override void OnUpdate(float deltaTime)
{
}
}
@@ -0,0 +1,11 @@
{
AssetLoader = null,
Config = null,
Importer = null,
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = null,
AssetHandle = 14385412659829048739
}
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,11 @@
{
AssetLoader = null,
Config = null,
Importer = null,
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = null,
AssetHandle = 3965163548234580201
}
@@ -0,0 +1,11 @@
{
AssetLoader = null,
Config = null,
Importer = null,
ImporterConfig = null,
Processor = null,
ProcessorConfig = null,
Exporter = null,
ExporterConfig = null,
AssetHandle = 14643606651153291317
}
+13 -11
View File
@@ -122,7 +122,10 @@ class AssetFile
CreateDefaultAssetLoader();
}
_lastConfigEditTime = File.GetLastWriteTimeUtc(_assetConfigPath);
if (File.GetLastWriteTimeUtc(_assetConfigPath) not case .Ok(out _lastConfigEditTime))
{
_lastConfigEditTime = .MinValue;
}
}
private void GenerateAssetHandle()
@@ -134,7 +137,7 @@ class AssetFile
{
String fileExtension = Path.GetExtension(_assetFile.Path, .. scope .());
Log.EngineLogger.Info($"Created config for {_assetFile.Path}");
Log.EngineLogger.Info($"Creating asset config for {_assetFile.Path}");
_assetConfig = new AssetConfig();
@@ -150,22 +153,21 @@ class AssetFile
var assetLoader = _contentManager.GetDefaultAssetLoader(fileExtension);
// We don't have a loader -> we don't need a config
if (assetLoader == null && assetImporter == null)
return;
if (assetLoader != null)
if (assetLoader != null || assetImporter != null)
{
_assetConfig.AssetLoader = new String();
assetLoader.GetType().GetName(_assetConfig.AssetLoader);
_assetConfig.Config = assetLoader?.GetDefaultConfig();
_assetConfig.Config?.[Friend]_changed = true;
}
_assetConfig.Importer = new String();
assetImporter?.GetType()?.GetName(_assetConfig.Importer);
_assetConfig.Importer = new String();
assetImporter?.GetType()?.GetName(_assetConfig.Importer);
}
// We might not have a loader, but we want to retain the AssetHandle:
// e. g. a .txt File can't be reasonably imported, but might still need to be packed into the final
// application bundle and be read in a script via AssetHandle.
SaveAssetConfig();
}
+137 -1
View File
@@ -6,13 +6,14 @@ using System.Collections;
using System.IO;
using System.Linq;
using GlitchyEngine.Content;
using GlitchyEditor.Multithreading;
namespace GlitchyEditor.Assets;
class AssetHierarchy
{
FileSystemWatcher fsw ~ {
_.StopRaisingEvents();
_?.StopRaisingEvents();
delete _;
};
@@ -65,6 +66,30 @@ class AssetHierarchy
Log.EngineLogger.Trace($"Created root node");
}
public void DeletePathsBackground(Span<StringView> paths)
{
DeleteBackgroundTask deleteTask = new .(paths);
deleteTask.DeleteWhenEnded = true;
for (StringView path in paths)
{
Result<TreeNode<AssetNode>> nodeResult = GetNodeFromPath(path);
if (nodeResult case .Ok(TreeNode<AssetNode> assetNode))
{
StringView assetDescriptorPath = assetNode->AssetFile?.AssetConfigPath ?? "";
// If it exists, also try to delete the .ass file
if (File.Exists(assetDescriptorPath))
{
deleteTask.AddPath(assetDescriptorPath);
}
}
}
EditorApp.Instance.BackgroundTaskManager.StartBackgroundTask(deleteTask);
}
/// Deletes the file that belongs to the given assetNode
public void DeleteFile(AssetNode assetNode)
{
@@ -646,4 +671,115 @@ class AssetHierarchy
public delegate void FileRenamedFunc(AssetNode node, StringView oldName);
public Event<FileRenamedFunc> OnFileRenamed ~ _.Dispose();
public enum CopyError
{
TargetDirectoryIsNotDirectory,
SourceDoesntExist,
TargetExists
}
public enum OverwriteMode
{
None,
Overwrite,
KeepBoth,
Combine
}
/*public Result<BackgroundTask> CopyExternFileToNodeBackground(TreeNode<AssetNode> targetDirectory, String sourcePath, OverwriteMode overwrite)
{
if (!targetDirectory->IsDirectory)
return .Err;
FileInfo sourceInfo = scope FileInfo(sourcePath);
if (!sourceInfo.Exists)
return .Err;
CopyBackgroundTask copyTask = new CopyBackgroundTask(sourcePath, targetDirectory->Path);
EditorApp.Instance.BackgroundTaskManager.StartBackgroundTask(copyTask);
return copyTask;
}*/
public Result<BackgroundTask> CopyExternFilesToNodeBackground(TreeNode<AssetNode> targetDirectory, List<String> sourcePaths, OverwriteMode overwrite)
{
if (!targetDirectory->IsDirectory)
return .Err;
/*FileInfo sourceInfo = scope FileInfo(sourcePath);
if (!sourceInfo.Exists)
return .Err;*/
CopyBackgroundTask copyTask = new CopyBackgroundTask(sourcePaths, targetDirectory->Path);
copyTask.DeleteWhenEnded = true;
EditorApp.Instance.BackgroundTaskManager.StartBackgroundTask(copyTask);
return copyTask;
}
public Result<void, CopyError> CopyExternFileToNode(TreeNode<AssetNode> targetDirectory, String sourcePath, OverwriteMode overwrite)
{
if (!targetDirectory->IsDirectory)
return .Err(.TargetDirectoryIsNotDirectory);
if (!File.Exists(sourcePath))
return .Err(.SourceDoesntExist);
FileInfo sourceInfo = scope FileInfo(sourcePath);
String fileName = scope .();
Path.GetFileName(sourcePath, fileName);
String targetPath = scope .();
Path.Combine(targetPath, targetDirectory->Path, fileName);
FileInfo targetInfo = scope FileInfo(targetPath);
if (sourceInfo.IsDirectory)
{
bool forceOverwrite = false;
if (targetInfo.Exists)
{
switch (overwrite)
{
case .KeepBoth:
String fileExtension = scope .();
Path.GetExtension(targetPath, fileExtension);
Path.FindFreePath(targetDirectory->Path, fileName.Substring(0..<^fileExtension.Length), fileExtension, targetPath..Clear());
case .Overwrite:
forceOverwrite = true;
default:
return .Err(.TargetExists);
}
}
}
else
{
bool forceOverwrite = false;
if (targetInfo.Exists)
{
switch (overwrite)
{
case .KeepBoth:
String fileExtension = scope .();
Path.GetExtension(targetPath, fileExtension);
Path.FindFreePath(targetDirectory->Path, fileName.Substring(0..<^fileExtension.Length), fileExtension, targetPath..Clear());
case .Overwrite:
forceOverwrite = true;
default:
return .Err(.TargetExists);
}
}
File.Copy(sourcePath, targetPath, forceOverwrite);
}
return .Ok;
}
}
@@ -1,5 +1,8 @@
using System;
namespace GlitchyEditor.Assets;
[Obsolete]
abstract class AssetPropertiesEditor
{
private AssetFile _asset;
@@ -18,14 +18,15 @@ class AssetThumbnailManager
if (assetNode.Path.EndsWith(".scene"))
return _icons.File_Scene;
if (assetNode.Path.EndsWith(".mat"))
return _icons.File_Material;
if (assetNode.Path.EndsWith(".cs"))
return _icons.File_CSharpScript;
if (assetNode.Path.EndsWith(".hlsl"))
return _icons.File_Shader;
return _icons.File_Hlsl;
if (assetNode.Path.EndsWith(".fx"))
return _icons.File_Effect;
if (assetNode.Path.EndsWith(".mat"))
return _icons.File_Material;
return _icons.File;
}
@@ -0,0 +1,304 @@
using GlitchyEngine.Renderer;
using ImGui;
using System;
using GlitchyEngine.Math;
using GlitchyEditor.EditWindows;
using GlitchyEngine.Content;
namespace GlitchyEditor.Assets.Editors;
class MaterialEditor
{
public static bool ShowEditor(AssetFile assetFile)
{
bool isDirty = false;
let material = assetFile.LoadedAsset as Material;
if (material == null)
return isDirty;
ImGui.PropertyTableStartNewProperty("Base Material");
if (material.Parent != null)
ImGui.TextUnformatted(material.Parent.Identifier);
else
ImGui.TextUnformatted("None");
ImGui.PropertyTableStartNewProperty("Shader");
ImGui.TextUnformatted(material.Effect.Identifier);
ImGui.PropertyTableStartNewRow();
if (ImGui.CollapsingHeader("Parameters", .DefaultOpen | .AllowOverlap | .Framed | .SpanFullWidth | .SpanAllColumns))
{
String prettyVariableName = scope .();
for (let (variableName, bufferVariable) in material.Variables)
{
// Don't show engine buffer variables
if (!bufferVariable.ConstantBuffer.EngineBufferName.IsEmpty)
{
continue;
}
ImGui.PushID(variableName);
ImGui.PropertyTableStartNewRow();
bool readOnly = bufferVariable.Flags.HasFlag(.Readonly);
ImGui.BeginDisabled(readOnly);
if (DrawLockButton(bufferVariable.Flags.HasFlag(.Locked)) && !readOnly)
{
bufferVariable.[Friend]_flags ^= .Locked;
// Also mark as dirty so we update our children!
bufferVariable.[Friend]_flags |= .Dirty;
}
ImGui.SameLine();
StringView displayName = bufferVariable.PreviewName;
if (displayName.IsEmpty)
{
ToPrettyName(variableName, prettyVariableName);
displayName = prettyVariableName;
}
ImGui.PropertyTableName(displayName);
// TODO: Somehow pass display name and type from imported shader to here!
bool handled = false;
switch (bufferVariable.EditorTypeName)
{
case "Color", "ColorHDR":
if (bufferVariable.ElementType != .Float || bufferVariable.Rows != 1 ||
bufferVariable.Columns < 3)
{
break;
}
ImGui.ColorEditFlags flags = .None;
if (_ == "ColorHDR")
{
flags |= .HDR | .Float;
}
if (bufferVariable.Columns == 3)
{
material.GetVariable<float3>(bufferVariable.Name, var value);
if (ImGui.ColorEdit3("", ref *(float[3]*)&value, flags))
material.SetVariable(bufferVariable.Name, value);
}
else if (bufferVariable.Columns == 4)
{
material.GetVariable<float4>(bufferVariable.Name, var value);
if (ImGui.ColorEdit4("", ref *(float[4]*)&value, flags))
material.SetVariable(bufferVariable.Name, value);
}
handled = true;
}
if (!handled)
{
switch (bufferVariable.ElementType)
{
case .Float:
// TODO: min and max values specified in shader
// TODO: support drag and enter number (specified in shader)
//for (int r < bufferVariable.Rows)
if (bufferVariable.Rows == 1)
{
switch (bufferVariable.Columns)
{
case 1:
material.GetVariable<float>(bufferVariable.Name, var value);
//float[1] minV = hasMin ? min.Get<float[1]>() : .(float.MinValue);
//float[1] maxV = hasMax ? max.Get<float[1]>() : .(float.MaxValue);
if (ImGui.VectorEditor<1>("", ref *(float[1]*)&value, .(), 0.1f /*, minV, maxV*/))
material.SetVariable(bufferVariable.Name, value);
case 2:
material.GetVariable<float2>(bufferVariable.Name, var value);
if (ImGui.Float2Editor("", ref value, .Zero, 0.1f, 100.0f))
material.SetVariable(bufferVariable.Name, value);
case 3:
material.GetVariable<float3>(bufferVariable.Name, var value);
if (ImGui.Float3Editor("", ref value, .Zero, 0.1f, 100.0f))
material.SetVariable(bufferVariable.Name, value);
case 4:
material.GetVariable<float4>(bufferVariable.Name, var value);
if (ImGui.Float4Editor("", ref value, .Zero, 0.1f, 100.0f))
material.SetVariable(bufferVariable.Name, value);
}
}
default:
ImGui.TextUnformatted(scope $"Element Type {_} not supported");
}
}
ImGui.EndDisabled();
ImGui.PopID();
}
}
ImGui.PropertyTableStartNewRow();
if (ImGui.CollapsingHeader("Textures", .DefaultOpen | .AllowOverlap | .Framed | .SpanFullWidth | .SpanAllColumns))
{
String prettyVariableName = scope .();
for (var (textureName, texture) in ref material.Textures)
{
ImGui.PushID(textureName);
ImGui.PropertyTableStartNewRow();
bool readOnly = texture.Flags.HasFlag(.Readonly);
ImGui.BeginDisabled(readOnly);
if (DrawLockButton(texture.Flags.HasFlag(.Locked)) && !readOnly)
{
texture.Flags ^= .Locked;
// Also mark as dirty so we update our children!
texture.Flags |= .Dirty;
}
ImGui.SameLine();
// TODO: Preview Name for textures
StringView displayName = "";//texture.PreviewName;
if (displayName.IsEmpty)
{
ToPrettyName(textureName, prettyVariableName);
displayName = prettyVariableName;
}
ImGui.PropertyTableName(displayName);
if (ComponentEditWindow.ShowAssetDropTarget<Texture>(ref texture.TextureHandle))
{
}
ImGui.EndDisabled();
ImGui.PopID();
}
}
return isDirty;
}
private static bool DrawLockButton(bool isLocked)
{
ImGui.PushStyleVar(.ItemInnerSpacing, ImGui.Vec2(0, 0));
ImGui.PushStyleColor(.Button, ImGui.Vec4(0, 0, 0, 0));
let colors = ImGui.GetStyle().Colors;
ImGui.Vec4 hoveredColor = colors[(int)ImGui.Col.ButtonHovered];
hoveredColor.w = 0.5f;
ImGui.Vec4 activeColor = colors[(int)ImGui.Col.ButtonActive];
activeColor.w = 0.5f;
ImGui.PushStyleColor(.ButtonHovered, hoveredColor);
ImGui.PushStyleColor(.ButtonActive, activeColor);
float padding = 2.0f;
float size = ImGui.GetTextLineHeight() - 2 * padding;
ImGui.PushID(0);
bool result = ImGui.ImageButton("", isLocked ? EditorIcons.Instance.Icon_Locked : EditorIcons.Instance.Icon_Unlocked, .(size, size), .Zero, .Ones);
ImGui.PopID();
if (isLocked)
ImGui.AttachTooltip("Click to unlock. The property is locked and cannot be changed by children.");
else
ImGui.AttachTooltip("Click to lock. The property is unlocked and can be changed by children.");
ImGui.PopStyleColor(3);
ImGui.PopStyleVar(1);
return result;
}
/// <summary>
/// Converts a variable name to a pretty name as good as reasonably possible.
/// </summary>
/// <param name="uglyName">The name of a variable to prettify.</param>
/// <returns>The pretty string.</returns>
public static void ToPrettyName(StringView uglyName, String outPrettyName)
{
outPrettyName.PrepareBuffer(uglyName.Length);
outPrettyName.Clear();
bool wasUpper = false;
bool inWord = false;
bool inNumber = false;
for (char8 c in uglyName)
{
if (c.IsLetter)
{
if (inNumber)
{
outPrettyName.Append(' ');
inNumber = false;
}
bool newWord = !inWord;
if (c.IsUpper && !wasUpper)
{
newWord = true;
wasUpper = true;
if (inWord)
{
outPrettyName.Append(' ');
}
}
else if (c.IsLower)
{
wasUpper = false;
}
outPrettyName.Append(newWord ? c.ToUpper : c.ToLower);
inWord = true;
}
else if (c.IsDigit)
{
if (inWord)
{
outPrettyName.Append(' ');
inWord = false;
inNumber = true;
}
outPrettyName.Append(c);
}
else if (inWord || inNumber)
{
outPrettyName.Append(' ');
inWord = false;
inNumber = false;
wasUpper = false;
}
}
}
}
@@ -1,62 +0,0 @@
using Bon;
using GlitchyEngine.Content;
using System;
using System.Collections;
using System.IO;
using GlitchyEngine;
using GlitchyEngine.Renderer;
namespace GlitchyEditor.Assets;
class EffectAssetPropertiesEditor : AssetPropertiesEditor
{
public this(AssetFile asset) : base(asset)
{
}
public override void ShowEditor()
{
}
public static AssetPropertiesEditor Factory(AssetFile assetFile)
{
return new Self(assetFile);
}
}
[BonTarget, BonPolyRegister]
class EffectAssetLoaderConfig : AssetLoaderConfig
{
}
class EffectAssetLoader : IAssetLoader //, IReloadingAssetLoader
{
private static readonly List<StringView> _fileExtensions = new .(){".hlsl"} ~ delete _;
public static List<StringView> FileExtensions => _fileExtensions;
public AssetLoaderConfig GetDefaultConfig()
{
return new EffectAssetLoaderConfig();
}
public Asset LoadAsset(Stream file, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager)
{
Effect effect = new Effect(file, assetIdentifier, contentManager);
return effect;
}
public Asset GetPlaceholderAsset(Type assetType)
{
return default;
}
public Asset GetErrorAsset(Type assetType)
{
return default;
}
}
@@ -0,0 +1,96 @@
using System;
using System.IO;
using GlitchyEngine.Renderer;
using GlitchyEditor.Assets.Processors;
using GlitchyEngine;
using GlitchyEditor.Assets.Importers;
using GlitchyEngine.Content;
using System.Diagnostics;
namespace GlitchyEditor.Assets.Exporters;
class MaterialExporter : IAssetExporter
{
public static AssetType ExportedAssetType => .Material;
public AssetExporterConfig CreateDefaultConfig()
{
return new AssetExporterConfig();
}
public Result<void> Export(Stream stream, ProcessedResource processedResource, AssetConfig config)
{
Log.EngineLogger.AssertDebug(processedResource is ProcessedMaterial);
ProcessedMaterial processedMaterial = (.)processedResource;
Compiler.Assert(sizeof(AssetHandle) == 8);
/*
File Format:
Effect Asset Handle (8 byte)
Texture Count (uint16)
Textures
{
Texture Handle (8 byte)
Texture Name Length (int16)
Texture Name Data...
}
Buffer Count (uint16)
Buffers
{
Buffer Size (int64)
Buffer Name Length (int16)
Buffer Name Data...
Raw Buffer Data...
}
*/
Try!(stream.Write((AssetHandle)processedMaterial.EffectHandle));
Try!(WriteTextures(stream, processedMaterial));
Try!(WriteBuffers(stream, processedMaterial));
return .Ok;
}
private Result<void> WriteTextures(Stream stream, ProcessedMaterial processedMaterial)
{
Log.EngineLogger.Assert(processedMaterial.Textures.Count < int16.MaxValue);
Try!(stream.Write((uint16)processedMaterial.Textures.Count));
for (let (textureName, textureHandle) in processedMaterial.Textures)
{
Try!(stream.Write((AssetHandle)textureHandle));
Log.EngineLogger.Assert(textureName.Length < int16.MaxValue);
Try!(stream.Write((int16)textureName.Length));
Try!(stream.Write(textureName));
}
return .Ok;
}
private Result<void> WriteBuffers(Stream stream, ProcessedMaterial processedMaterial)
{
Log.EngineLogger.Assert(processedMaterial.Buffers.Count < int16.MaxValue);
Try!(stream.Write((uint16)processedMaterial.Buffers.Count));
for (let (bufferName, bufferData) in processedMaterial.Buffers)
{
Try!(stream.Write((int64)bufferData.Count));
Log.EngineLogger.Assert(bufferName.Length < int16.MaxValue);
Try!(stream.Write((int16)bufferName.Length));
Try!(stream.Write(bufferName));
Try!(stream.Write(Span<uint8>(bufferData)));
}
return .Ok;
}
}
@@ -0,0 +1,169 @@
using GlitchyEngine;
using GlitchyEditor.Assets.Importers;
using GlitchyEditor.Assets.Processors;
using GlitchyEngine.Content;
using System;
using System.IO;
namespace GlitchyEditor.Assets.Exporters;
class ShaderExporter : IAssetExporter
{
public static AssetType ExportedAssetType => .Shader;
public AssetExporterConfig CreateDefaultConfig() => new AssetExporterConfig();
public Result<void> Export(Stream stream, ProcessedResource processedResource, AssetConfig config)
{
Log.EngineLogger.AssertDebug(processedResource is ProcessedShader);
ProcessedShader shader = (.)processedResource;
/*
File Format:
Vertex Shader Blob Length (uint64 / 8 bytes) (0 if null)
Pixel Shader Blob Length (uint64 / 8 bytes) (0 if null)
Vertex Shader Data...
Pixel Shader Data...
Texture Count (uint16)
Textures
{
Texture Dimension (1 byte)
Vertex Shader Bind Point (int32, 4 bytes) # TODO: ASSUME same bind point for all shader stages?
Pixel Shader Bind Point (int32, 4 bytes) # TODO: ASSUME same bind point for all shader stages?
Texture Name Length (uint16)
Texture Name Data...
}
Buffer Count (uint16)
Buffers
{
Buffer Size (int64)
Vertex Shader Bind Point (int32, 4 bytes) # TODO: ASSUME same bind point for all shader stages?
Pixel Shader Bind Point (int32, 4 bytes) # TODO: ASSUME same bind point for all shader stages?
Buffer Name Length (uint16)
Buffer Name Data...
Engine Buffer Name Length (uint16)
Engine Buffer Name Data...
Variable Count (uint16)
Variables
{
Offset (uint64)
Size In Bytes (uint64)
IsUsed (bool, 1 byte)
ShaderVariableType (1 Byte)
Rows (uint8)
Columns (uint8)
ArraySize (uint64)
Name Length (uint16)
Name Data...
Preview Name Length (uint16)
Preview Name Data...
Type Editor Name Length (uint16)
Type Editor Name Data...
# TODO: Min and Max
}
RawData...
}
*/
Span<uint8> vsData = shader.VertexShader?.Blob ?? Span<uint8>();
Span<uint8> psData = shader.PixelShader?.Blob ?? Span<uint8>();
Try!(stream.Write((uint64)vsData.Length));
Try!(stream.Write((uint64)psData.Length));
if (vsData.Ptr != null)
Try!(stream.Write(vsData));
if (psData.Ptr != null)
Try!(stream.Write(psData));
Try!(WriteTextures(stream, shader));
Try!(WriteConstantBuffers(stream, shader));
return .Ok;
}
private Result<void> WriteTextures(Stream stream, ProcessedShader shader)
{
Log.EngineLogger.Assert(shader.Textures.Count < int16.MaxValue);
Try!(stream.Write((uint16)shader.Textures.Count));
for (let (textureName, textureEntry) in shader.Textures)
{
Try!(stream.Write((uint8)textureEntry.TextureDimension));
Try!(stream.Write((int32)textureEntry.VertexShaderBindPoint));
Try!(stream.Write((int32)textureEntry.PixelShaderBindPoint));
WriteSizedString<uint16>(stream, textureName);
}
return .Ok;
}
private Result<void> WriteConstantBuffers(Stream stream, ProcessedShader shader)
{
Log.EngineLogger.Assert(shader.ConstantBuffers.Count < int16.MaxValue);
Try!(stream.Write((uint16)shader.ConstantBuffers.Count));
for (let (bufferName, bufferEntry) in shader.ConstantBuffers)
{
ReflectedConstantBuffer buffer = bufferEntry.ConstantBuffer;
Try!(stream.Write((uint64)buffer.Size));
Try!(stream.Write((int32)bufferEntry.VertexShaderBindPoint));
Try!(stream.Write((int32)bufferEntry.PixelShaderBindPoint));
WriteSizedString<uint16>(stream, bufferName);
WriteSizedString<uint16>(stream, bufferEntry.ConstantBuffer.EngineBufferName);
Log.EngineLogger.Assert(buffer.Variables.Count < int16.MaxValue);
Try!(stream.Write((uint16)buffer.Variables.Count));
for (let (variableName, variable) in buffer.Variables)
{
Try!(stream.Write((uint64)variable.Offset));
Try!(stream.Write((uint64)variable.SizeInBytes));
Try!(stream.Write((uint8)(variable.IsUsed ? 1 : 0)));
Try!(stream.Write((uint8)(variable.ElementType)));
Log.EngineLogger.Assert(variable.Rows < int8.MaxValue);
Log.EngineLogger.Assert(variable.Columns < int8.MaxValue);
Try!(stream.Write((uint8)variable.Rows));
Try!(stream.Write((uint8)variable.Columns));
Try!(stream.Write((uint64)variable.ArraySize));
WriteSizedString<uint16>(stream, variableName);
WriteSizedString<uint16>(stream, variable.PreviewName);
WriteSizedString<uint16>(stream, variable.EditorType);
}
Try!(stream.Write(Span<uint8>(buffer.RawData, 0, buffer.Size)));
}
return .Ok;
}
private static Result<void> WriteSizedString<Tint>(Stream stream, StringView string)
where Tint : IInteger
where int : operator explicit Tint
where Tint : operator explicit int
where Tint : struct
{
Log.EngineLogger.Assert(string.Length < (int)typeof(Tint).MaxValue);
Try!(stream.Write((Tint)string.Length));
if (string.Length > 0)
Try!(stream.WriteStrUnsized(string));
return .Ok;
}
}
@@ -26,7 +26,7 @@ class TextureExporter : IAssetExporter
/*
File Format:
TextureType (1 byte)
Texture Dimension (1 byte)
Pixel Format (4 bytes)
Width of larges mip-slice (4 bytes)
Height of larges mip-slice (4 bytes)
+1 -1
View File
@@ -8,7 +8,7 @@ namespace GlitchyEditor.Assets.Importers;
abstract class Config
{
[BonIgnore]
protected bool _changed = true;
protected bool _changed = false;
public bool Changed => _changed;
@@ -0,0 +1,236 @@
using System;
using System.Collections;
using GlitchyEngine.Content;
using GlitchyEngine.Renderer;
using Bon;
using GlitchyEngine.Math;
using System.IO;
using Bon.Integrated;
using GlitchyEngine;
namespace GlitchyEditor.Assets.Importers;
[BonTarget]
class MaterialVariable
{
public ShaderVariableType ElementType;
public int MatrixColumns;
public int MatrixRows;
public int ArrayElements;
public uint8[] RawData ~ delete _;
public this()
{
}
public this(ShaderVariableType elementType, int matrixColumns, int matrixRows, int arrayElements)
{
ElementType = elementType;
MatrixColumns = matrixColumns;
MatrixRows = matrixRows;
ArrayElements = arrayElements;
RawData = new uint8[elementType.ElementSizeInBytes() * matrixColumns * matrixRows * arrayElements];
}
static this()
{
gBonEnv.typeHandlers.Add(typeof(Self),
((.)new => VariableValueSerialize, (.)new => VariableValueDeserialize));
}
static void VariableValueSerialize(BonWriter writer, ValueView value, BonEnvironment env, SerializeValueState state)
{
/*Log.EngineLogger.Assert(value.type == typeof(Self));
let variableValue = value.Get<Self>();
writer.Type(variableValue)
using (writer.ObjectBlock())
{
Serialize.Value(writer, nameof(MaterialFile.Effect), materialFile.Effect, env);
Serialize.Value(writer, nameof(MaterialFile.Textures), materialFile.Textures, env);
}*/
}
static Result<void> VariableValueDeserialize(BonReader reader, ValueView val, BonEnvironment env, DeserializeValueState state)
{
MaterialVariable output = val.Get<MaterialVariable>();
StringView typeName = Try!(reader.EnumName());
// If we don't find a digit, we assume entire type name is element type and rows string ends up being an empty string.
int firstDigitIndex = typeName.Length;
for (char8 c in typeName)
{
if (c.IsDigit)
{
firstDigitIndex = @c.Index;
}
}
Result<ShaderVariableType> elementTypeResult = Enum.Parse<ShaderVariableType>(typeName.Substring(0, firstDigitIndex), true);
if (elementTypeResult case .Err)
{
Deserialize.Error!("Unknown element type.", reader);
}
int xIndex = typeName.LastIndexOf('x');
StringView rowsString = null;
StringView columnsString = null;
if (xIndex != -1)
{
rowsString = typeName.Substring(firstDigitIndex ..< xIndex);
columnsString = typeName.Substring(xIndex + 1);
}
else
{
rowsString = typeName.Substring(firstDigitIndex);
}
Result<int, Int.ParseError> rowsResult = int.Parse(rowsString);
if (rowsResult case .Err(let error))
{
switch (error)
{
case .NoValue:
rowsResult = 1;
case .Overflow:
Deserialize.Error!("Integer overflow in row count.", reader);
case .InvalidChar:
Deserialize.Error!("Invalid character in row count.", reader);
default:
return .Err;
}
}
Result<int, Int.ParseError> columnsResult = int.Parse(columnsString);
if (columnsResult case .Err(let error))
{
switch (error)
{
case .NoValue:
columnsResult = 1;
case .Overflow:
Deserialize.Error!("Integer overflow in column count.", reader);
case .InvalidChar:
Deserialize.Error!("Invalid character in column count.", reader);
default:
return .Err;
}
}
// TODO: Array
/*if (reader.[Friend]Check('['))
{
}*/
output.ElementType = elementTypeResult.Get();
output.MatrixRows = rowsResult.Get();
output.MatrixColumns = columnsResult.Get();
output.ArrayElements = 1;
int elementSize = output.ElementType.ElementSizeInBytes();
int elementCount = output.MatrixColumns * output.MatrixRows;
output.RawData = new uint8[elementSize * elementCount * output.ArrayElements];
Try!(reader.ObjectBlock());
for (int currentElementIndex < elementCount)
{
switch (output.ElementType)
{
case .Float:
StringView valueString = Try!(reader.Floating());
float* rawFloats = (float*)output.RawData.Ptr;
rawFloats[currentElementIndex] = Try!(float.Parse(valueString));
case .Bool:
// TODO: I'm pretty sure this is wrong, bools in c buffers are usually 32 bit
bool value = Try!(reader.Bool());
((bool*)&output.RawData)[currentElementIndex] = value;
case .Int:
StringView valueString = Try!(reader.Integer());
((int32*)&output.RawData)[currentElementIndex] = Try!(int32.Parse(valueString));
case .UInt:
StringView valueString = Try!(reader.Integer());
((uint32*)&output.RawData)[currentElementIndex] = Try!(uint32.Parse(valueString));
}
if (reader.ObjectHasMore())
{
Try!(reader.EntryEnd());
}
else
{
Log.EngineLogger.Warning("Vector/Matrix doesn't contain enough elements.");
break;
}
}
Try!(reader.ObjectBlockEnd());
return .Ok;
}
}
[BonTarget]
class NewMaterialFile : ImportedResource
{
public AssetHandle<Effect> Effect;
public Dictionary<String, AssetHandle<Texture>> Textures = new .() ~ DeleteDictionaryAndKeys!(_);
public Dictionary<String, MaterialVariable> Constants = new .() ~ DeleteDictionaryAndKeysAndValues!(_);
public this(AssetIdentifier ownAssetIdentifier) : base(ownAssetIdentifier)
{
}
}
class MaterialImporter: IAssetImporter
{
private static readonly List<StringView> _fileExtensions = new .(){".mat"} ~ delete _;
public static List<StringView> FileExtensions => _fileExtensions;
public static Type ProcessedAssetType => typeof(NewMaterialFile);
public AssetImporterConfig CreateDefaultConfig()
{
return new AssetImporterConfig();
}
public Result<ImportedResource> Import(StringView fullFileName, AssetIdentifier assetIdentifier, AssetConfig config)
{
NewMaterialFile material = new .(new AssetIdentifier(assetIdentifier.FullIdentifier));
defer
{
if (@return case .Err)
{
delete material;
}
}
String fullText = scope .();
Try!(File.ReadAllText(fullFileName, fullText, true));
Try!(Bon.Deserialize<NewMaterialFile>(ref material, fullText));
return material;
}
}
@@ -0,0 +1,51 @@
using Bon;
using System;
using System.Collections;
using GlitchyEngine.Content;
using System.IO;
namespace GlitchyEditor.Assets.Importers;
class ImportedShader : ImportedResource
{
private String _hlslCode = new .() ~ delete _;
public StringView HlslCode
{
get => _hlslCode;
}
public this(AssetIdentifier ownAssetIdentifier) : base(ownAssetIdentifier)
{
}
}
[BonTarget, BonPolyRegister]
class ShaderImporterConfig : AssetImporterConfig
{
}
class ShaderImporter : IAssetImporter
{
private static readonly List<StringView> _fileExtensions = new .(){".fx"} ~ delete _;
public static List<StringView> FileExtensions => _fileExtensions;
public static Type ProcessedAssetType => typeof(ImportedShader);
public AssetImporterConfig CreateDefaultConfig()
{
return new AssetImporterConfig();
}
public Result<ImportedResource> Import(StringView fullFileName, AssetIdentifier assetIdentifier, AssetConfig config)
{
ImportedShader shader = new ImportedShader(new AssetIdentifier(assetIdentifier));
Try!(File.ReadAllText(fullFileName, shader.[Friend]_hlslCode, true));
shader.[Friend]_hlslCode.Append('\n');
return shader;
}
}
+3 -311
View File
@@ -144,7 +144,7 @@ class MaterialAssetPropertiesEditor : AssetPropertiesEditor
{
if (previewType.Get<String>().Equals("Color", .InvariantCultureIgnoreCase))
{
Log.EngineLogger.AssertDebug(variable.Type == .Float && variable.Rows == 1);
Log.EngineLogger.AssertDebug(variable.ElementType == .Float && variable.Rows == 1);
if (variable.Columns == 3)
{
@@ -173,7 +173,7 @@ class MaterialAssetPropertiesEditor : AssetPropertiesEditor
}
else if (previewType.Get<String>().Equals("ColorHDR", .InvariantCultureIgnoreCase))
{
Log.EngineLogger.AssertDebug(variable.Type == .Float && variable.Rows == 1);
Log.EngineLogger.AssertDebug(variable.ElementType == .Float && variable.Rows == 1);
if (variable.Columns == 3)
{
@@ -203,7 +203,7 @@ class MaterialAssetPropertiesEditor : AssetPropertiesEditor
}
//ImGui.ColorEdit3("", null, .HDR | .Float)
else if (variable.Type == .Float && variable.Rows == 1)
else if (variable.ElementType == .Float && variable.Rows == 1)
{
bool hasMin = TryGetValue(arguments, "Min", var min);
bool hasMax = TryGetValue(arguments, "Max", var max);
@@ -255,311 +255,3 @@ class MaterialAssetPropertiesEditor : AssetPropertiesEditor
return new Self(assetFile);
}
}
[BonTarget, BonPolyRegister]
class MaterialAssetLoaderConfig : AssetLoaderConfig
{
}
[BonTarget]
public enum VariableValue
{
case bool(bool Value);
case bool2(bool2 Value);
case bool3(bool3 Value);
case bool4(bool4 Value);
case int(int Value);
case int2(int2 Value);
case int3(int3 Value);
case int4(int4 Value);
case uint(uint Value);
case uint2(uint2 Value);
case uint3(uint3 Value);
case uint4(uint4 Value);
case Float(float Value);
case Float2(float2 Value);
case Float3(float3 Value);
case Float4(float4 Value);
case ColorRGB(ColorRGB Value);
case ColorRGBA(ColorRGBA Value);
case None;
/*static this()
{
gBonEnv.typeHandlers.Add(typeof(Self),
((.)new => VariableValueSerialize, (.)new => VariableValueDeserialize));
}
static void VariableValueSerialize(BonWriter writer, ValueView value, BonEnvironment env)
{
Log.EngineLogger.Assert(value.type == typeof(Self));
let variableValue = value.Get<Self>();
writer.Type(variableValue)
using (writer.ObjectBlock())
{
Serialize.Value(writer, nameof(MaterialFile.Effect), materialFile.Effect, env);
Serialize.Value(writer, nameof(MaterialFile.Textures), materialFile.Textures, env);
}
}
static Result<void> VariableValueDeserialize(BonReader reader, ValueView val, BonEnvironment env)
{
return .Ok;
}*/
}
[BonTarget]
class MaterialFile
{
public String Effect ~ delete _;
//public Dictionary<String, String> Textures ~ DeleteDictionaryAndKeysAndValues!(_);
public Dictionary<String, AssetHandle<Texture>> Textures ~ DeleteDictionaryAndKeys!(_);
public Dictionary<String, VariableValue> Variables ~
{
if (_ != null)
{
for (var entry in _)
{
delete entry.key;
//delete entry.value;
/*if (entry.value.HasValue)
entry.value->Dispose();*/
}
delete _;
}
};
/*static this()
{
gBonEnv.typeHandlers.Add(typeof(Self),
((.)new => MaterialSerialize, (.)new => MaterialDeserialize));
}
static void MaterialSerialize(BonWriter writer, ValueView value, BonEnvironment env)
{
Log.EngineLogger.Assert(value.type == typeof(Self));
let materialFile = value.Get<Self>();
using (writer.ObjectBlock())
{
Serialize.Value(writer, nameof(MaterialFile.Effect), materialFile.Effect, env);
Serialize.Value(writer, nameof(MaterialFile.Textures), materialFile.Textures, env);
}
}
private static void SerializeVariablesDictionary(BonWriter writer, MaterialFile materialFile, BonEnvironment env)
{
using (writer.ArrayBlock())
{
for (let (name, value) in materialFile.Variables)
{
let keyVal = ValueView(typeof(String), name);
Serialize.Value(writer, keyVal, env);
writer.Pair();
ValueView valueVal;// = ValueView(, entriesPtr + (currentIndex * entryStride) + entryValueOffset);
switch(value.GetType())
{
case typeof(ColorRGBA):
writer.Identifier("ColorRGBA");
default:
}
Serialize.Value(writer, valueVal, env);
}
}
}
static Result<void> MaterialDeserialize(BonReader reader, ValueView val, BonEnvironment env)
{
return .Ok;
}*/
}
class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
{
private static readonly List<StringView> _fileExtensions = new .(){".mat"} ~ delete _;
public static List<StringView> FileExtensions => _fileExtensions;
public AssetLoaderConfig GetDefaultConfig()
{
return new ModelAssetLoaderConfig();
}
public Asset LoadAsset(Stream file, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager)
{
StreamReader reader = scope .(file);
String text = scope .();
reader.ReadToEnd(text);
MaterialFile materialFile = scope .();
var result = Bon.Deserialize<MaterialFile>(ref materialFile, text);
if (result case .Err)
{
Log.EngineLogger.Error("Failed to load material.");
Debug.SafeBreak();
return null;
}
Effect fx = Content.GetAsset<Effect>(contentManager.LoadAsset(materialFile.Effect, true), contentManager);
Material material = new Material(fx);
for (let (slotName, textureHandle) in materialFile.Textures)
{
AssetHandle<Texture> texture = contentManager.LoadAsset(textureHandle);
if (texture.IsInvalid)
{
Log.EngineLogger.Error($"Failed to load texture \"{textureHandle}\".");
}
material.SetTexture(slotName, texture);
}
for (let (slotName, variableValue) in materialFile.Variables)
{
switch (variableValue)
{
case .ColorRGBA(let value):
material.SetVariable(slotName, value);
case .ColorRGB(let value):
material.SetVariable(slotName, value);
case .Float(let value):
material.SetVariable(slotName, value);
case .Float2(let value):
material.SetVariable(slotName, value);
case .Float3(let value):
material.SetVariable(slotName, value);
case .Float4(let value):
material.SetVariable(slotName, value);
case .None:
default:
Log.EngineLogger.Error($"Unknown variable type of variable {slotName}: {variableValue}");
}
}
return material;
}
public Result<void> EditorSaveAsset(Stream file, Asset asset, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager)
{
Material material = asset as Material;
if (material == null)
{
Log.EngineLogger.Error("Asset must be a Material!");
return .Err;
}
MaterialFile materialFile = scope .();
materialFile.Effect = new String(material.Effect?.Identifier ?? "");
materialFile.Textures = new .();
materialFile.Variables = new .();
for (let (slotName, texture) in material.[Friend]_textures)
{
materialFile.Textures.Add(new String(slotName), texture.Handle);
}
Effect effect = material.Effect;
if (effect != null)
{
for (let (name, arguments) in effect.[Friend]_variableDescriptions)
{
VariableValue variableValue = .None;
let variable = effect.Variables[name];
bool hasPreviewType = MaterialAssetPropertiesEditor.TryGetValue(arguments, "Type", var previewType);
if (hasPreviewType && previewType.Get<String>() == "Color")
{
Log.EngineLogger.AssertDebug(variable.Type == .Float && variable.Rows == 1);
if (variable.Columns == 3)
{
material.GetVariable<ColorRGB>(variable.Name, var value);
value = ColorRGB.LinearToSRGB((ColorRGB)value);
//variantValue = new box value;
variableValue = .ColorRGB(value);
}
else if (variable.Columns == 4)
{
material.GetVariable<ColorRGBA>(variable.Name, var value);
value = ColorRGBA.LinearToSRGB((ColorRGBA)value);
variableValue = .ColorRGBA(value);
}
}
else if (variable.Type == .Float && variable.Rows == 1)
{
switch (variable.Columns)
{
case 1:
material.GetVariable<float>(variable.Name, let value);
variableValue = .Float(value);
case 2:
material.GetVariable<float2>(variable.Name, let value);
variableValue = .Float2(value);
case 3:
material.GetVariable<float3>(variable.Name, let value);
variableValue = .Float3(value);
case 4:
material.GetVariable<float4>(variable.Name, let value);
variableValue = .Float4(value);
}
}
materialFile.Variables.Add(new String(name), variableValue);
}
}
String text = scope .();
gBonEnv.serializeFlags |= .IncludeDefault | .Verbose;
Bon.Serialize<MaterialFile>(materialFile, text);
StreamWriter writer = scope .(file, .UTF8, 1024);
writer.Write(text);
return .Ok;
}
Material _placeholder;
Material _error;
public Asset GetPlaceholderAsset(Type assetType)
{
return default;
}
public Asset GetErrorAsset(Type assetType)
{
return default;
}
}
@@ -0,0 +1,129 @@
using System;
using System.Collections;
using GlitchyEditor.Assets.Importers;
using GlitchyEngine.Renderer;
using GlitchyEngine;
using GlitchyEngine.Content;
using System.Diagnostics;
namespace GlitchyEditor.Assets.Processors;
class ProcessedMaterial : ProcessedResource
{
public AssetHandle EffectHandle;
private Dictionary<String, uint8[]> _bufferData = new .() ~ DeleteDictionaryAndKeysAndValues!(_);
private Dictionary<String, AssetHandle> _textures = new .() ~ DeleteDictionaryAndKeys!(_);
public override AssetType AssetType => .Material;
public Dictionary<String, uint8[]> Buffers => _bufferData;
public Dictionary<String, AssetHandle> Textures => _textures;
public this(AssetIdentifier ownAssetIdentifier, AssetHandle assetHandle) : base(ownAssetIdentifier, assetHandle)
{
}
public Span<uint8> CloneBuffer(String name, ConstantBuffer cBuffer)
{
String copyName = new String(name);
uint8[] data = new uint8[cBuffer.RawData.Length];
cBuffer.RawData.CopyTo(data);
_bufferData.Add(copyName, data);
return data;
}
public void AddTexture(StringView name, AssetHandle textureHandle)
{
String copyName = new String(name);
_textures.Add(copyName, textureHandle);
}
}
class MaterialProcessor : IAssetProcessor
{
public AssetProcessorConfig CreateDefaultConfig()
{
return new AssetProcessorConfig();
}
public static Type ProcessedAssetType => typeof(NewMaterialFile);
public Result<void> Process(ImportedResource importedResource, AssetConfig config, List<ProcessedResource> outProcessedResources)
{
NewMaterialFile importedMaterial = importedResource as NewMaterialFile;
if (importedMaterial == null)
{
Log.EngineLogger.Error($"Expected type {nameof(NewMaterialFile)} but received {importedResource.GetType()} instead.");
return .Err;
}
Effect effect = Content.GetAsset<Effect>(importedMaterial.Effect, blocking: true);
if (effect == null)
// TODO: Don't error out, use the error-effect
return .Err;
ProcessedMaterial processedMaterial = new .(new AssetIdentifier(importedMaterial.AssetIdentifier), config.AssetHandle);
processedMaterial.EffectHandle = importedMaterial.Effect;
for (let buffer in effect.Buffers)
{
// TODO: Skip engine buffers
if (buffer.Buffer == null)
continue;
ConstantBuffer cBuffer = buffer.Buffer as ConstantBuffer;
if (cBuffer == null)
{
Log.EngineLogger.Error("The buffers of a material must be ConstantBuffers.");
continue;
// TODO: Why even allow any other kind of Buffer for Materials? Will this make sense later if we can create and bind Buffers from C#?
}
Span<uint8> data = processedMaterial.CloneBuffer(buffer.Name, cBuffer);
for (BufferVariable effectVariable in cBuffer.Variables)
{
if (importedMaterial.Constants.TryGetValue(effectVariable.Name, let materialValue))
{
if (effectVariable.ElementType != materialValue.ElementType)
{
Log.EngineLogger.Error("Element type doesn't match between material and effect.");
continue;
}
int effectVariableSize = effectVariable.Rows * effectVariable.Columns * Math.Max(1, effectVariable.ArrayElements) * effectVariable.ElementType.ElementSizeInBytes();
if (effectVariableSize != materialValue.RawData.Count)
{
Log.EngineLogger.Warning("Warning, matrix size in matrix and effect doesn't match. Truncating value.");
}
int bytesToCopy = Math.Min(effectVariableSize, materialValue.RawData.Count);
Debug.Assert(effectVariable.Offset + bytesToCopy <= data.Length, "Material value goes out of bounds ");
Internal.MemCpy(data.Ptr + effectVariable.Offset, materialValue.RawData.Ptr, bytesToCopy);
}
}
}
for (let (textureName, textureBinding) in effect.Textures)
{
if (importedMaterial.Textures.TryGetValue(textureName, let textureHandle))
{
processedMaterial.AddTexture(textureName, textureHandle);
}
}
outProcessedResources.Add(processedMaterial);
return .Ok;
}
}
@@ -0,0 +1,331 @@
using System;
using System.Collections;
using GlitchyEngine;
using GlitchyEngine.Math;
namespace GlitchyEditor.Assets.Processors;
static class ShaderCodePreprocessor
{
public static Result<void> ProcessFileContent(String fileContent, String outVsName, String outPsName,
Dictionary<StringView, ShaderVariable> outVarDescs,
List<String> outBufferNames, Dictionary<StringView, StringView> outEngineBuffers)
{
Debug.Profiler.ProfileResourceFunction!();
Dictionary<StringView, StringView> arguments = scope .();
int index = 0;
while (true)
{
if ((int Start, int End) value = GetNextPreprocessor(fileContent, index, let name, arguments..Clear()))
{
index = value.End;
switch(name)
{
case "Effect":
for (let (argName, argValue) in arguments)
{
switch(argName)
{
case "VS", "VertexShader":
outVsName.Append(argValue);
case "PS", "PixelShader":
outPsName.Append(argValue);
default:
Log.EngineLogger.Error($"Unknown parameter name \"{name}\".");
return .Err;
}
}
case "EditorVariable":
Try!(ProcessEditorVariables(arguments, outVarDescs));
case "EngineBuffer":
Try!(ProcessEngineBuffer(arguments, outBufferNames, outEngineBuffers));
default:
continue;
}
CommentLine(fileContent, value.Start);
}
else
{
break;
}
}
return .Ok;
}
private static void CommentLine(StringView code, int commentPosition)
{
code[commentPosition] = '/';
code[commentPosition + 1] = '/';
}
private static Result<(int Start, int End)> GetNextPreprocessor(StringView code, int startindex, out StringView name, Dictionary<StringView, StringView> arguments)
{
var startindex;
name = .();
int startOfLine = -1;
int startOfPragma = -1;
int endOfLine = startindex;
do
{
repeat
{
startindex = endOfLine;
startOfPragma = code.IndexOf("#pragma", startindex);
if (startOfPragma == -1)
return .Err;
endOfLine = code.IndexOf('\n', startOfPragma);
startOfLine = code[...startOfPragma].LastIndexOf('\n');
// If the line is commented out, look for the next
} while (startOfLine != -1 && code[startOfLine...startOfPragma].Contains("//"));
StringView line = (endOfLine != -1) ? code.Substring(startOfPragma, endOfLine - startOfPragma) : code.Substring(startOfPragma);
// cut off the #pragma
line = line.Substring(7);
int lBracketIndex = line.IndexOf('[');
if (lBracketIndex == -1)
{
name = line..Trim();
break;
}
name = line.Substring(0, lBracketIndex);
name.Trim();
int rBracketIndex = line.IndexOf(']');
if (rBracketIndex == -1)
{
Log.EngineLogger.Error($"Pragma is missing closing Bracket (\"{line}\")");
rBracketIndex = line.Length;
}
StringView argumentText = line.Substring(lBracketIndex + 1, rBracketIndex - lBracketIndex - 1);
for (StringView argument in argumentText.Split(';'))
{
int equalsIndex = argument.IndexOf('=');
StringView argumentName = .();
StringView argumentValue = .();
if (equalsIndex == -1)
{
argumentName = argument;
argumentName.Trim();
}
else
{
argumentName = argument.Substring(0, equalsIndex);
argumentName.Trim();
argumentValue = argument.Substring(equalsIndex + 1);
argumentValue.Trim();
}
if (arguments.ContainsKey(argumentName))
{
Log.EngineLogger.Error($"Arguments \"{argumentName}\" already exists.");
continue;
}
arguments.Add(argumentName, argumentValue);
}
}
return .Ok((startOfPragma, endOfLine));
}
private static Result<void> ProcessEngineBuffer(Dictionary<StringView, StringView> arguments,
List<String> outBufferNames, Dictionary<StringView, StringView> outEngineBuffers)
{
String nameInEngine = null;
String nameInShader = null;
defer
{
if (@return case .Err)
{
delete nameInEngine;
delete nameInShader;
}
}
for (var (argName, argValue) in arguments)
{
if (argValue.StartsWith('"') && argValue.EndsWith('"'))
{
argValue = argValue[1...^2];
}
switch (argName)
{
case "Name":
nameInShader = new String(argValue);
case "Binding":
nameInEngine = new String(argValue);
default:
Log.EngineLogger.Error($"Unknown parameter for EngineBuffer: \"{argName}\".");
return .Err;
}
}
if (String.IsNullOrWhiteSpace(nameInEngine) || String.IsNullOrWhiteSpace(nameInShader))
{
Log.EngineLogger.Error($"Name and Binding need to be defined.");
return .Err;
}
outBufferNames.Add(nameInShader);
outBufferNames.Add(nameInEngine);
outEngineBuffers.Add(nameInShader, nameInEngine);
return .Ok;
}
private static Result<void> ProcessEditorVariables(Dictionary<StringView, StringView> arguments, Dictionary<StringView, ShaderVariable> outVarDescs)
{
ShaderVariable variable = new .();
defer
{
if (@return case .Err)
delete variable;
}
for (var (name, value) in arguments)
{
if (value.StartsWith('"') && value.EndsWith('"'))
{
value = value[1...^2];
}
switch(name)
{
case "Name":
variable.Name = value;
case "Preview":
variable.PreviewName = value;
case "Type":
variable.EditorType = value;
case "Min":
variable.MinValue = Try!(ParseVariableValue(value));
case "Max":
variable.MaxValue = Try!(ParseVariableValue(value));
default:
Variant paramValue = Variant.Create(new String(value), true);
variable.AddParameter(name, paramValue);
}
}
if (variable.Name.IsWhiteSpace)
{
Log.EngineLogger.Error("Failed to process shader: Missing argument \"Name\" int variable description.");
return .Err;
}
outVarDescs.Add(variable.Name, variable);
return .Ok;
}
private static Result<Variant> ParseVariableValue(StringView valueString)
{
// TODO: Simply always use double?
if (valueString[0].IsDigit || valueString[0] == '-')
{
var valueString;
if (valueString.EndsWith('f'))
valueString.Length--;
float value = Try!(float.Parse(valueString));
return Variant.Create(value);
}
else if (valueString.StartsWith("float"))
{
int index = 5;
int numComponents = valueString[index++] - '0';
while (valueString[index] != '(')
{
if (!valueString[index].IsWhiteSpace)
{
Log.EngineLogger.Error("Failed to process shader: Expected '('.");
return .Err;
}
index++;
}
if (numComponents < 2 || numComponents > 4)
{
Log.EngineLogger.Error($"Failed to process shader: Unsupported component count {numComponents}. Value must be between 2 and 4.");
return .Err;
}
float[] floats = scope float[numComponents];
for (int i < numComponents)
{
while (true)
{
char8 c = valueString[++index];
if (c.IsDigit || c == '.' || c == '-')
break;
}
int start = index;
while (true)
{
char8 c = valueString[++index];
if (!c.IsDigit && c != '.')
break;
}
int end = index;
StringView numberView = .(valueString, start, end - start);
var result = float.Parse(numberView);
if (result case .Ok(let value))
{
floats[i] = value;
}
}
if (numComponents == 2)
return Variant.Create(*(float2*)floats.Ptr);
else if (numComponents == 3)
return Variant.Create(*(float3*)floats.Ptr);
else
return Variant.Create(*(float4*)floats.Ptr);
}
else
{
Log.EngineLogger.Error($"Failed to process shader: Unsupported variable value: \"{valueString}\".");
return .Err;
}
}
}
@@ -0,0 +1,556 @@
using System;
using DirectX.Common;
using GlitchyEngine;
using System.IO;
using DirectX.D3DCompiler;
using GlitchyEngine.Renderer;
using DirectX.D3D11Shader;
using System.Collections;
using GlitchyEngine.Content;
namespace GlitchyEditor.Assets.Processors;
using internal GlitchyEditor.Assets.Processors;
class ShaderDefineValue
{
private String _name ~ delete _;
private String _definition ~ delete _;
public StringView Name
{
get => _name;
set => String.NewOrSet!(_name, value);
}
public StringView Definition
{
get => _definition;
set => String.NewOrSet!(_definition, value);
}
}
class CompiledShader
{
private Dictionary<StringView, ReflectedConstantBuffer> _buffers = new .() ~ DeleteDictionaryAndValues!(_);
private Dictionary<StringView, ReflectedTexture> _textures = new .() ~ DeleteDictionaryAndValues!(_);
public Dictionary<StringView, ReflectedConstantBuffer> ConstantBuffers => _buffers;
public Dictionary<StringView, ReflectedTexture> Textures => _textures;
public extern Span<uint8> Blob {get;}
public void AddConstantBuffer(ReflectedConstantBuffer buffer)
{
_buffers.Add(buffer.Name, buffer);
}
public void AddTexture(ReflectedTexture texture)
{
_textures.Add(texture.Name, texture);
}
}
class ReflectedConstantBuffer
{
private String _name ~ delete _;
private String _engineBufferName ~ delete _;
public int Size;
public int BindPoint;
public StringView Name
{
get => _name;
set => String.NewOrSet!(_name, value);
}
public StringView EngineBufferName
{
get => _engineBufferName;
set => String.NewOrSet!(_engineBufferName, value);
}
private Dictionary<StringView, ReflectedConstantBufferVariable> _variables = new .() ~ DeleteDictionaryAndValues!(_);
public Dictionary<StringView, ReflectedConstantBufferVariable> Variables => _variables;
public void AddVariable(ReflectedConstantBufferVariable vaiable)
{
_variables.Add(vaiable.Name, vaiable);
}
public uint8[] RawData ~ delete _;
}
class ReflectedTexture
{
private String _name ~ delete _;
public uint32 BindPoint;
public TextureDimension TextureDimension;
public StringView Name
{
get => _name;
set => String.NewOrSet!(_name, value);
}
public this(StringView name, uint32 bindPoint, TextureDimension textureDimension)
{
Name = name;
BindPoint = bindPoint;
TextureDimension = textureDimension;
}
}
class ReflectedConstantBufferVariable
{
private String _name ~ delete _;
private String _previewName ~ delete _;
private String _editorType ~ delete _;
public StringView Name
{
get => _name;
set => String.NewOrSet!(_name, value);
}
public StringView PreviewName
{
get => _previewName;
set => String.NewOrSet!(_previewName, value);
}
public StringView EditorType
{
get => _editorType;
set => String.NewOrSet!(_editorType, value);
}
public int Offset;
public int SizeInBytes;
public bool IsUsed;
public GlitchyEngine.Renderer.ShaderVariableType ElementType;
public int Rows;
public int Columns;
public int ArraySize;
public Variant MinValue;
public Variant MaxValue;
}
// TODO: Dx11
extension CompiledShader
{
internal ID3DBlob* _shaderBlob;
public override Span<uint8> Blob => Span<uint8>((uint8*)_shaderBlob.GetBufferPointer(), (int)_shaderBlob.GetBufferSize());
}
class ShaderCompiler
{
public static Result<CompiledShader> CompileAndReflectShader(StringView code, AssetIdentifier assetIdentifier, StringView entryPoint, StringView compileTarget, Span<ShaderDefineValue> defines)
{
defer {
if (@return case .Err)
delete shader;
}
CompiledShader shader = new CompiledShader();
ShaderCompilationContext context = scope .();
Try!(PlatformCompileShaderFromSource(code, assetIdentifier, entryPoint, compileTarget, defines, shader, context));
Try!(PlatformReflectShader(shader, context));
return shader;
}
protected static extern Result<void> PlatformCompileShaderFromSource(StringView code, AssetIdentifier fileName, StringView entryPoint, StringView compileTarget, Span<ShaderDefineValue> defines, CompiledShader outShader, ShaderCompilationContext context);
protected static extern Result<void> PlatformReflectShader(CompiledShader shader, ShaderCompilationContext context);
}
class ShaderCompilationContext
{
public append String VsName = String();
public append String PsName = String();
public append Dictionary<StringView, ShaderVariable> Variables = .() ~ ClearDictionaryAndDeleteValues!(_);
public append List<String> BufferNames = .() ~ ClearAndDeleteItems!(_);
// Name in Shader -> Name in Engine
public append Dictionary<StringView, StringView> EngineBuffers = .();
}
// TODO: Dx11
extension ShaderCompiler
{
// TODO: This should be a setting per shader, really!
protected const ShaderCompileFlags DefaultCompileFlags = .EnableStrictness |
#if DEBUG
.Debug;
#else
.OptimizationLevel3;
#endif
protected static override Result<void> PlatformCompileShaderFromSource(StringView code, AssetIdentifier fileName, StringView entryPoint, StringView compileTarget, Span<ShaderDefineValue> defines, CompiledShader outShader, ShaderCompilationContext context)
{
Debug.Profiler.ProfileResourceFunction!();
Log.EngineLogger.AssertDebug(outShader._shaderBlob == null);
ShaderMacro* nativeMacros = defines.Length == 0 ? null : new:ScopedAlloc! ShaderMacro[defines.Length]*;
for(int i < defines.Length)
{
nativeMacros[i].Name = defines[i].Name.ToScopedNativeWChar!::();
nativeMacros[i].Definition = defines[i].Definition.ToScopedNativeWChar!::();
}
// Todo: sourceName, includes,
// Todo: variable shader target?
ID3DBlob* errorBlob = null;
String directory = scope .();
Path.GetDirectoryPath(fileName, directory);
String shaderCode = new:ScopedAlloc! String(code);
Try!(ShaderCodePreprocessor.ProcessFileContent(shaderCode, context.VsName, context.PsName, context.Variables, context.BufferNames, context.EngineBuffers));
using (let includer = PreprocessingIncluder(directory, context))
{
var result = D3DCompiler.D3DCompile(code.Ptr, (.)code.Length, fileName.FullIdentifier.ToScopeCStr!(), nativeMacros, &includer, entryPoint.ToScopeCStr!(),
compileTarget.ToScopeCStr!(), DefaultCompileFlags /* Pass down compile flags? */, .None, &outShader._shaderBlob, &errorBlob);
if(result.Failed)
{
StringView str = StringView((char8*)errorBlob.GetBufferPointer(), (int)errorBlob.GetBufferSize());
Log.EngineLogger.Error($"Failed to compile Shader: Error Code({(int)result}): {result} | Error Message: {str}");
return .Err;
}
}
Log.EngineLogger.Assert(outShader._shaderBlob != null, "Shader compilation failed.");
return .Ok;
}
protected override static Result<void> PlatformReflectShader(CompiledShader shader, ShaderCompilationContext context)
{
Debug.Profiler.ProfileResourceFunction!();
ID3D11ShaderReflection* reflection = null;
defer { reflection?.Release(); }
var reflectionResult = D3DCompiler.D3DReflect(shader._shaderBlob.GetBufferPointer(), shader._shaderBlob.GetBufferSize(), &reflection);
if (reflectionResult.Failed)
{
Log.EngineLogger.Error($"Failed to reflect shader: ({(int)reflectionResult}) {reflectionResult}");
return .Err;
}
var getDescResult = reflection.GetDescription(let desc);
if (getDescResult.Failed)
{
Log.EngineLogger.Error($"GetDescription failed: ({(int)getDescResult}) {getDescResult}");
return .Err;
}
uint32 resourceCount = desc.BoundResources;
for (uint32 i < resourceCount)
{
var res = reflection.GetResourceBindingDescription(i, let bindDesc);
if (res.Failed)
{
Log.EngineLogger.Error($"Error({(int)res}) {res}: Failed to get resource binding desc for resource {i}");
return .Err;
}
switch(bindDesc.Type)
{
case .ConstantBuffer:
//var bufferReflection = reflection.GetConstantBufferByName(bindDesc.Name);
var bufferReflection = reflection.GetConstantBufferByName(bindDesc.Name);
bufferReflection.GetDescription(let bufferDesc);
// ConstantBuffer
if(bufferDesc.Type == .D3D11_CT_CBUFFER)
{
ReflectedConstantBuffer cbuffer = Try!(ReflectConstantBuffer(bindDesc, bufferReflection, context));
shader.AddConstantBuffer(cbuffer);
}
case .Texture:
TextureDimension textureDimension;
switch (bindDesc.Dimension)
{
case .Texture1D:
textureDimension = .Texture1D;
case .Texture1DArray:
textureDimension = .Texture1DArray;
case .Texture2D:
textureDimension = .Texture2D;
case .Texture2DArray:
textureDimension = .Texture2DArray;
case .Texture3D:
textureDimension = .Texture3D;
case .TextureCube:
textureDimension = .TextureCube;
case .TextureCubeArray:
textureDimension = .TextureCubeArray;
default:
textureDimension = .Unknown;
}
shader.AddTexture(new ReflectedTexture(StringView(bindDesc.Name), bindDesc.BindPoint, textureDimension));
case .Sampler:
// There is nothing to do for samplers
default:
Log.EngineLogger.Warning($"Unhandled shader resource type: \"{bindDesc.Type}\"");
}
}
return .Ok;
}
private static Result<ReflectedConstantBuffer> ReflectConstantBuffer(ShaderInputBindDescription bindDesc, ID3D11ShaderReflectionConstantBuffer* bufferReflection, ShaderCompilationContext context)
{
Debug.Profiler.ProfileResourceFunction!();
var buffer = new ReflectedConstantBuffer();
HResult result = bufferReflection.GetDescription(let bufferDescription);
Log.EngineLogger.Assert(result.Succeeded, scope $"Failed to get buffer description. Error({(int)result}): {result}");
Log.EngineLogger.AssertDebug(bufferDescription.Type == .D3D_CT_CBUFFER, "The buffer is not of type \"D3D_CT_CBUFFER\"");
buffer.Name = StringView(bufferDescription.Name);
buffer.Size = bufferDescription.Size;
buffer.BindPoint = bindDesc.BindPoint;
buffer.RawData = new uint8[buffer.Size];
for(uint32 v = 0; v < bufferDescription.Variables; v++)
{
ID3D11ShaderReflectionVariable* variableReflection = bufferReflection.GetVariableByIndex(v);
if (ReflectConstantBufferVariable(buffer, variableReflection, context) case .Err)
{
delete buffer;
return .Err;
}
}
if (context.EngineBuffers.TryGetValue(buffer.Name, let engineBufferName))
{
buffer.EngineBufferName = engineBufferName;
}
return buffer;
}
private static Result<void> ReflectConstantBufferVariable(ReflectedConstantBuffer buffer, ID3D11ShaderReflectionVariable* variableReflection, ShaderCompilationContext context)
{
Debug.Profiler.ProfileResourceFunction!();
HResult getDescResult = variableReflection.GetDescription(let variableDescription);
if (getDescResult.Failed)
{
Log.EngineLogger.Error($"Failed to get variable description. Error({(int)getDescResult}): {getDescResult}");
return .Err;
}
let variableType = variableReflection.GetVariableType();
let getTypeDescResult = variableType.GetDescription(let shaderTypeDescription);
if (getTypeDescResult.Failed)
{
Log.EngineLogger.Error($"Failed to get variable description. Error({(int)getDescResult}): {getDescResult}");
return .Ok;
}
ReflectedConstantBufferVariable variable = new ReflectedConstantBufferVariable();
variable.Name = StringView(variableDescription.Name);
variable.Offset = variableDescription.StartOffset;
variable.SizeInBytes = variableDescription.Size;
variable.IsUsed = variableDescription.uFlags.HasFlag(.Used);
variable.Columns = shaderTypeDescription.Columns;
variable.Rows = shaderTypeDescription.Rows;
variable.ArraySize = shaderTypeDescription.Elements;
switch(shaderTypeDescription.Type)
{
case .Bool:
variable.ElementType = .Bool;
case .Float:
variable.ElementType = .Float;
case .Int:
variable.ElementType = .Int;
case .UInt:
variable.ElementType = .UInt;
default:
Log.EngineLogger.Error($"Unhandled shader variable type: {shaderTypeDescription.Type}.");
delete variable;
return .Err;
}
if (variableDescription.DefaultValue == null)
{
Internal.MemSet(&buffer.RawData[variable.Offset], 0, variable.SizeInBytes);
}
else
{
Internal.MemCpy(&buffer.RawData[variable.Offset], variableDescription.DefaultValue, variable.SizeInBytes);
}
if (context.Variables.TryGetValue(variable.Name, let variableAnnotation))
{
variable.PreviewName = variableAnnotation.PreviewName;
variable.EditorType = variableAnnotation.EditorType;
// TODO: We need to somehow validate against our variable type
variable.MinValue = variableAnnotation.MinValue;
variable.MaxValue = variableAnnotation.MaxValue;
}
buffer.AddVariable(variable);
return .Ok;
}
}
struct PreprocessingIncluder : ID3DInclude, IDisposable
{
private VTable _vTable;
private Dictionary<StringView, (String FileName, String FileContent)> _loadedFiles;
private String _parentFileDirectory;
private ShaderCompilationContext _context;
public this(String parentFileDirectory, ShaderCompilationContext context)
{
_parentFileDirectory = parentFileDirectory;
_loadedFiles = new Dictionary<StringView, (String FileName, String FileContent)>();
_vTable.Open = => Open;
_vTable.Close = => Close;
_vt = &_vTable;
_context = context;
}
public void Dispose()
{
for (let (key, value) in _loadedFiles)
{
delete value.FileName;
delete value.FileContent;
}
delete _loadedFiles;
}
public static HResult Open(ID3DInclude* self, IncludeType includeType, char8* fileNamePtr, void* parentData, void** data, uint32* bytes)
{
PreprocessingIncluder* includer = (.)self;
StringView fileName = StringView(fileNamePtr);
if (includer._loadedFiles.TryGetValue(fileName, let value))
{
*data = (void*)value.FileContent.Ptr;
*bytes = (uint32)value.FileContent.Length;
return .S_OK;
}
String fullPath = scope .();
Path.Combine(fullPath, includer._parentFileDirectory, fileName);
if (!File.Exists(fullPath))
{
let treeResult = Editor.Instance.ContentManager.AssetHierarchy.GetNodeFromIdentifier(scope AssetIdentifier(fileName));
if (var assetNode = treeResult)
{
fullPath.Set(assetNode->Path);
}
else
{
Log.EngineLogger.Error($"Failed to include shader file \"{fileName}\"");
return .E_FILENOTFOUND;
}
}
Stream fileStream = Application.Instance.ContentManager.GetStream(fullPath);
if (fileStream == null)
{
fileStream = Application.Instance.ContentManager.GetStream(StringView(fileName));
}
if (fileStream == null)
{
return .E_FILENOTFOUND;
}
String fileContent = new String();
{
StreamReader reader = scope .(fileStream);
reader.ReadToEnd(fileContent);
String fileNameStr = new String(fileName);
includer._loadedFiles.Add(fileNameStr, (fileNameStr, fileContent));
}
delete fileStream;
String trashbin = scope String();
if (ShaderCodePreprocessor.ProcessFileContent(fileContent, trashbin, trashbin, includer._context.Variables, includer._context.BufferNames, includer._context.EngineBuffers) case .Err)
{
return .E_FAIL;
}
*data = (void*)fileContent.Ptr;
*bytes = (uint32)fileContent.Length;
return .S_OK;
}
public static HResult Close(ID3DInclude* self, void** data)
{
PreprocessingIncluder* includer = (.)self;
for (var v in includer._loadedFiles)
{
if (v.value.FileContent.Ptr == data)
{
includer._loadedFiles.Remove(v.key);
delete v.value.FileContent;
delete v.value.FileName;
break;
}
}
return .S_OK;
}
}
@@ -0,0 +1,296 @@
using GlitchyEditor.Assets.Importers;
using System;
using GlitchyEngine.Content;
using System.Collections;
using GlitchyEngine;
using GlitchyEngine.Math;
using GlitchyEngine.Renderer;
namespace GlitchyEditor.Assets.Processors;
class ProcessedShader : ProcessedResource
{
public struct TextureEntry
{
public StringView Name;
public TextureDimension TextureDimension;
public int32 VertexShaderBindPoint;
public int32 PixelShaderBindPoint;
public static readonly TextureEntry Default = .() {
Name = null,
TextureDimension = .Unknown,
VertexShaderBindPoint = -1,
PixelShaderBindPoint = -1
};
}
public struct ConstantBufferEntry
{
public ReflectedConstantBuffer ConstantBuffer;
public int32 VertexShaderBindPoint;
public int32 PixelShaderBindPoint;
public static readonly Self Default = .() {
ConstantBuffer = null,
VertexShaderBindPoint = -1,
PixelShaderBindPoint = -1,
};
}
public override AssetType AssetType => .Shader;
public CompiledShader VertexShader ~ delete _;
public CompiledShader PixelShader ~ delete _;
Dictionary<StringView, ConstantBufferEntry> _constantBuffers = new .() ~ delete _; // Only delete container, Buffers come from shaders
Dictionary<StringView, TextureEntry> _textures = new .() ~ delete _;
public Dictionary<StringView, ConstantBufferEntry> ConstantBuffers => _constantBuffers;
public Dictionary<StringView, TextureEntry> Textures => _textures;
public this(AssetIdentifier ownAssetIdentifier, AssetHandle assetHandle) : base(ownAssetIdentifier, assetHandle)
{
}
public void AddConstantBuffer(ConstantBufferEntry buffer)
{
_constantBuffers.Add(buffer.ConstantBuffer.Name, buffer);
}
public void AddTextureEntry(TextureEntry textureEntry)
{
_textures.Add(textureEntry.Name, textureEntry);
}
}
class ShaderVariable
{
private String _name ~ delete _;
private String _previewName ~ delete _;
private String _editorType ~ delete _;
private Dictionary<String, Variant> _parameters = new .() ~ {
for (var (entryKey, entry) in _)
{
delete entryKey;
entry.Dispose();
}
delete _;
};
public StringView Name
{
get => _name;
set => String.NewOrSet!(_name, value);
}
public StringView PreviewName
{
get => _previewName;
set => String.NewOrSet!(_previewName, value);
}
public StringView EditorType
{
get => _editorType;
set => String.NewOrSet!(_editorType, value);
}
public Variant MinValue ~ _.Dispose();
public Variant MaxValue ~ _.Dispose();
public Dictionary<String, Variant> Parameters => _parameters;
public void AddParameter(StringView name, Variant value)
{
_parameters.Add(new String(name), value);
}
}
class ShaderProcessor : IAssetProcessor
{
public AssetProcessorConfig CreateDefaultConfig()
{
return new AssetProcessorConfig();
}
public static Type ProcessedAssetType => typeof(ImportedShader);
public Result<void> Process(ImportedResource importedResource, AssetConfig config, List<ProcessedResource> outProcessedResources)
{
Log.EngineLogger.AssertDebug(importedResource is ImportedShader);
Try!(ProcessShader(importedResource as ImportedShader, config, outProcessedResources));
return default;
}
private static Result<void> ProcessShader(ImportedShader importedShader, AssetConfig config, List<ProcessedResource> outProcessedResources)
{
String vsName = scope String();
String psName = scope String();
Dictionary<StringView, ShaderVariable> variables = scope .();
List<String> bufferNames = scope .();
// Name in Shader -> Name in Engine
Dictionary<StringView, StringView> engineBuffers = scope .();
ProcessedShader processedShader = null;
defer
{
ClearDictionaryAndDeleteValues!(variables);
ClearAndDeleteItems!(bufferNames);
if (@return case .Err)
{
delete processedShader;
}
}
String tmpCode = new String(importedShader.HlslCode);
// TODO: This is terrible. Currently we process too often... (here + each shader stage)
Try!(ShaderCodePreprocessor.ProcessFileContent(tmpCode , vsName, psName, variables, bufferNames, engineBuffers));
delete tmpCode;
if (String.IsNullOrWhiteSpace(vsName) && String.IsNullOrWhiteSpace(psName))
{
// this is not an effect -> we don't need to compile it
return .Ok;
}
processedShader = new ProcessedShader(new AssetIdentifier(importedShader.AssetIdentifier), config.AssetHandle);
Try!(CompileAndReflect(vsName, psName, importedShader, importedShader.HlslCode, processedShader));
Try!(MergeResources(processedShader));
outProcessedResources.Add(processedShader);
return .Ok;
}
private static Result<void> MergeResources(ProcessedShader processedShader)
{
Try!(MergeConstantBuffers(processedShader));
Try!(MergeTextures(processedShader));
return .Ok;
}
private static Result<void> MergeConstantBuffers(ProcessedShader processedShader)
{
HashSet<StringView> bufferNames = scope .();
void AddBufferNames(CompiledShader shader)
{
for (StringView name in shader.ConstantBuffers.Keys)
{
bufferNames.Add(name);
}
}
AddBufferNames(processedShader.VertexShader);
AddBufferNames(processedShader.PixelShader);
for (StringView bufferName in bufferNames)
{
Result<ReflectedConstantBuffer> vsBufferResult = processedShader.VertexShader.ConstantBuffers.GetValue(bufferName);
Result<ReflectedConstantBuffer> psBufferResult = processedShader.PixelShader.ConstantBuffers.GetValue(bufferName);
ProcessedShader.ConstantBufferEntry constantBufferEntry = .Default;
if (vsBufferResult case .Ok(let vsBuffer) && psBufferResult case .Ok(let psBuffer))
{
// Choose larger buffer
constantBufferEntry.ConstantBuffer = (vsBuffer.Size >= psBuffer.Size) ? vsBuffer : psBuffer;
constantBufferEntry.VertexShaderBindPoint = (.)vsBuffer.BindPoint;
constantBufferEntry.PixelShaderBindPoint = (.)psBuffer.BindPoint;
}
else if (vsBufferResult case .Ok(let vsBuffer))
{
constantBufferEntry.ConstantBuffer = vsBuffer;
constantBufferEntry.VertexShaderBindPoint = (.)vsBuffer.BindPoint;
}
else if (psBufferResult case .Ok(let psBuffer))
{
constantBufferEntry.ConstantBuffer = psBuffer;
constantBufferEntry.PixelShaderBindPoint = (.)psBuffer.BindPoint;
}
Log.EngineLogger.Assert(constantBufferEntry.ConstantBuffer != null);
Log.EngineLogger.Assert(constantBufferEntry.VertexShaderBindPoint > -1 || constantBufferEntry.PixelShaderBindPoint > -1);
processedShader.AddConstantBuffer(constantBufferEntry);
}
return .Ok;
}
static ref ProcessedShader.TextureEntry AddOrGetTextureEntry(ProcessedShader processedShader, ReflectedTexture reflectedTexture)
{
if (!processedShader.Textures.ContainsKey(reflectedTexture.Name))
{
ProcessedShader.TextureEntry textureEntry = .Default;
textureEntry.Name = reflectedTexture.Name;
textureEntry.TextureDimension = reflectedTexture.TextureDimension;
processedShader.Textures.Add(textureEntry.Name, textureEntry);
}
ref ProcessedShader.TextureEntry entry = ref processedShader.Textures[reflectedTexture.Name];
return ref entry;
}
private static Result<void> MergeTextures(ProcessedShader processedShader)
{
for (let (textureName, reflectedTexture) in processedShader.VertexShader.Textures)
{
ref ProcessedShader.TextureEntry textureEntry = ref AddOrGetTextureEntry(processedShader, reflectedTexture);
textureEntry.VertexShaderBindPoint = (int32)reflectedTexture.BindPoint;
}
for (let (textureName, reflectedTexture) in processedShader.PixelShader.Textures)
{
ref ProcessedShader.TextureEntry textureEntry = ref AddOrGetTextureEntry(processedShader, reflectedTexture);
textureEntry.PixelShaderBindPoint = (int32)reflectedTexture.BindPoint;
}
return .Ok;
}
private static Result<void> CompileAndReflect(StringView vsName, StringView psName, ImportedShader shader, StringView code, ProcessedShader processedShader)
{
if (!vsName.IsWhiteSpace)
{
processedShader.VertexShader = Try!(CompileAndReflectVertexShader(vsName, shader, code));
}
if (!psName.IsWhiteSpace)
{
processedShader.PixelShader = Try!(CompileAndReflectPixelShader(psName, shader, code));
}
return .Ok;
}
private static Result<CompiledShader> CompileAndReflectVertexShader(StringView vsName, ImportedShader importedShader, StringView code)
{
Debug.Profiler.ProfileResourceFunction!();
return ShaderCompiler.CompileAndReflectShader(code, importedShader.AssetIdentifier, vsName, "vs_5_0", .());
}
private static Result<CompiledShader> CompileAndReflectPixelShader(StringView vsName, ImportedShader importedShader, StringView code)
{
Debug.Profiler.ProfileResourceFunction!();
return ShaderCompiler.CompileAndReflectShader(code, importedShader.AssetIdentifier, vsName, "ps_5_0", .());
}
}
@@ -0,0 +1,127 @@
#if BF_PLATFORM_WINDOWS
using System;
using System.Diagnostics;
using System.Threading;
using DirectX.Common;
using GlitchyEngine;
using GlitchyEditor.Platform.Windows.Com;
namespace GlitchyEditor.CodeEditors;
/// A request to open the script solution (or a file in it) in Visual Studio.
/// Runs on its own worker thread so the editor never blocks on a busy or starting Visual Studio.
class DteOpenRequest
{
private const int ColdStartMaxPolls = 120;
private const int ColdStartPollIntervalMs = 500;
private append String _solutionPath = .();
private append String _devenvPath = .();
private append String _fileName = .();
public StringView SolutionPath
{
get => _solutionPath;
set => _solutionPath.Set(value);
}
public StringView DevenvPath
{
get => _devenvPath;
set => _devenvPath.Set(value);
}
/// Leave empty to just open the project.
public StringView FileName
{
get => _fileName;
set => _fileName.Set(value);
}
public int LineNumber;
private static int32 sWorkerActive = 0;
public static void Start(DteOpenRequest ownRequest)
{
if (Interlocked.CompareExchange(ref sWorkerActive, 0, 1) != 0)
{
Log.EngineLogger.Info("Visual Studio is already being opened, ignoring the request.");
delete ownRequest;
return;
}
Thread workerThread = new Thread(new () =>
{
ownRequest.Run();
delete ownRequest;
Interlocked.Exchange(ref sWorkerActive, 0);
});
workerThread.SetName("Visual Studio DTE worker");
// Don't block editor shutdown on a worker that is still polling; skipping the COM cleanup
// at process exit is harmless.
workerThread.IsBackground = true;
workerThread.Start(true);
}
private void Run()
{
// The worker does its own COM initialization, the editor main thread (STA via OleInitialize)
// stays untouched.
if (CoInitializeEx(null, COINIT_APARTMENTTHREADED).Failed(let result))
{
Log.EngineLogger.Error(scope $"CoInitializeEx failed: {result} ({(uint32)result:X8})");
return;
}
defer CoUninitialize();
if (VisualStudioDte.FindRunningInstance(SolutionPath) case .Ok(let dte))
{
if (FileName.IsEmpty)
VisualStudioDte.ActivateMainWindow(dte);
else
VisualStudioDte.OpenFileAtLine(dte, FileName, LineNumber).IgnoreError();
delete dte;
return;
}
Log.EngineLogger.Info(scope $"No running Visual Studio instance has \"{SolutionPath}\" open, starting a new one.");
ProcessStartInfo startInfo = scope .();
startInfo.SetFileName(DevenvPath);
startInfo.SetArguments(scope $"\"{SolutionPath}\"");
if (scope SpawnedProcess().Start(startInfo) case .Err)
{
Log.EngineLogger.Error(scope $"Failed to start Visual Studio (\"{DevenvPath}\").");
return;
}
if (FileName.IsEmpty)
return;
// Wait for the new instance to finish loading the solution (Solution.FullName stays empty
// until then), then open the file in it.
for (int i < ColdStartMaxPolls)
{
Thread.Sleep(ColdStartPollIntervalMs);
if (VisualStudioDte.FindRunningInstance(SolutionPath) case .Ok(let startedDte))
{
VisualStudioDte.OpenFileAtLine(startedDte, FileName, LineNumber).IgnoreError();
delete startedDte;
return;
}
}
Log.EngineLogger.Warning(scope $"Visual Studio was started but its automation interface never became available, \"{FileName}\" was not opened automatically.");
}
}
#endif
@@ -0,0 +1,236 @@
#if BF_PLATFORM_WINDOWS
using System;
using System.Threading;
using DirectX.Common;
using GlitchyEngine;
using GlitchyEditor.Platform.Windows.Com;
using System.IO;
namespace GlitchyEditor.CodeEditors;
/// Talks to running Visual Studio instances via their EnvDTE automation objects, which are
/// published in the Running Object Table as "!VisualStudio.DTE.<version>:<pid>".
/// All methods must be called from a thread that is initialized for COM (CoInitializeEx).
static class VisualStudioDte
{
private const String MonikerPrefix = "!VisualStudio.DTE";
/// EnvDTE.Constants.vsViewKindCode
private const String ViewKindCode = "{7651A701-06E5-11D1-8EBD-00A0C90F26EA}";
/// Searches the Running Object Table for a Visual Studio instance that has the given solution open.
public static Result<ComDispatch> FindRunningInstance(StringView solutionPath)
{
IRunningObjectTable* rot = null;
if (GetRunningObjectTable(0, out rot).Failed(var result) || rot == null)
{
Log.EngineLogger.Error(scope $"GetRunningObjectTable failed: {result} ({(uint32)result:X8})");
return .Err;
}
defer rot.Release();
IBindCtx* bindCtx = null;
if (CreateBindCtx(0, out bindCtx).Failed(out result) || bindCtx == null)
{
Log.EngineLogger.Error(scope $"CreateBindCtx failed: {result} ({(uint32)result:X8})");
return .Err;
}
defer bindCtx.Release();
IEnumMoniker* enumMoniker = null;
if (rot.EnumRunning(out enumMoniker).Failed(out result) || enumMoniker == null)
{
Log.EngineLogger.Error(scope $"IRunningObjectTable.EnumRunning failed: {result} ({(uint32)result:X8})");
return .Err;
}
defer enumMoniker.Release();
IMoniker* moniker = null;
while (enumMoniker.Next(1, out moniker, ?) == .S_OK)
{
ComDispatch dte = GetDteFromMoniker(rot, bindCtx, moniker);
ReleaseAndNullify!(moniker);
if (dte == null)
continue;
String solutionFullName = scope .();
if (TryGetSolutionFullName(dte, solutionFullName))
{
Log.EngineLogger.Trace(scope $"Found running Visual Studio instance with solution \"{solutionFullName}\".");
if (SolutionPathsEqual(solutionFullName, solutionPath))
return dte;
}
// No match, cleanup.
delete dte;
}
return .Err;
}
/// Brings the main window of the given Visual Studio instance to the foreground.
public static void ActivateMainWindow(ComDispatch dte)
{
if (!(dte.GetObjectProperty("MainWindow") case .Ok(let mainWindow)))
{
Log.EngineLogger.Warning("Failed to get the Visual Studio main window.");
return;
}
defer delete mainWindow;
if (mainWindow.InvokeMethod("Activate") case .Ok(var activateResult))
VariantClear(&activateResult);
// Activate alone often isn't enough to raise a window of another process,
// so additionally restore and foreground it via its HWND.
if (mainWindow.GetProperty("HWnd") case .Ok(var hwndValue))
{
if (hwndValue.GetHwnd() case .Ok(let hwnd))
{
if (IsIconic(hwnd))
ShowWindow(hwnd, SW_RESTORE);
SetForegroundWindow(hwnd);
}
VariantClear(&hwndValue);
}
}
/// Opens the given file in the code editor of the given Visual Studio instance and
/// jumps to lineNumber (1-based, ignored if <= 0).
public static Result<void> OpenFileAtLine(ComDispatch dte, StringView fileName, int lineNumber)
{
ComDispatch itemOperations = Try!(dte.GetObjectProperty("ItemOperations"));
defer delete itemOperations;
VARIANT[] openFileArgs = scope .(VARIANT.FromBStr(fileName), VARIANT.FromBStr(ViewKindCode));
if (itemOperations.InvokeMethod("OpenFile", openFileArgs) case .Ok(var window))
{
VariantClear(&window);
}
else
{
Log.EngineLogger.Error(scope $"Failed to open \"{fileName}\" in Visual Studio.");
return .Err;
}
if (lineNumber > 0)
GoToLine(dte, lineNumber);
ActivateMainWindow(dte);
return .Ok;
}
private static ComDispatch GetDteFromMoniker(IRunningObjectTable* rot, IBindCtx* bindCtx, IMoniker* moniker)
{
char16* displayNameW = null;
if (moniker.GetDisplayName(bindCtx, null, out displayNameW).Failed || displayNameW == null)
return null;
String displayName = scope .();
displayName.Append(displayNameW);
CoTaskMemFree(displayNameW);
if (!displayName.StartsWith(MonikerPrefix))
return null;
IUnknown* unknown = null;
defer { unknown?.Release(); }
if (rot.GetObject(moniker, out unknown).Failed || unknown == null)
return null;
IDispatch* dispatch = null;
if (unknown.QueryInterface<IDispatch>(out dispatch).Succeeded)
return new ComDispatch(dispatch);
return null;
}
private static bool TryGetSolutionFullName(ComDispatch dte, String outFullName)
{
if (!(dte.GetObjectProperty("Solution") case .Ok(let solution)))
return false;
defer delete solution;
return solution.GetStringProperty("FullName", outFullName) case .Ok;
}
private static bool SolutionPathsEqual(StringView left, StringView right)
{
String normalizedLeft = NormalizePath(left, .. scope .());
String normalizedRight = NormalizePath(right, .. scope .());
// Be tolerant about the solution extension: it isn't guaranteed that the DTE reports
// the same extension for .slnx solutions that we expect.
return StripSolutionExtension(normalizedLeft) == StripSolutionExtension(normalizedRight);
}
private static void NormalizePath(StringView path, String output)
{
output.Append(path);
output.Replace('/', '\\');
output.ToLower();
}
private static StringView StripSolutionExtension(StringView path)
{
if (path.EndsWith(".slnx"))
return path.Substring(0, path.Length - ".slnx".Length);
if (path.EndsWith(".sln"))
return path.Substring(0, path.Length - ".sln".Length);
return path;
}
private static void GoToLine(ComDispatch dte, int lineNumber)
{
ComDispatch document = null;
defer { delete document; }
// Directly after OpenFile the ActiveDocument might not be set yet, so retry for a bit.
for (int i < 10)
{
if (dte.GetObjectProperty("ActiveDocument") case .Ok(out document))
break;
Thread.Sleep(100);
}
if (document == null)
{
Log.EngineLogger.Warning("Could not jump to the requested line: no active document.");
return;
}
if (document.GetObjectProperty("Selection") not case .Ok(let selection))
{
Log.EngineLogger.Warning("Could not jump to the requested line: failed to get the text selection.");
return;
}
VARIANT[2] gotoLineArgs = .(VARIANT.FromInt32((int32)lineNumber), VARIANT.FromBool(false));
if (selection.InvokeMethod("GotoLine", gotoLineArgs) case .Ok(var gotoResult))
VariantClear(&gotoResult);
delete selection;
}
}
#endif
+2 -4
View File
@@ -3,9 +3,7 @@ namespace GlitchyEditor.CodeEditors;
interface IIdeAdapter
{
static void OpenScript(StringView fileName);
void OpenScript(StringView fileName, int lineNumber = 0);
static void OpenScript(StringView fileName, int lineNumber);
static void OpenScriptProject();
void OpenScriptProject();
}
@@ -2,37 +2,64 @@ using System;
using System.Diagnostics;
using GlitchyEngine;
using System.Collections;
using System.IO;
using System.IO;
using GlitchyEditor.EditWindows;
using ImGui;
using GlitchyEditor.Settings;
namespace GlitchyEditor.CodeEditors;
class RiderIdeAdapter : IIdeAdapter
{
public static void OpenScript(StringView fileName)
private IdeInstallation _ideInstallation;
public this(IdeInstallation ideInstallation)
{
OpenScript(fileName, 0);
Debug.Assert(ideInstallation.Ide == .Rider);
_ideInstallation = ideInstallation;
}
public static void OpenScript(StringView fileName, int lineNumber)
public void OpenScript(StringView fileName, int lineNumber)
{
String solutionPath = scope .();
Editor.Instance.CurrentProject.PathInProject(solutionPath, scope $"{Editor.Instance.CurrentProject.Name}.sln");
Editor.Instance.CurrentProject.GetPathToScriptSolutionFile(solutionPath);
ProcessStartInfo startInfo = scope .();
startInfo.SetFileName(Application.Instance.Settings.ScriptSettings.RiderPath);
startInfo.SetFileName(_ideInstallation.Path);
startInfo.SetArguments(scope $"{solutionPath} --line {lineNumber} {fileName}");
scope SpawnedProcess().Start(startInfo);
}
public static void OpenScriptProject()
public void OpenScriptProject()
{
String solutionPath = scope .();
Editor.Instance.CurrentProject.PathInProject(solutionPath, scope $"{Editor.Instance.CurrentProject.Name}.sln");
Editor.Instance.CurrentProject.GetPathToScriptSolutionFile(solutionPath);
if (!File.Exists(solutionPath))
{
Log.EngineLogger.Error("Could not find solution file?");
return;
}
if (!File.Exists(_ideInstallation.Path))
{
Editor.Instance.ShowSettings();
Editor.Instance.SettingsWindow.HighlightSetting("Tools", "Rider path");
PopupService.Instance.ShowMessageBox("Rider not found.",
"The rider path could not be found. Please select the correct path.");
return;
}
ProcessStartInfo startInfo = scope .();
startInfo.SetFileName(Application.Instance.Settings.ScriptSettings.RiderPath);
startInfo.SetFileName(_ideInstallation.Path);
startInfo.SetArguments(solutionPath);
scope SpawnedProcess().Start(startInfo);
}
}
@@ -0,0 +1,214 @@
GlitchyEditor/src/CodeEditors/RiderPathLocator.bf (and the platform specific implementations)
are a Beef port of the Rider path locator from the JetBrains "Rider Source Code Access" Unreal Engine plugin:
https://github.com/JetBrains/RiderSourceCodeAccess
Source/RiderSourceCodeAccess/Private/RiderPathLocator/
The original work and this port are licensed under the Apache License, Version 2.0,
reproduced in full below.
-------------------------------------------------------------------------------
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0.txt
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,445 @@
// Beef port of JetBrains "Rider Source Code Access":
// https://github.com/JetBrains/RiderSourceCodeAccess/tree/main/Source/RiderSourceCodeAccess/Private/RiderPathLocator
//
// Copyright Epic Games, Inc. All Rights Reserved.
// Licensed under the Apache License, Version 2.0 - see LICENSE-RiderPathLocator.txt
//
// Changes from the original: rewritten in Beef against corlib instead of the
// Unreal Engine types (FString/TArray/TOptional/FPaths/FRegexPattern/FJsonSerializer/...).
// The "plugins/rider-cpp" requirement, the ESupportUproject state and the
// RiderLocations.txt resource-file lookup were removed because they only make sense for Unreal projects.
//
// This file holds the platform independent part, the platform specific parts live in the extensions (e.g. RiderPathLocatorWindows.bf).
using System;
using System.Collections;
using System.IO;
using Beefy.utils;
namespace GlitchyEditor.CodeEditors;
/// A dot-separated version number, e.g. "2024.1.2" or the build number "241.15989.155".
struct RiderVersion : IHashable
{
public const int32 InvalidVersion = -1;
private const int32 MaxParts = 4;
private int32[MaxParts] _parts;
private int32 _count;
public bool IsInitialized => _count != 0;
public int32 Major => _count >= 1 ? _parts[0] : InvalidVersion;
public int32 Minor => _count >= 2 ? _parts[1] : InvalidVersion;
public int32 Patch => _count >= 3 ? _parts[2] : InvalidVersion;
public static RiderVersion Parse(StringView versionString)
{
RiderVersion version = .();
for (StringView part in versionString.Split('.'))
{
if (version._count >= MaxParts)
break;
// Note: the original uses FCString::Atoi, which yields 0 for garbage instead of failing.
int32 value = 0;
if (int32.Parse(part) case .Ok(let parsedValue))
value = parsedValue;
version._parts[version._count] = value;
version._count++;
}
return version;
}
/// Compares two versions element by element.
/// @returns A negative value if lhs is older than rhs, a positive value if it is newer; otherwise 0.
public static int Compare(Self lhs, Self rhs)
{
int32 sharedCount = Math.Min(lhs._count, rhs._count);
for (int32 i < sharedCount)
{
if (lhs._parts[i] != rhs._parts[i])
return lhs._parts[i] < rhs._parts[i] ? -1 : 1;
}
// Equal prefix: the shorter version is the older one, so that "5.0" is older than "5.0.1".
return lhs._count <=> rhs._count;
}
public static bool operator<(Self lhs, Self rhs) => Compare(lhs, rhs) < 0;
public static bool operator==(Self lhs, Self rhs) => Compare(lhs, rhs) == 0;
public static bool operator!=(Self lhs, Self rhs) => Compare(lhs, rhs) != 0;
public int GetHashCode()
{
int hash = 0;
for (int32 i < _count)
hash = (hash * 31) + (int)_parts[i];
return hash;
}
public override void ToString(String strBuffer)
{
if (_count == 0)
{
strBuffer.Append("<unknown>");
return;
}
for (int32 i < _count)
{
if (i != 0)
strBuffer.Append('.');
_parts[i].ToString(strBuffer);
}
}
}
/// How a Rider installation was set up.
enum RiderInstallType
{
/// Installed by the regular Rider installer.
Installed,
/// Installed through the JetBrains Toolbox.
Toolbox,
/// Provided by the user.
Custom
}
/// A single Rider installation found on this machine.
class RiderInstallInfo
{
/// The full path of the Rider executable (rider64.exe).
public String Path ~ delete _;
public RiderVersion Version;
public RiderInstallType InstallType;
public String Name ~ delete _;
public this(StringView path, RiderVersion version, RiderInstallType installType, StringView name)
{
Path = new String(path);
Version = version;
InstallType = installType;
Name = new String(name);
}
}
/// Locates the JetBrains Rider installations on this machine.
static class RiderPathLocator
{
/// Maximum directory depth we descend into while looking for the Rider executable.
/// The original search is unbounded, but it searches locations like "%LOCALAPPDATA%/Programs".
protected const int MaxSearchDepth = 8;
/// Maximum number of directories visited by a single recursive search.
protected const int MaxSearchedDirectories = 4096;
/// Creates the installation info for the given Rider executable.
/// @param riderExePath The path of a rider64.exe.
/// @returns The installation info; or .Err if the given path is not a usable Rider installation.
public static extern Result<RiderInstallInfo> GetInstallInfoFromRiderPath(StringView riderExePath, RiderInstallType installType);
/// Collects every Rider installation that can be found on this machine.
/// The entries are deduplicated by path and sorted, newest version first.
/// @param outInstalls Receives the installations. The caller takes ownership of the entries.
public static extern void CollectAllPaths(List<RiderInstallInfo> outInstalls);
/// Gets the file name of the Rider executable on this platform (e.g. "rider64.exe").
protected static extern StringView GetRiderExecutableName();
/// Gets the default location the Toolbox V2 installs IDEs into.
protected static extern Result<void> GetDefaultIdeInstallLocationForToolboxV2(String outPath);
/// Moves the located installations into outInstalls, dropping the ones that were found more
/// than once, and sorts them so that the newest installation comes first.
/// @param found The located installations. The entries are moved into outInstalls or deleted.
/// @param outInstalls Receives the installations. The caller takes ownership of the entries.
protected static void DeduplicateAndSort(List<RiderInstallInfo> found, List<RiderInstallInfo> outInstalls)
{
// The same installation usually shows up through more than one source.
for (int i < found.Count)
{
RiderInstallInfo install = found[i];
bool isDuplicate = false;
for (RiderInstallInfo existing in outInstalls)
{
if (IsSamePath(existing.Path, install.Path))
{
isDuplicate = true;
break;
}
}
if (isDuplicate)
continue;
outInstalls.Add(install);
// Ownership moved to outInstalls, make sure the caller's cleanup doesn't delete it.
found[i] = null;
}
// Newest first, so that the caller can simply take the first entry.
outInstalls.Sort(scope (lhs, rhs) => RiderVersion.Compare(rhs.Version, lhs.Version));
}
/// Gets the installation directory of Rider from the path of its executable.
/// The executable is expected to live in a "bin" directory, i.e. "<riderDirectory>/bin/rider64.exe".
protected static Result<void> GetRiderDirectory(StringView riderExePath, String outDirectory)
{
// The original matches the regex "(.*)(?:\\|/)bin" against the executable path,
// which is the same as taking the grandparent directory of a file inside "bin".
String binDirectory = scope .();
Try!(Path.GetDirectoryPath(riderExePath, binDirectory));
String binDirectoryName = scope .();
Path.GetFileName(binDirectory, binDirectoryName);
if (StringView.Compare(binDirectoryName, "bin", true) != 0)
return .Err;
return Path.GetDirectoryPath(binDirectory, outDirectory);
}
/// Compares two paths, ignoring case and the difference between the directory separators.
protected static bool IsSamePath(StringView left, StringView right)
{
if (left.Length != right.Length)
return false;
for (int i < left.Length)
{
char8 leftChar = left[i];
char8 rightChar = right[i];
if (Path.IsDirectorySeparatorChar(leftChar) && Path.IsDirectorySeparatorChar(rightChar))
continue;
if (leftChar.ToLower != rightChar.ToLower)
return false;
}
return true;
}
/// Collects the Rider installations managed by the JetBrains Toolbox at the given location.
protected static void CollectPathsFromToolbox(StringView toolboxPath, List<RiderInstallInfo> outInstalls)
{
if (toolboxPath.IsEmpty || !Directory.Exists(toolboxPath))
return;
String customInstallLocation = scope .();
ExtractPathFromSettingsJson(toolboxPath, customInstallLocation);
if (!customInstallLocation.IsEmpty)
{
// Toolbox V1 puts the IDEs into an "apps" subdirectory of the install location.
String appsPath = scope .();
Path.Combine(appsPath, customInstallLocation, "apps");
int countBefore = outInstalls.Count;
CollectPathsFromToolboxDirectory(appsPath, outInstalls);
if (outInstalls.Count != countBefore)
return;
// Toolbox V2 puts them directly into the install location.
CollectPathsFromToolboxDirectory(customInstallLocation, outInstalls);
return;
}
// Toolbox V1 default install location.
String defaultAppsPath = scope .();
Path.Combine(defaultAppsPath, toolboxPath, "apps");
int countBefore = outInstalls.Count;
CollectPathsFromToolboxDirectory(defaultAppsPath, outInstalls);
if (outInstalls.Count != countBefore)
return;
// Toolbox V2 default install location.
String defaultInstallLocation = scope .();
if (GetDefaultIdeInstallLocationForToolboxV2(defaultInstallLocation) case .Ok)
CollectPathsFromToolboxDirectory(defaultInstallLocation, outInstalls);
}
/// Searches the given directory for Rider executables and creates the installation infos for them.
protected static void CollectPathsFromToolboxDirectory(StringView toolboxRiderRootPath, List<RiderInstallInfo> outInstalls)
{
if (toolboxRiderRootPath.IsEmpty || !Directory.Exists(toolboxRiderRootPath))
return;
List<String> riderPaths = scope .();
defer { ClearAndDeleteItems!(riderPaths); }
int searchedDirectories = 0;
FindFilesRecursive(toolboxRiderRootPath, GetRiderExecutableName(), riderPaths, MaxSearchDepth, ref searchedDirectories);
// Reused across the iterations, see the note in FindFilesRecursive.
String historyJsonPath = scope .();
for (String riderPath in riderPaths)
{
if (!(GetInstallInfoFromRiderPath(riderPath, .Toolbox) case .Ok(let installInfo)))
continue;
// The Toolbox leaves the directories of old versions behind after an update, so we
// check which build it actually considers the current one for this installation.
historyJsonPath.Clear();
if (GetHistoryJsonPath(riderPath, historyJsonPath) case .Ok)
{
RiderVersion lastBuildVersion = GetLastBuildVersion(historyJsonPath);
if (lastBuildVersion.IsInitialized && installInfo.Version != lastBuildVersion)
{
delete installInfo;
continue;
}
}
outInstalls.Add(installInfo);
}
}
/// Recursively searches the given directory for files with the given name.
/// @remarks corlib's Directory.EnumerateFiles doesn't search subdirectories.
protected static void FindFilesRecursive(StringView directory, StringView fileName, List<String> outPaths,
int remainingDepth, ref int searchedDirectories)
{
if (remainingDepth <= 0 || searchedDirectories >= MaxSearchedDirectories)
return;
searchedDirectories++;
for (FileFindEntry entry in Directory.EnumerateFiles(directory, fileName))
{
String path = new String();
entry.GetFilePath(path);
outPaths.Add(path);
}
// Reused across the iterations: a "scope" allocation inside a loop body lives until the
// enclosing method returns, so allocating per iteration would grow the stack unboundedly.
String entryName = scope .();
String subDirectory = scope .();
for (FileFindEntry entry in Directory.EnumerateDirectories(directory))
{
entryName.Clear();
entry.GetFileName(entryName);
if (entryName == "." || entryName == "..")
continue;
subDirectory.Clear();
entry.GetFilePath(subDirectory);
FindFilesRecursive(subDirectory, fileName, outPaths, remainingDepth - 1, ref searchedDirectories);
}
}
/// Reads the custom install location out of the Toolbox' ".settings.json".
protected static void ExtractPathFromSettingsJson(StringView toolboxPath, String outInstallLocation)
{
String settingsJsonPath = scope .();
Path.Combine(settingsJsonPath, toolboxPath, ".settings.json");
if (!File.Exists(settingsJsonPath))
return;
StructuredData data = scope .();
if (data.Load(settingsJsonPath) case .Err)
return;
data.GetString("install_location", outInstallLocation);
}
/// Reads the version of a Rider installation out of its "product-info.json".
protected static RiderVersion ParseProductInfoJson(StringView productInfoJsonPath, String outName, String outVersion)
{
StructuredData data = scope .();
if (data.Load(productInfoJsonPath) case .Err)
return .();
String buildNumber = scope .();
data.GetString("buildNumber", buildNumber);
data.GetString("name", outName);
data.GetString("version", outVersion);
return RiderVersion.Parse(buildNumber);
}
/// Walks up from the given Rider executable looking for the Toolbox' ".history.json".
protected static Result<void> GetHistoryJsonPath(StringView riderPath, String outPath)
{
String directory = scope .();
Try!(Path.GetDirectoryPath(riderPath, directory));
String parentDirectory = scope .();
String historyPath = scope .();
// Bounded so that we don't walk all the way up to the drive root.
for (int i < 10)
{
if (!Directory.Exists(directory))
break;
historyPath.Clear();
Path.Combine(historyPath, directory, ".history.json");
if (File.Exists(historyPath))
{
outPath.Append(historyPath);
return .Ok;
}
parentDirectory.Clear();
if (Path.GetDirectoryPath(directory, parentDirectory) case .Err)
break;
if (parentDirectory.IsEmpty || parentDirectory == directory)
break;
directory.Set(parentDirectory);
}
return .Err;
}
/// Reads the build number of the most recent entry of the Toolbox' ".history.json".
protected static RiderVersion GetLastBuildVersion(StringView historyJsonPath)
{
StructuredData data = scope .();
if (data.Load(historyJsonPath) case .Err)
return .();
String lastBuild = scope .();
// The most recent build is the last entry of the "history" array.
for (data.Enumerate("history"))
{
lastBuild.Clear();
using (data.Open("item"))
{
data.GetString("build", lastBuild);
}
}
return RiderVersion.Parse(lastBuild);
}
}
@@ -0,0 +1,243 @@
// Windows implementation of the RiderPathLocator, see RiderPathLocator.bf for the platform independent part and the license of the original code.
#if BF_PLATFORM_WINDOWS
using System;
using System.Collections;
using System.IO;
using System.Text;
namespace GlitchyEditor.CodeEditors;
extension RiderPathLocator
{
private const String RiderExecutableName = "rider64.exe";
/// The maximum length of a registry key name.
private const int MaxRegistryKeyNameLength = 255;
[Import("advapi32.lib"), CLink, CallingConvention(.Stdcall)]
private static extern int32 RegEnumKeyExW(Windows.HKey hKey, uint32 dwIndex, char16* lpName,
uint32* lpcchName, uint32* lpReserved, char16* lpClass, uint32* lpcchClass,
void* lpftLastWriteTime);
protected static override StringView GetRiderExecutableName() => RiderExecutableName;
public static override Result<RiderInstallInfo> GetInstallInfoFromRiderPath(StringView riderExePath, RiderInstallType installType)
{
if (!File.Exists(riderExePath))
return .Err;
String riderDirectory = scope .();
Try!(GetRiderDirectory(riderExePath, riderDirectory));
RiderVersion version = .();
String installName = scope .();
String installVersion = scope .();
String productInfoPath = scope .();
Path.Combine(productInfoPath, riderDirectory, "product-info.json");
if (File.Exists(productInfoPath))
version = ParseProductInfoJson(productInfoPath, installName, installVersion);
else
{
installName.Set("Rider");
version.ToString(installVersion..Clear());
}
if (!version.IsInitialized)
{
// Toolbox installations are laid out as "<...>/<build number>/bin/rider64.exe",
// so the name of the installation directory is the version.
String directoryName = scope .();
Path.GetFileName(riderDirectory, directoryName);
version = RiderVersion.Parse(directoryName);
}
return new RiderInstallInfo(riderExePath, version, installType, scope $"{installName} {installVersion}");
}
public static override void CollectAllPaths(List<RiderInstallInfo> outInstalls)
{
const String UninstallKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
const String UninstallKey32 = @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
const String RiderKey = @"SOFTWARE\JetBrains\Rider";
const String RiderKey32 = @"SOFTWARE\WOW6432Node\JetBrains\Rider";
List<RiderInstallInfo> found = scope .();
// Everything that isn't moved into outInstalls (i.e. the duplicates) is deleted here.
defer { ClearAndDeleteItems!(found); }
// Regular installations register themselves in the uninstall list.
CollectPathsFromRegistry(Windows.HKEY_CURRENT_USER, UninstallKey, "InstallLocation", "Rider", found);
CollectPathsFromRegistry(Windows.HKEY_LOCAL_MACHINE, UninstallKey, "InstallLocation", "Rider", found);
CollectPathsFromRegistry(Windows.HKEY_CURRENT_USER, UninstallKey32, "InstallLocation", "Rider", found);
CollectPathsFromRegistry(Windows.HKEY_LOCAL_MACHINE, UninstallKey32, "InstallLocation", "Rider", found);
// dotUltimate installations use their own key.
CollectPathsFromRegistry(Windows.HKEY_CURRENT_USER, RiderKey, "InstallDir", "", found);
CollectPathsFromRegistry(Windows.HKEY_LOCAL_MACHINE, RiderKey, "InstallDir", "", found);
CollectPathsFromRegistry(Windows.HKEY_CURRENT_USER, RiderKey32, "InstallDir", "", found);
CollectPathsFromRegistry(Windows.HKEY_LOCAL_MACHINE, RiderKey32, "InstallDir", "", found);
// Toolbox installations aren't registered anywhere, we have to go looking for them.
String defaultToolboxPath = scope .();
if (GetToolboxPathFromEnvironment(defaultToolboxPath) case .Ok)
CollectPathsFromToolbox(defaultToolboxPath, found);
CollectPathsFromToolboxRegistry(Windows.HKEY_CURRENT_USER, @"Software\JetBrains\Toolbox\", found);
CollectPathsFromToolboxRegistry(Windows.HKEY_LOCAL_MACHINE, @"Software\JetBrains\Toolbox\", found);
CollectPathsFromToolboxRegistry(Windows.HKEY_CURRENT_USER, @"Software\JetBrains s.r.o.\JetBrainsToolbox\", found);
CollectPathsFromToolboxRegistry(Windows.HKEY_LOCAL_MACHINE, @"Software\JetBrains s.r.o.\JetBrainsToolbox\", found);
DeduplicateAndSort(found, outInstalls);
}
/// Enumerates the subkeys of the given registry key and creates an installation info for
/// every subkey that points at a Rider installation.
/// @param rootKey The registry hive to look in.
/// @param subKeyPath The key whose subkeys will be enumerated.
/// @param valueName The name of the value that holds the installation directory.
/// @param keyNameFilter If not empty, only subkeys whose name contains this string are looked at.
private static void CollectPathsFromRegistry(Windows.HKey rootKey, StringView subKeyPath,
StringView valueName, StringView keyNameFilter, List<RiderInstallInfo> outInstalls)
{
Windows.HKey key;
if (Windows.RegOpenKeyExW(rootKey, subKeyPath.ToScopedNativeWChar!(), 0, (.)Windows.KEY_READ, out key) != Windows.ERROR_SUCCESS)
return;
defer Windows.RegCloseKey(key);
List<String> subKeyNames = scope .();
defer { ClearAndDeleteItems!(subKeyNames); }
if (EnumerateSubKeys(key, subKeyNames) case .Err)
return;
// All of these are reused across the iterations: a "scope" allocation inside a loop body
// lives until the enclosing method returns, and the uninstall key has hundreds of subkeys.
char16[MaxRegistryKeyNameLength + 1] subKeyNameBuffer = ?;
String installLocation = scope .();
String exePath = scope .();
for (String subKeyName in subKeyNames)
{
if (!keyNameFilter.IsEmpty && !subKeyName.Contains(keyNameFilter))
continue;
int encodedLength = UTF16.GetEncodedLen(subKeyName);
if (encodedLength > MaxRegistryKeyNameLength)
continue;
UTF16.Encode(subKeyName, &subKeyNameBuffer, encodedLength).IgnoreError();
subKeyNameBuffer[encodedLength] = 0;
Windows.HKey subKey;
if (Windows.RegOpenKeyExW(key, &subKeyNameBuffer, 0, (.)Windows.KEY_READ, out subKey) != Windows.ERROR_SUCCESS)
continue;
installLocation.Clear();
Result<void> valueResult = subKey.GetValue(valueName, installLocation);
Windows.RegCloseKey(subKey);
if (valueResult case .Err)
continue;
if (installLocation.IsWhiteSpace)
continue;
exePath.Clear();
Path.Combine(exePath, installLocation, "bin", RiderExecutableName);
if (GetInstallInfoFromRiderPath(exePath, .Installed) case .Ok(let installInfo))
outInstalls.Add(installInfo);
}
}
/// Enumerates the names of all subkeys of the given registry key.
/// @remarks Windows.HKey.EnumValue enumerates values (and is unimplemented), there is no
/// counterpart for subkeys in corlib.
private static Result<void> EnumerateSubKeys(Windows.HKey key, List<String> outNames)
{
// Reused across the iterations, see the note in CollectPathsFromRegistry.
char16[MaxRegistryKeyNameLength + 1] nameBuffer = ?;
for (uint32 index = 0; true; index++)
{
uint32 nameLength = nameBuffer.Count;
int32 result = RegEnumKeyExW(key, index, &nameBuffer, &nameLength, null, null, null, null);
if (result == Windows.ERROR_NO_MORE_ITEMS)
break;
if (result != Windows.ERROR_SUCCESS)
return .Err;
// RegEnumKeyExW null-terminates, but be explicit about it since we reuse the buffer.
nameBuffer[Math.Min((int)nameLength, MaxRegistryKeyNameLength)] = 0;
outNames.Add(new String(&nameBuffer));
}
return .Ok;
}
/// Gets the default location of the JetBrains Toolbox ("%LOCALAPPDATA%/JetBrains/Toolbox").
private static Result<void> GetToolboxPathFromEnvironment(String outPath)
{
String localAppData = scope .();
Try!(Environment.GetEnvironmentVariable("LOCALAPPDATA", localAppData));
Path.Combine(outPath, localAppData, "JetBrains", "Toolbox");
return .Ok;
}
protected static override Result<void> GetDefaultIdeInstallLocationForToolboxV2(String outPath)
{
String localAppData = scope .();
Try!(Environment.GetEnvironmentVariable("LOCALAPPDATA", localAppData));
Path.Combine(outPath, localAppData, "Programs");
return .Ok;
}
/// Looks up the location of the JetBrains Toolbox in the registry and collects the Rider
/// installations it manages.
private static void CollectPathsFromToolboxRegistry(Windows.HKey rootKey, StringView subKeyPath, List<RiderInstallInfo> outInstalls)
{
Windows.HKey key;
if (Windows.RegOpenKeyExW(rootKey, subKeyPath.ToScopedNativeWChar!(), 0, (.)Windows.KEY_READ, out key) != Windows.ERROR_SUCCESS)
return;
defer Windows.RegCloseKey(key);
// The default value of the key contains the path of the Toolbox' "bin" directory.
String toolboxBinPath = scope .();
if (key.GetValue("", toolboxBinPath) case .Err)
return;
if (toolboxBinPath.IsWhiteSpace)
return;
String binDirectoryName = scope .();
Path.GetFileName(toolboxBinPath, binDirectoryName);
if (StringView.Compare(binDirectoryName, "bin", true) != 0)
return;
String toolboxPath = scope .();
if (Path.GetDirectoryPath(toolboxBinPath, toolboxPath) case .Err)
return;
CollectPathsFromToolbox(toolboxPath, outInstalls);
}
}
#endif
@@ -1,65 +1,70 @@
using System;
using System.Diagnostics;
using System.IO;
using GlitchyEngine;
using System.Collections;
using GlitchyEditor.EditWindows;
using GlitchyEditor.Settings;
namespace GlitchyEditor.CodeEditors;
class VisualStudioIdeAdapter : IIdeAdapter
{
public static bool IsRunning()
private IdeInstallation _ideInstallation;
public this(IdeInstallation ideInstallation)
{
List<Process> processes = scope .();
Debug.Assert(ideInstallation.Ide == .VisualStudio);
if (Process.GetProcesses(processes) case .Err)
{
Log.EngineLogger.Error("Failed to get running processes.");
return false;
}
for (Process process in processes)
{
}
ClearAndDeleteItems!(processes);
return false;
_ideInstallation = ideInstallation;
}
public static void OpenScript(StringView fileName)
public void OpenScript(StringView fileName, int lineNumber)
{
if (IsRunning())
{
ProcessStartInfo startInfo = scope .();
startInfo.SetFileName(Application.Instance.Settings.ScriptSettings.VisualStudioPath);
startInfo.SetArguments(scope $"/Edit {fileName}");
scope SpawnedProcess().Start(startInfo);
}
else
{
ProcessStartInfo startInfo = scope .();
startInfo.SetFileName(Application.Instance.Settings.ScriptSettings.VisualStudioPath);
startInfo.SetArguments(scope $"/Edit {fileName}");
scope SpawnedProcess().Start(startInfo);
}
Open(fileName, lineNumber);
}
public static void OpenScript(StringView fileName, int lineNumber)
public void OpenScriptProject()
{
OpenScript(fileName);
Open(null, 0);
}
public static void OpenScriptProject()
private void Open(StringView fileName, int lineNumber)
{
String solutionPath = scope .();
Editor.Instance.CurrentProject.PathInProject(solutionPath, scope $"{Editor.Instance.CurrentProject.Name}.sln");
String solutionPath = scope String();
Editor.Instance.CurrentProject.GetPathToScriptSolutionFile(solutionPath);
ProcessStartInfo psi = scope .();
psi.SetFileName("devenv");
psi.SetArguments(solutionPath);
if (!File.Exists(solutionPath))
{
Log.EngineLogger.Error(scope $"Could not find solution file \"{solutionPath}\".");
return;
}
scope SpawnedProcess().Start(psi);
if (!File.Exists(_ideInstallation.Path))
{
Editor.Instance.ShowSettings();
Editor.Instance.SettingsWindow.HighlightSetting("Tools", "IDE");
PopupService.Instance.ShowMessageBox("Visual Studio not found.",
"The Visual Studio path could not be found. Please select a different IDE.");
return;
}
#if BF_PLATFORM_WINDOWS
// Reuses a running Visual Studio instance that has our solution open (via EnvDTE),
// otherwise starts a new one.
DteOpenRequest request = new .();
request.SolutionPath = solutionPath;
request.DevenvPath = _ideInstallation.Path;
request.FileName = fileName;
request.LineNumber = lineNumber;
DteOpenRequest.Start(request);
#else
ProcessStartInfo startInfo = scope .();
startInfo.SetFileName(_ideInstallation.Path);
startInfo.SetArguments(scope $"\"{solutionPath}\"");
scope SpawnedProcess().Start(startInfo);
#endif
}
}
+175 -90
View File
@@ -14,7 +14,7 @@ class AssetViewer : EditorWindow
private AssetHandle _selectedAsset;
append TexturererViewerer _textureViewer = .();
append TextureViewer _textureViewer = .();
public this(EditorContentManager contentManager)
{
@@ -133,7 +133,7 @@ class AssetViewer : EditorWindow
}
}
class TexturererViewerer
class TextureViewer
{
enum BackgroundMode : int32
{
@@ -152,35 +152,55 @@ class TexturererViewerer
enum ColorChannelSwizzle : int32
{
None,
Zero,
One,
R,
G,
B,
A
A,
OneMinusR,
OneMinusG,
OneMinusB,
OneMinusA
}
AssetHandle<Effect> _effect;
AssetHandle<Effect> _renderTargetEffect;
//AssetHandle<Effect> _renderTargetEffect;
Material _renderTargetMaterial ~ _.ReleaseRef();
float _zoom = 1.0f;
bool _showTexelBorder = false;
BackgroundMode _backgroundMode = .Checkerboard;
ColorRGBA _backgroundColor;
SampleMode _sampleMode = .Linear;
RenderTarget2D _target ~ _?.ReleaseRef();
// TODO: we don't need depth!
DepthStencilTarget _depth ~ _?.ReleaseRef();
RenderTargetGroup _targets ~ _?.ReleaseRef();
public this()
{
_effect = Content.LoadAsset("Resources/Shaders/textureViewerShader.hlsl");
_renderTargetEffect = Content.LoadAsset("Resources/Shaders/RenderTargetGroupViewer.hlsl");
_effect = Content.LoadAsset("Resources/Shaders/textureViewerShader.fx", null, true);
AssetHandle<Effect> effect = Content.LoadAsset("Resources/Shaders/RenderTargetGroupViewer.fx", null, true);
_renderTargetMaterial = new Material(effect);
_targets = new RenderTargetGroup(.(){
Width = 100,
Height = 100,
ColorTargetDescriptions = TargetDescription[](
.(.R8G8B8A8_UNorm, clearColor: .Color(ColorRGBA.Pink), ownDebugName: new $"Color"),
.(.R32_UInt, ownDebugName: new $"Enity ID (Unused)")
),
DepthTargetDescription = .(.D24_UNorm_S8_UInt, ownDebugName: new $"Depth Stencil")
});
_targets.[Friend]Identifier = "Asset Viewer";
}
float2 _position;
float4 _viewAreaRect;
bool _moving;
float _colorOffset = 0;
@@ -204,10 +224,33 @@ class TexturererViewerer
if (ImGui.CollapsingHeader("View"))
{
ImGui.SliderFloat("Zoom", &_zoom, 0.01f, 100.0f);
ImGui.SameLine();
if (ImGui.Button("Reset##ResetZoom"))
{
_zoom = 1.0f;
}
float maxDimension = max(width, height);
ImGui.SliderFloat2("Position", ref *(float[2]*)&_position, 2 * -maxDimension * _zoom, 2 * maxDimension * _zoom);
ImGui.SameLine();
if (ImGui.Button("Reset##ResetPos"))
{
_position = 0.0f;
}
ImGui.Separator();
if (ImGui.Button("Fit to View"))
{
_position = 0.0f;
_zoom = min(_targets.Width / width, _targets.Height / height);
}
ImGui.Separator();
@@ -215,7 +258,8 @@ class TexturererViewerer
if (_backgroundMode == .CustomColor)
{
ImGui.ColorPicker4("Color", ref _backgroundColor);
ImGui.ColorEdit3("Color", ref _backgroundColor);
_backgroundColor.A = 1.0f;
}
}
@@ -225,22 +269,22 @@ class TexturererViewerer
ImGui.Combo("Sampler", (.)&_sampleMode, items.Ptr, (.)items.Count);
ImGui.SliderInt("Mip Level", &_mipLevel, 0, (int32)maxMips);
ImGui.SliderInt("Array Slice", &_arraySlice, 0, (int32)arraySize);
ImGui.BeginDisabled(rtGroup == null);
ImGui.SliderInt("Group Target", &_groupIndex, (rtGroup?.HasDepth == true) ? -1 : 0, (int32)(rtGroup?.ColorTargetCount ?? 1) - 1);
ImGui.TextUnformatted(scope $"Target name: {(rtGroup?.GetTargetDescription(_groupIndex).DebugName ?? "???")}");
ImGui.SliderInt("Mip Level", &_mipLevel, 0, (int32)maxMips - 1);
ImGui.BeginDisabled(arraySize <= 1);
ImGui.SliderInt("Array Slice", &_arraySlice, 0, (int32)arraySize - 1);
ImGui.EndDisabled();
if (rtGroup != null)
{
ImGui.SliderInt("Group Target", &_groupIndex, (rtGroup?.HasDepth == true) ? -1 : 0, (int32)(rtGroup?.ColorTargetCount ?? 1) - 1);
ImGui.TextUnformatted(scope $"Target name: {(rtGroup?.GetTargetDescription(_groupIndex).DebugName ?? "???")}");
}
}
if (ImGui.CollapsingHeader("Color and Transparency"))
{
ImGui.TextUnformatted("Channel Swizzle:");
if (ImGui.BeginTable("ColorAndAlpha", 4))
{
ImGui.TableNextRow();
@@ -296,6 +340,10 @@ class TexturererViewerer
ImGui.Separator();
ImGui.Checkbox("Show texel grid", &_showTexelBorder);
ImGui.Separator();
ImGui.TextUnformatted("Channel Swizzle:");
if (ImGui.BeginTable("SwizzleTable", 9))
@@ -324,6 +372,18 @@ class TexturererViewerer
ImGui.EndTable();
}
}
if (ImGui.CollapsingHeader("Info"))
{
ImGui.Text($"Resolution: {width} x {height}");
ImGui.Text($"Array Size: {arraySize}");
ImGui.Text($"Mip Levels: {maxMips}");
if (rtGroup != null)
{
ImGui.Text($"Group Size: {rtGroup.TargetCount}");
}
}
}
private void SwizzleCombo(StringView text, ref ColorChannelSwizzle swizzle)
@@ -334,6 +394,12 @@ class TexturererViewerer
if (ImGui.BeginCombo(scope $"##swizzle{text}", scope $"{swizzle}"))
{
if (ImGui.Selectable("0", swizzle == .Zero))
swizzle = .Zero;
if (ImGui.Selectable("1", swizzle == .One))
swizzle = .One;
if (ImGui.Selectable("R", swizzle == .R))
swizzle = .R;
@@ -345,17 +411,26 @@ class TexturererViewerer
if (ImGui.Selectable("A", swizzle == .A))
swizzle = .A;
if (ImGui.Selectable("1 - R", swizzle == .OneMinusR))
swizzle = .OneMinusR;
if (ImGui.Selectable("1 - G", swizzle == .OneMinusG))
swizzle = .OneMinusG;
if (ImGui.Selectable("None", swizzle == .None))
swizzle = .None;
if (ImGui.Selectable("1 - B", swizzle == .OneMinusB))
swizzle = .OneMinusB;
if (ImGui.Selectable("1 - A", swizzle == .OneMinusA))
swizzle = .OneMinusA;
ImGui.EndCombo();
}
}
public void ViewTexture(RenderTargetGroup texture)
public void ViewTexture(RenderTargetGroup textureGroup)
{
ShowSettings(texture.Width, texture.Height, texture.MipLevels - 1, texture.ArraySize - 1, texture);
ShowSettings(textureGroup.Width, textureGroup.Height, textureGroup.MipLevels, textureGroup.ArraySize, textureGroup);
if (ImGui.BeginChild("imageChild"))
{
@@ -365,21 +440,19 @@ class TexturererViewerer
viewportSize.x = Math.Max(viewportSize.x, 1);
viewportSize.y = Math.Max(viewportSize.y, 1);
if(_target == null || viewportSize.x != _target.Width || viewportSize.y != _target.Height)
if (_targets.Width != viewportSize.x || _targets.Height != viewportSize.y)
{
_target?.ReleaseRef();
_target = new RenderTarget2D(.(.R8G8B8A8_UNorm_SRGB, (.)viewportSize.x, (.)viewportSize.y));
_target.SamplerState = SamplerStateManager.PointClamp;
_depth?.ReleaseRef();
_depth = new DepthStencilTarget((.)viewportSize.x, (.)viewportSize.y, .D16_UNorm);
_targets.Resize((.)viewportSize.x, (.)viewportSize.y);
}
RenderBackground();
RenderTexture(texture);
ImGui.Image(_target, viewportSize);
RenderTexture(textureGroup);
ImGui.Image(_targets.GetViewBinding(0), viewportSize);
_lastPreviewTopLeft = (.)ImGui.GetItemRectMin();
ImGui.EndChild();
}
@@ -387,7 +460,7 @@ class TexturererViewerer
public void ViewTexture(Texture texture)
{
ShowSettings(texture.Width, texture.Height, texture.MipLevels - 1, texture.ArraySize - 1, null);
ShowSettings(texture.Width, texture.Height, texture.MipLevels, texture.ArraySize, null);
if (ImGui.BeginChild("imageChild"))
{
@@ -398,20 +471,18 @@ class TexturererViewerer
viewportSize.x = Math.Max(viewportSize.x, 1);
viewportSize.y = Math.Max(viewportSize.y, 1);
if(_target == null || viewportSize.x != _target.Width || viewportSize.y != _target.Height)
if (_targets.Width != viewportSize.x || _targets.Height != viewportSize.y)
{
_target?.ReleaseRef();
_target = new RenderTarget2D(.(.R8G8B8A8_UNorm_SRGB, (.)viewportSize.x, (.)viewportSize.y));
_target.SamplerState = SamplerStateManager.PointClamp;
_depth?.ReleaseRef();
_depth = new DepthStencilTarget((.)viewportSize.x, (.)viewportSize.y, .D16_UNorm);
_targets.Resize((.)viewportSize.x, (.)viewportSize.y);
}
RenderBackground();
RenderTexture(texture);
ImGui.Image(_target, viewportSize);
ImGui.Image(_targets.GetViewBinding(0), viewportSize);
_lastPreviewTopLeft = (.)ImGui.GetItemRectMin();
ImGui.EndChild();
}
@@ -419,11 +490,11 @@ class TexturererViewerer
float lastWheel;
float2 _lastPreviewTopLeft;
private void UpdateInput()
{
var windowPos = ImGui.GetWindowPos();
var mousePos = ImGui.GetIO().MousePos;
float2 mouseInWindow = .(mousePos.x - windowPos.x, mousePos.y - windowPos.y);
bool windowHovered = ImGui.IsWindowHovered();
@@ -441,18 +512,21 @@ class TexturererViewerer
float mouseWheel = ImGui.GetIO().MouseWheel;
float delta = mouseWheel - lastWheel;
float2 mousePosInObject = .(mousePos.x, mousePos.y) - _lastPreviewTopLeft;
float2 mouseOffsetFromCenter = mousePosInObject - float2(_targets.Width, _targets.Height) / 2.0f;
if(delta != 0)
{
_position -= mouseInWindow;
_position -= mouseOffsetFromCenter;
_position /= _zoom;
_zoom *= Math.Pow(1.1f, delta);
_position *= _zoom;
_position += mouseInWindow;
_position += mouseOffsetFromCenter;
}
}
if(_moving)
@@ -468,19 +542,21 @@ class TexturererViewerer
private void RenderBackground()
{
Viewport vp = .(0, 0, _target.Width, _target.Height);
Viewport vp = .(0, 0, _targets.Width, _targets.Height);
RenderCommand.SetViewport(vp);
RenderCommand.Clear(_targets, .ColorDepth);
// TODO: don't clear pink!
RenderCommand.Clear(_target, .Pink);
RenderCommand.Clear(_depth, .Depth, 1.0f, 0);
//RenderCommand.Clear(_target, .Pink);
//RenderCommand.Clear(_depth, .Depth, 1.0f, 0);
//_target.Bind();
RenderCommand.SetRenderTarget(_target);
RenderCommand.SetDepthStencilTarget(_depth);
RenderCommand.SetRenderTargetGroup(_targets);
//RenderCommand.SetRenderTarget(_idTarget, 1);
//RenderCommand.SetDepthStencilTarget(_depth);
RenderCommand.BindRenderTargets();
float2 targetSize = float2(_target.Width, _target.Height);
float2 targetSize = float2(_targets.Width, _targets.Height);
_camera.Left = 0;
_camera.Top = 0;
@@ -520,9 +596,9 @@ class TexturererViewerer
Renderer2D.EndScene();
}
private void RenderTexture(TextureViewBinding viewedTexture, float2 textureSize, Format format)
private void RenderTexture(Asset textureAsset, float2 textureSize, Format format, int32? groupTargetIndex = null)
{
float2 mippedTextureSize = float2((int)textureSize.X >> _mipLevel, (int)textureSize.X >> _mipLevel);
float2 mippedTextureSize = float2((int)textureSize.X >> _mipLevel, (int)textureSize.Y >> _mipLevel);
// Sicherstellen, dass die Auflösung nicht 0 wird
mippedTextureSize = max(mippedTextureSize, 1);
@@ -541,55 +617,64 @@ class TexturererViewerer
viewedTexture.SamplerState = SamplerStateManager.LinearClamp;
}*/
//Renderer2D.BeginScene(_camera, .SortByTexture, _effect);
RenderCommand.Clear(_targets, .Depth);
// float3(_position * .(1, -1), 0)
RenderCommand.Clear(_depth, .Depth, 1.0f, 0);
Matrix matrix = .Translation(float3((_position + float2(_targets.Width, _targets.Height) / 2) * .(1, -1), 0)) * .Scaling(float3(zoomedTextureSize, 1));
Matrix matrix = .Translation(float3(_position * .(1, -1), 0)) * .Scaling(float3(zoomedTextureSize, 1));
_renderTargetMaterial.SetVariable("WorldViewProjection", _camera.ViewProjection * matrix);
_renderTargetMaterial.SetVariable("ColorOffset", _colorOffset);
_renderTargetMaterial.SetVariable("ColorScale", _colorScale);
_renderTargetMaterial.SetVariable("AlphaOffset", _alphaOffset);
_renderTargetMaterial.SetVariable("AlphaScale", _alphaScale);
// TODO: ViewProjection kommt nicht korrekt an?
_renderTargetEffect.Variables["WorldViewProjection"].SetData(_camera.ViewProjection * matrix);
_renderTargetEffect.Variables["ColorOffset"].SetData(_colorOffset);
_renderTargetEffect.Variables["ColorScale"].SetData(_colorScale);
_renderTargetEffect.Variables["AlphaOffset"].SetData(_alphaOffset);
_renderTargetEffect.Variables["AlphaScale"].SetData(_alphaScale);
_renderTargetMaterial.SetVariable("Texels", mippedTextureSize);
_renderTargetEffect.Variables["Texels"].SetData(mippedTextureSize);
_renderTargetMaterial.SetVariable("MipLevel", (float)_mipLevel);
_renderTargetEffect.Variables["MipLevel"].SetData((float)_mipLevel);
_renderTargetMaterial.SetVariable("Swizzle", int4((int32)_swizzleR, (int32)_swizzleG, (int32)_swizzleB, (int32)_swizzleA));
_renderTargetMaterial.SetVariable("ShowTexelBorders", _showTexelBorder);
_renderTargetMaterial.SetVariable("TexelBorderCutoff", 10.0f);
_renderTargetEffect.Variables["Swizzle"].SetData(int4((int32)_swizzleR, (int32)_swizzleG, (int32)_swizzleB, (int32)_swizzleA));
int textureSlot = -1;
if (((DirectX.DXGI.Format)format).IsInt())
if (format.IsInt())
{
// Int Texture
_renderTargetEffect.Variables["Mode"].SetData(1);
_renderTargetEffect.SetTexture("IntTexture", viewedTexture);
_renderTargetEffect.SetTexture("UIntTexture", null);
_renderTargetEffect.SetTexture("Texture", null);
// Int Texture
_renderTargetMaterial.SetVariable("Mode", 1);
textureSlot = _renderTargetMaterial.Effect.Textures["IntTexture"].PsSlot.Index;
}
else if (format.IsUInt())
{
// UInt Texture
_renderTargetEffect.Variables["Mode"].SetData(2);
_renderTargetEffect.SetTexture("IntTexture", null);
_renderTargetEffect.SetTexture("UIntTexture", viewedTexture);
_renderTargetEffect.SetTexture("Texture", null);
_renderTargetMaterial.SetVariable("Mode", 2);
textureSlot = _renderTargetMaterial.Effect.Textures["UIntTexture"].PsSlot.Index;
}
else
{
// Float Texture
_renderTargetEffect.Variables["Mode"].SetData(0);
_renderTargetEffect.SetTexture("IntTexture", null);
_renderTargetEffect.SetTexture("UIntTexture", null);
_renderTargetEffect.SetTexture("Texture", viewedTexture);
_renderTargetMaterial.SetVariable("Mode", 0);
textureSlot = _renderTargetMaterial.Effect.Textures["Texture"].PsSlot.Index;
}
_renderTargetEffect.Variables["Swizzle"].SetData(int4((int32)_swizzleR, (int32)_swizzleG, (int32)_swizzleB, (int32)_swizzleA));
_renderTargetMaterial.Bind();
_renderTargetEffect.ApplyChanges();
_renderTargetEffect.Bind();
if (let renderTargetGroup = textureAsset as RenderTargetGroup)
{
using (let viewBinding = renderTargetGroup.GetViewBinding((.)groupTargetIndex))
{
RenderCommand.BindTexture(viewBinding, textureSlot, .Pixel);
// TODO: BindSampler command?
// TODO: Allow manually setting sampler for view binding?
}
}
else if (let texture = textureAsset as Texture)
{
using (let viewBinding = texture.GetViewBinding())
{
RenderCommand.BindTexture(viewBinding, textureSlot, .Pixel);
}
}
Quad.Draw();
}
@@ -598,11 +683,11 @@ class TexturererViewerer
{
var desc = _groupIndex >= 0 ? viewedTexture.[Friend]_colorTargetDescriptions[_groupIndex] : viewedTexture.[Friend]_depthTargetDescription;
RenderTexture(viewedTexture.GetViewBinding(_groupIndex), float2(viewedTexture.Width, viewedTexture.Height), (.)desc.Format.GetShaderViewFormat());
RenderTexture(viewedTexture, float2(viewedTexture.Width, viewedTexture.Height), (.)desc.Format.GetShaderViewFormat(), _groupIndex);
}
private void RenderTexture(Texture viewedTexture)
{
RenderTexture(viewedTexture.GetViewBinding(), float2(viewedTexture.Width, viewedTexture.Height), viewedTexture.Format);
RenderTexture(viewedTexture, float2(viewedTexture.Width, viewedTexture.Height), viewedTexture.Format, -1);
}
}
@@ -470,7 +470,8 @@ namespace GlitchyEditor.EditWindows
spriteRendererComponent.Color = ColorRGBA.SRgbToLinear(spriteColor);
ImGui.PropertyTableStartNewProperty("Texture");
ImGui.Button("...");
ShowAssetDropTarget(ref spriteRendererComponent.Sprite);
/*ImGui.Button("...");
if (ImGui.BeginDragDropTarget())
{
@@ -486,10 +487,86 @@ namespace GlitchyEditor.EditWindows
}
ImGui.EndDragDropTarget();
}
}*/
ImGui.PropertyTableStartNewProperty("UV Transform");
ImGui.Float4Editor("##UV Transform", ref spriteRendererComponent.UvTransform, resetValues: float4(0, 0, 1, 1));
ImGui.PropertyTableStartNewProperty("Material");
ShowAssetDropTarget<Material>(ref spriteRendererComponent.Material);
}
public static bool ShowAssetDropTarget(ref AssetHandle target)
{
bool changed = false;
Asset currentAsset = Content.GetAsset(target);
StringView identifier = currentAsset?.Identifier ?? (target.IsValid ? "<Missing Asset>" : "None");
if (ImGui.Button(identifier.ToScopeCStr!()))
{
// TODO: Show asset selector
}
if (ImGui.BeginDragDropTarget())
{
ImGui.Payload* payload = ImGui.AcceptDragDropPayload(.ContentBrowserItem);
if (payload != null)
{
Log.EngineLogger.Warning("");
// We shouldn't pass the path/identifier. We should carry ID and Type!
StringView path = .((char8*)payload.Data, (int)payload.DataSize);
// TODO: Somehow validate the type, please!
target = (AssetHandle)Content.LoadAsset(path);
changed = true;
}
ImGui.EndDragDropTarget();
}
return changed;
}
public static bool ShowAssetDropTarget<T>(ref AssetHandle<T> target) where T : Asset
{
bool changed = false;
T currentAsset = target.Get();
StringView identifier = (target.IsValid ? "<Missing Asset>" : "None");
if (currentAsset != null)
identifier = currentAsset.Identifier;
if (ImGui.Button(identifier.ToScopeCStr!()))
{
// TODO: Show asset selector
}
if (ImGui.BeginDragDropTarget())
{
ImGui.Payload* payload = ImGui.AcceptDragDropPayload(.ContentBrowserItem);
if (payload != null)
{
Log.EngineLogger.Warning("");
StringView path = .((char8*)payload.Data, (int)payload.DataSize);
// TODO: Somehow validate the type, please!
target = (AssetHandle<T>)Content.LoadAsset(path);
changed = true;
}
ImGui.EndDragDropTarget();
}
return changed;
}
private static void ShowCircleRendererComponentEditor(Entity entity, CircleRendererComponent* circleRendererComponent)
@@ -607,31 +684,7 @@ namespace GlitchyEditor.EditWindows
{
ImGui.PropertyTableStartNewProperty("Material");
Material material = meshRendererComponent.Material;
StringView identifier = material?.Identifier ?? "None";
ImGui.Button(identifier.ToScopeCStr!());
if (ImGui.BeginDragDropTarget())
{
ImGui.Payload* payload = ImGui.AcceptDragDropPayload(.ContentBrowserItem);
if (payload != null)
{
StringView fullpath = .((char8*)payload.Data, (int)payload.DataSize);
meshRendererComponent.Material = Content.LoadAsset(fullpath);
}
ImGui.EndDragDropTarget();
}
/*Effect effect = material?.Effect;
if (effect == null)
return;*/
// Show a preview of the material here!
ShowAssetDropTarget<Material>(ref meshRendererComponent.Material);
}
private static void ShowRigidBody2DComponentEditor(Entity entity, Rigidbody2DComponent* rigidBodyComponent)
@@ -901,50 +954,8 @@ namespace GlitchyEditor.EditWindows
private static void ShowMeshComponentEditor(Entity entity, MeshComponent* meshComponent)
{
ImGui.PropertyTableStartNewProperty("Mesh");
GeometryBinding mesh = meshComponent.Mesh;
StringView identifier = mesh?.Identifier ?? (meshComponent.Mesh.IsValid ? "<Missing Asset>" : "None");
ImGui.Button(identifier.ToScopeCStr!());
if (ImGui.BeginDragDropTarget())
{
ImGui.Payload* payload = ImGui.AcceptDragDropPayload(.ContentBrowserItem);
if (payload != null)
{
StringView fullpath = .((char8*)payload.Data, (int)payload.DataSize);
/*int idx = fullpath.IndexOf('#');
if (idx == -1)
{
// Doesn't make sense here, we NEED a sub asset
Runtime.NotImplemented();
}
StringView filePath = fullpath.Substring(0, idx);
StringView meshName = fullpath.Substring(idx + 1);*/
meshComponent.Mesh = Content.LoadAsset(fullpath);
// TODO: support multiple primitives (treat every primitive as a single mesh? or: mesh can have multiple primitives)
/*using (GeometryBinding binding = ModelLoader.LoadMesh(filePath, meshName, 0))
{
meshComponent.Mesh = binding;
}*/
//ModelLoader.LoadModel(scope .(path), )
/*using (Texture2D newTexture = new Texture2D(path, true))
{
newTexture.SamplerState = SamplerStateManager.AnisotropicWrap;
material.SetTexture(texture.key, newTexture);
}*/
}
ImGui.EndDragDropTarget();
}
ShowAssetDropTarget<GeometryBinding>(ref meshComponent.Mesh);
}
private static void ShowAddComponentButton(Entity entity)

Some files were not shown because too many files have changed in this diff Show More