diff --git a/.gitmodules b/.gitmodules index cdc2552..4c735e8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -24,4 +24,10 @@ url = https://github.com/EinScott/bon.git [submodule "GlitchyEngineHelper/vendor/DirectXTK"] path = GlitchyEngineHelper/vendor/DirectXTK - url = https://github.com/microsoft/DirectXTK.git \ No newline at end of file + url = https://github.com/microsoft/DirectXTK.git +[submodule "GlitchyEngine/vendor/box2D"] + path = GlitchyEngine/vendor/box2D + url = https://github.com/jazzbre/box2d-beef.git +[submodule "GlitchyEngine/vendor/Beef.Linq"] + path = GlitchyEngine/vendor/Beef.Linq + url = https://github.com/aharabada/Beef.Linq.git diff --git a/BeefSpace.toml b/BeefSpace.toml index e243759..6bee90e 100644 --- a/BeefSpace.toml +++ b/BeefSpace.toml @@ -1,6 +1,10 @@ FileVersion = 1 -Projects = {Sandbox = {Path = "Sandbox"}, GlitchyEngine = {Path = "GlitchyEngine"}, GlitchLog = {Path = "GlitchLog"}, DirectX = {Path = "GlitchyEngine/vendor/directx/DirectX"}, DirectXTK = {Path = "GlitchyEngine/vendor/DirectXTK/DirectXTK-beef"}, 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"}} -WorkspaceFolders = {GlitchyEngine = ["GlitchyEngine", "GlitchLog", "GlitchyEngineHelper"], "GlitchyEngine/Dependencies" = ["cgltf-beef", "DirectX", "DirectXTK", "FreeType", "ImGui", "ImGuiImplDX11", "ImGuiImplWin32", "ImGuizmo", "LodePng", "msdfgen-beef", "bon"]} +Projects = {Sandbox = {Path = "Sandbox"}, 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"}} +Unlocked = ["corlib"] +WorkspaceFolders = {GlitchyEngine = ["GlitchyEngine", "GlitchLog", "GlitchyEngineHelper"], "GlitchyEngine/Dependencies" = ["cgltf-beef", "DirectX", "FreeType", "ImGui", "ImGuiImplDX11", "ImGuiImplWin32", "ImGuizmo", "LodePng", "msdfgen-beef", "bon", "box2d-beef", "Beef.Linq"]} [Workspace] StartupProject = "GlitchyEditor" + +[Configs.Debug.Win64] +AllocStackTraceDepth = 12 diff --git a/Doc/DesignCodeExamples/Assets/BasicAssetUsage.bf b/Doc/DesignCodeExamples/Assets/BasicAssetUsage.bf new file mode 100644 index 0000000..3787ca7 --- /dev/null +++ b/Doc/DesignCodeExamples/Assets/BasicAssetUsage.bf @@ -0,0 +1,92 @@ +/// +/// Loading and usage of an asset. +/// +// Shows the usage of the Content Manager. +// An asset is loaded with Content.LoadAsset(AssetName) +// Loading an asset returns an AssetHandle. +// Usually you want to hold on to the AssetHandle. +// If you need the actual asset you can use Content.GetAsset(AssetHandle) to retrieve the actual asset. + +/* + * Only hold on to AssetHandle. + * Do NOT hold actual Assets! The Asset might get invalidated + * (e.g. reloading after the source-file changed). + */ +AssetHandle _lineEffect; + +void Start() +{ + /* + * Loading an asset returns a handle to that asset. + */ + _lineEffect = Content.LoadAsset("Shaders\\LineEffect.hlsl"); +} + +void Update() +{ + // Use case 1: + { + // Retrieve the actual asset. Note that this Asset is only guaranteed to persist + // for the current frame. + Effect fx = Content.GetAsset(_lineEffect); + + // Note: Assets are ref counted, however since they are not inteded to be held GetAsset + // does NOT increment the reference counter so you usually should NOT decrement it. + // The exception is when you manually hold on to an asset (see Example below) + + // Use asset + fx.Variables["ViewProjection"].SetData(viewProjection * transform); + fx.Variables["Color"].SetData(color); + fx.ApplyChanges(); + fx.Bind(); + } + + // Use case 2: + { + // Retrieve the actual asset. + Effect fx = _lineEffect.Get(); + + // ... + // Same as case 1 + } +} + + +/// +/// Holding on to an actual asset. +/// +// Shows the usage of the Content Manager when you hold a reference to the actual Asset instead of the handle. +// This should usually not be done. Eventhough it is technically fine, it has the implication, that if the +// content manager decides to reload the asset the changes will not be reflected in the held reference +// (which they would if you used GetAsset before every usage.) +// Also the content manager can't unload the asset because the reference counter wouldn't be zero. +// Though there currently is no way for the content manager to unload an asset unless it is specifically told to do so. + +/* + * Field to hold the asset. + */ +Effect _lineEffect; + +void Start() +{ + // Load the Asset. + AssetHandle handle = Content.LoadAsset("Shaders\\LineEffect.hlsl"); + // Get the actual asset. + _lineEffect = Content.GetAsset(handle); // Alternative: _lineEffect = handle.Get(); + // Increment the reference counter so that the asset doesn't get unloaded. + _lineEffect.AddRef(); +} + +void Destroy() +{ + // Make sure to release the asset once you don't need it anymore. Otherwise it will leak. + _lineEffect.Release(); +} + +void Update() +{ + _lineEffect.Variables["ViewProjection"].SetData(viewProjection * transform); + _lineEffect.Variables["Color"].SetData(color); + _lineEffect.ApplyChanges(); + _lineEffect.Bind(); +} diff --git a/Doc/DesignCodeExamples/Assets/SmartAssetHandle.bf b/Doc/DesignCodeExamples/Assets/SmartAssetHandle.bf new file mode 100644 index 0000000..b0c0340 --- /dev/null +++ b/Doc/DesignCodeExamples/Assets/SmartAssetHandle.bf @@ -0,0 +1,44 @@ +/// +/// This shows the usage of a smart/typed Asset Handle. +/// +// In the basic usage-patterns the user has two options: +// Either he/she holds a handle and has to query for the actual asset every time +// or he/she hold the actual asset and loses reloading capabilities. +// The idea of the auto asset is to provide a way that can provide both. + +AssetHandle _lineEffect; + +void Start() +{ + /* + * Loading an asset returns a handle to that asset. + * The asset handle is implicitly converted to a smart AssetHandle. + */ + _lineEffect = Content.LoadAsset("Shaders\\LineEffect.hlsl"); +} + +void Update() +{ + // Use case 1 (Convenience) + { + _lineEffect.Variables["ViewProjection"].SetData(viewProjection * transform); + _lineEffect.Variables["Color"].SetData(color); + _lineEffect.ApplyChanges(); + _lineEffect.Bind(); + } + + // Use case 2 (Technically more efficient) + { + Effect fx = _lineEffect.Get(); + fx.Variables["ViewProjection"].SetData(viewProjection * transform); + fx.Variables["Color"].SetData(color); + fx.ApplyChanges(); + fx.Bind(); + } +} + +// +// Auto Assets use comp time to expose all the fields, Methods and properties of the actual asset. +// They will automatically check if the asset for the handle changed and update accordingly. +// This means it is more convenient to use than both basic patterns and has all benefits of both. +// diff --git a/Doc/DesignCodeExamples/readme.md b/Doc/DesignCodeExamples/readme.md new file mode 100644 index 0000000..69326ab --- /dev/null +++ b/Doc/DesignCodeExamples/readme.md @@ -0,0 +1,3 @@ +# Design Code Examples + +This directory (and its subdirectories) contains non functional code snippets showing the intended usage of certain systems of the engine. \ No newline at end of file diff --git a/GlitchyEditor/content/Models/plane.glb b/GlitchyEditor/content/Models/plane.glb new file mode 100644 index 0000000..c345a08 Binary files /dev/null and b/GlitchyEditor/content/Models/plane.glb differ diff --git a/GlitchyEditor/content/Models/plane.glb.ass b/GlitchyEditor/content/Models/plane.glb.ass new file mode 100644 index 0000000..d5fb47f --- /dev/null +++ b/GlitchyEditor/content/Models/plane.glb.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "ModelAssetLoader", + Config = (GlitchyEditor.Assets.ModelAssetLoaderConfig){} +} \ No newline at end of file diff --git a/GlitchyEditor/content/Models/sphere.glb b/GlitchyEditor/content/Models/sphere.glb new file mode 100644 index 0000000..1e81373 Binary files /dev/null and b/GlitchyEditor/content/Models/sphere.glb differ diff --git a/GlitchyEditor/content/Models/sphere.glb.ass b/GlitchyEditor/content/Models/sphere.glb.ass new file mode 100644 index 0000000..d5fb47f --- /dev/null +++ b/GlitchyEditor/content/Models/sphere.glb.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "ModelAssetLoader", + Config = (GlitchyEditor.Assets.ModelAssetLoaderConfig){} +} \ No newline at end of file diff --git a/GlitchyEditor/content/Scenes/Simple3D.scene b/GlitchyEditor/content/Scenes/Simple3D.scene new file mode 100644 index 0000000..421c22e --- /dev/null +++ b/GlitchyEditor/content/Scenes/Simple3D.scene @@ -0,0 +1,224 @@ +{ + Name = "Scene name here pls!!!", + Entities = [ + { + Id = 5169765174113462770, + NameComponent = { + Name = "Sphere" + }, + TransformComponent = { + Position = { + X = 0, + Y = 0.5, + Z = 0 + }, + Rotation = { + X = 0, + Y = 0, + Z = 0, + W = 1 + }, + Scale = { + X = 1, + Y = 1, + Z = 1 + }, + EditorEulerRotation = { + X = 0, + Y = 0, + Z = 0 + } + }, + MeshComponent = { + Mesh = "Models/sphere.glb" + }, + MeshRendererComponent = { + Material = "Textures/TestMaterial.mat" + } + }, + { + Id = 17158420331978163131, + NameComponent = { + Name = "Light" + }, + TransformComponent = { + Position = { + X = -1, + Y = 4, + Z = -4 + }, + Rotation = { + X = 0.614328027, + Y = 0.239776045, + Z = 0.0315669999, + W = 0.751073837 + }, + Scale = { + X = 0.999998868, + Y = 1, + Z = 1.00000095 + }, + EditorEulerRotation = { + X = 1.30899811, + Y = 0.349065989, + Z = 0.349065989 + } + }, + LightComponent = { + LightType = .Directional, + Illuminance = 10, + Color = { + R = 1, + G = 0.991771996, + B = 0.74086225 + } + } + }, + { + Id = 5556050645816939548, + NameComponent = { + Name = "Plane" + }, + TransformComponent = { + Position = { + X = 0, + Y = 0, + Z = 0 + }, + Rotation = { + X = 0, + Y = 0, + Z = 0, + W = 1 + }, + Scale = { + X = 10, + Y = 1, + Z = 10 + }, + EditorEulerRotation = { + X = 0, + Y = 0, + Z = 0 + } + }, + MeshComponent = { + Mesh = "Models/plane.glb" + }, + MeshRendererComponent = { + Material = "Textures/TestMaterial.mat" + } + }, + { + Id = 3947673900993587516, + NameComponent = { + Name = "Camera" + }, + TransformComponent = { + Position = { + X = 0, + Y = 2, + Z = -5 + }, + Rotation = { + X = 0.216440007, + Y = 0, + Z = 0, + W = 0.976296008 + }, + Scale = { + X = 1, + Y = 1, + Z = 1 + }, + EditorEulerRotation = { + X = 0.436332017, + Y = 0, + Z = 0 + } + }, + CameraComponent = { + Primary = true, + ProjectionType = .InfinitePerspective, + PerspectiveFovY = 1.30899692, + PerspectiveNearPlane = 0.100000001, + PerspectiveFarPlane = 10000, + OrthographicHeight = 10, + OrthographicNearPlane = 0, + OrthographicFarPlane = 10, + AspectRatio = 2.19888878, + FixedAspectRatio = false + } + }, + { + Id = 420718992779301759, + NameComponent = { + Name = "Entity" + }, + TransformComponent = { + Position = { + X = 3, + Y = 0.501076996, + Z = -0.669378757 + }, + Rotation = { + X = 0.18301262, + Y = -0.683013558, + Z = 0.683012128, + W = 0.183013007 + }, + Scale = { + X = 1, + Y = 1, + Z = 1 + }, + EditorEulerRotation = { + X = 1.57079446, + Y = -2.61799312, + Z = 0 + } + }, + MeshComponent = { + Mesh = "Models/plane.glb" + }, + MeshRendererComponent = { + Material = "" + } + }, + { + Id = 8080288871271083510, + NameComponent = { + Name = "Rocket" + }, + TransformComponent = { + Position = { + X = 2.37353587, + Y = 0.629615188, + Z = -1.34831977 + }, + Rotation = { + X = 0.270598024, + Y = -0.65328145, + Z = 0.65328151, + W = 0.270598054 + }, + Scale = { + X = 0.999999881, + Y = 0.999999821, + Z = 0.999999702 + }, + EditorEulerRotation = { + X = 1.57079637, + Y = -2.3561945, + Z = 8.94069743e-08 + } + }, + MeshComponent = { + Mesh = "Models/plane.glb" + }, + MeshRendererComponent = { + Material = "Textures/RocketMaterial.mat" + } + } + ] +} \ No newline at end of file diff --git a/GlitchyEditor/content/Scenes/childTest.scene b/GlitchyEditor/content/Scenes/childTest.scene new file mode 100644 index 0000000..494e902 --- /dev/null +++ b/GlitchyEditor/content/Scenes/childTest.scene @@ -0,0 +1,212 @@ +{ + Name = "Scene name here pls!!!", + Entities = [ + { + Id = 6000868443984780499, + NameComponent = { + Name = "Child" + }, + SpriterRendererComponent = { + Color = { + R = 1, + G = 0.388184, + B = 0.090109, + A = 1 + }, + UvTransform = { + X = 0, + Y = 0, + Z = 1, + W = 1 + } + }, + TransformComponent = { + ParentId = 820806682740348099, + Position = { + X = 0, + Y = 0.75, + Z = 0 + }, + Rotation = { + X = 0, + Y = 0, + Z = 0, + W = 1 + }, + Scale = { + X = 0.5, + Y = 0.5, + Z = 1 + }, + EditorEulerRotation = { + X = 0, + Y = 0, + Z = 0 + } + } + }, + { + Id = 820806682740348099, + NameComponent = { + Name = "Quad" + }, + SpriterRendererComponent = { + Color = { + R = 0, + G = 0.551178, + B = 0.328787, + A = 1 + }, + UvTransform = { + X = 0, + Y = 0, + Z = 1, + W = 1 + } + }, + TransformComponent = { + Position = { + X = 0, + Y = 2, + Z = 0 + }, + Rotation = { + X = 0, + Y = 0, + Z = 0, + W = 1 + }, + Scale = { + X = 1, + Y = 1, + Z = 1 + }, + EditorEulerRotation = { + X = 0, + Y = 0, + Z = 0 + } + }, + Rigidbody2D = { + BodyType = .Dynamic, + FixedRotation = false + }, + BoxCollider2D = { + Offset = { + X = 0, + Y = 0 + }, + Size = { + X = 0.5, + Y = 0.5 + }, + Density = 1, + Friction = 0.5, + Restitution = 0, + RestitutionThreshold = 0.5 + } + }, + { + Id = 15398726361722237419, + NameComponent = { + Name = "Floor" + }, + SpriterRendererComponent = { + Color = { + R = 1, + G = 0.941886, + B = 0.401485, + A = 1 + }, + UvTransform = { + X = 0, + Y = 0, + Z = 1, + W = 1 + } + }, + TransformComponent = { + Position = { + X = 0, + Y = -0.5, + Z = 0 + }, + Rotation = { + X = 0, + Y = 0, + Z = 0, + W = 1 + }, + Scale = { + X = 10, + Y = 1, + Z = 1 + }, + EditorEulerRotation = { + X = 0, + Y = 0, + Z = 0 + } + }, + Rigidbody2D = { + BodyType = .Static, + FixedRotation = false + }, + BoxCollider2D = { + Offset = { + X = 0, + Y = 0 + }, + Size = { + X = 0.5, + Y = 0.5 + }, + Density = 1, + Friction = 0.5, + Restitution = 0, + RestitutionThreshold = 0.5 + } + }, + { + Id = 1491484622542812645, + NameComponent = { + Name = "Camera" + }, + TransformComponent = { + Position = { + X = 0, + Y = 1, + Z = -5 + }, + Rotation = { + X = 0, + Y = 0, + Z = 0, + W = 1 + }, + Scale = { + X = 1, + Y = 1, + Z = 1 + }, + EditorEulerRotation = { + X = 0, + Y = 0, + Z = 0 + } + }, + CameraComponent = { + Primary = true, + ProjectionType = .InfinitePerspective, + PerspectiveFovY = 1.308997, + PerspectiveNearPlane = 0.1, + PerspectiveFarPlane = 10000, + OrthographicHeight = 10, + OrthographicNearPlane = 0, + OrthographicFarPlane = 10, + AspectRatio = 2.116827, + FixedAspectRatio = false + } + } + ] +} \ No newline at end of file diff --git a/GlitchyEditor/content/Scenes/physics2D.scene b/GlitchyEditor/content/Scenes/physics2D.scene new file mode 100644 index 0000000..8744796 --- /dev/null +++ b/GlitchyEditor/content/Scenes/physics2D.scene @@ -0,0 +1,303 @@ +{ + Name = "Scene name here pls!!!", + Entities = [ + { + Id = 820806682740348099, + NameComponent = { + Name = "Quad" + }, + SpriteRendererComponent = { + Color = { + R = 0, + G = 0.55117774, + B = 0.32878688, + A = 1 + }, + Sprite = "Textures/TestMat/rustediron2_albedo.png", + UvTransform = { + X = 0, + Y = 0, + Z = 1, + W = 1 + } + }, + TransformComponent = { + Position = { + X = 0, + Y = 2, + Z = 0 + }, + Rotation = { + X = 0, + Y = 0, + Z = 0, + W = 1 + }, + Scale = { + X = 1, + Y = 1, + Z = 1 + }, + EditorEulerRotation = { + X = 0, + Y = 0, + Z = 0 + } + }, + Rigidbody2D = { + BodyType = .Dynamic, + FixedRotation = false + }, + BoxCollider2D = { + Offset = { + X = 0, + Y = 0 + }, + Size = { + X = 0.5, + Y = 0.5 + }, + Density = 1, + Friction = 0.5, + Restitution = 0, + RestitutionThreshold = 0.5 + } + }, + { + Id = 15398726361722237419, + NameComponent = { + Name = "Floor" + }, + SpriteRendererComponent = { + Color = { + R = 1, + G = 0.941886365, + B = 0.401484847, + A = 1 + }, + Sprite = "", + UvTransform = { + X = 0, + Y = 0, + Z = 1, + W = 1 + } + }, + TransformComponent = { + Position = { + X = 0, + Y = -0.5, + Z = 0 + }, + Rotation = { + X = 0, + Y = 0, + Z = 0, + W = 1 + }, + Scale = { + X = 10, + Y = 1, + Z = 1 + }, + EditorEulerRotation = { + X = 0, + Y = 0, + Z = 0 + } + }, + Rigidbody2D = { + BodyType = .Static, + FixedRotation = false + }, + BoxCollider2D = { + Offset = { + X = 0, + Y = 0 + }, + Size = { + X = 0.5, + Y = 0.5 + }, + Density = 1, + Friction = 0.5, + Restitution = 0, + RestitutionThreshold = 0.5 + } + }, + { + Id = 1491484622542812645, + NameComponent = { + Name = "Camera" + }, + TransformComponent = { + Position = { + X = 0, + Y = 1, + Z = -5 + }, + Rotation = { + X = 0, + Y = 0, + Z = 0, + W = 1 + }, + Scale = { + X = 1, + Y = 1, + Z = 1 + }, + EditorEulerRotation = { + X = 0, + Y = 0, + Z = 0 + } + }, + CameraComponent = { + Primary = true, + ProjectionType = .InfinitePerspective, + PerspectiveFovY = 1.30899692, + PerspectiveNearPlane = 0.100000001, + PerspectiveFarPlane = 10000, + OrthographicHeight = 10, + OrthographicNearPlane = 0, + OrthographicFarPlane = 10, + AspectRatio = 3.22169805, + FixedAspectRatio = false + } + }, + { + Id = 15710354273720487680, + NameComponent = { + Name = "Circle" + }, + CircleRendererComponent = { + Color = { + R = 1, + G = 0, + B = 0, + A = 1 + }, + InnerRadius = 0.300000012, + Sprite = "", + UvTransform = { + X = 0, + Y = 0, + Z = 1, + W = 1 + } + }, + TransformComponent = { + Position = { + X = -0.12120308, + Y = 0.660160363, + Z = 0 + }, + Rotation = { + X = 0, + Y = 0, + Z = 0, + W = 1 + }, + Scale = { + X = 1, + Y = 1, + Z = 1 + }, + EditorEulerRotation = { + X = 0, + Y = 0, + Z = 0 + } + }, + Rigidbody2D = { + BodyType = .Dynamic, + FixedRotation = false + }, + CircleCollider2D = { + Offset = { + X = 0, + Y = 0 + }, + Radius = 0.5, + Density = 1, + Friction = 0.5, + Restitution = 0, + RestitutionThreshold = 0.5 + } + }, + { + Id = 935640822766280888, + NameComponent = { + Name = "Light" + }, + TransformComponent = { + Position = { + X = 3.88327599, + Y = 0, + Z = -0.808795214 + }, + Rotation = { + X = 0.497987002, + Y = -0.103420995, + Z = 0.155380026, + W = 0.846859217 + }, + Scale = { + X = 0.999997795, + Y = 1, + Z = 1 + }, + EditorEulerRotation = { + X = 1.0908947, + Y = -0.340793997, + Z = 0.160857916 + } + }, + LightComponent = { + LightType = .Directional, + Illuminance = 12.8999996, + Color = { + R = 0.985467017, + G = 1, + B = 0.569978654 + } + } + }, + { + Id = 721949523193525565, + NameComponent = { + Name = "Sphere" + }, + TransformComponent = { + Position = { + X = 0, + Y = 0, + Z = -1.47202551 + }, + Rotation = { + X = 0, + Y = 0, + Z = 0, + W = 1 + }, + Scale = { + X = 1, + Y = 1, + Z = 1 + }, + EditorEulerRotation = { + X = 0, + Y = 0, + Z = 0 + } + }, + MeshComponent = { + Mesh = "Models/sphere.glb" + }, + MeshRendererComponent = { + Material = "Textures/TestMaterial.mat" + } + } + ] +} \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/ACES.hlsl b/GlitchyEditor/content/Shaders/ACES.hlsl new file mode 100644 index 0000000..b401567 --- /dev/null +++ b/GlitchyEditor/content/Shaders/ACES.hlsl @@ -0,0 +1,52 @@ +//================================================================================================= +// +// Baking Lab +// by MJP and David Neubelt +// http://mynameismjp.wordpress.com/ +// +// All code licensed under the MIT license +// +//================================================================================================= + +// The code in this file was originally written by Stephen Hill (@self_shadow), who deserves all +// credit for coming up with this fit and implementing it. Buy him a beer next time you see him. :) + +// Source: https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl + +// sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT +static const float3x3 ACESInputMat = +{ + {0.59719, 0.35458, 0.04823}, + {0.07600, 0.90834, 0.01566}, + {0.02840, 0.13383, 0.83777} +}; + +// ODT_SAT => XYZ => D60_2_D65 => sRGB +static const float3x3 ACESOutputMat = +{ + { 1.60475, -0.53108, -0.07367}, + {-0.10208, 1.10813, -0.00605}, + {-0.00327, -0.07276, 1.07602} +}; + +float3 RRTAndODTFit(float3 v) +{ + float3 a = v * (v + 0.0245786f) - 0.000090537f; + float3 b = v * (0.983729f * v + 0.4329510f) + 0.238081f; + return a / b; +} + +float3 ACESFitted(float3 color) +{ + color = mul(ACESInputMat, color); + + // Apply RRT and ODT + color = RRTAndODTFit(color); + + color = mul(ACESOutputMat, color); + + // Clamp to [0, 1] + color = saturate(color); + + return color; +} diff --git a/GlitchyEditor/content/Shaders/ACES.hlsl.ass b/GlitchyEditor/content/Shaders/ACES.hlsl.ass new file mode 100644 index 0000000..0dc97c1 --- /dev/null +++ b/GlitchyEditor/content/Shaders/ACES.hlsl.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "EffectAssetLoader", + Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){} +} \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/ClearUInt.hlsl b/GlitchyEditor/content/Shaders/ClearUInt.hlsl new file mode 100644 index 0000000..cfe67bf --- /dev/null +++ b/GlitchyEditor/content/Shaders/ClearUInt.hlsl @@ -0,0 +1,16 @@ +cbuffer Constants : register(b0) +{ + uint ClearValue; +} + +float4 VS(float2 input : POSITION) : SV_Position +{ + return float4(input, 0.0f, 1.0f); +} + +uint PS(float4 input : SV_Position) : SV_Target0 +{ + return ClearValue; +} + +#pragma Effect[VS = VS; PS = PS] diff --git a/GlitchyEditor/content/Shaders/ClearUInt.hlsl.ass b/GlitchyEditor/content/Shaders/ClearUInt.hlsl.ass new file mode 100644 index 0000000..0dc97c1 --- /dev/null +++ b/GlitchyEditor/content/Shaders/ClearUInt.hlsl.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "EffectAssetLoader", + Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){} +} \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/GammaCorrect.hlsl b/GlitchyEditor/content/Shaders/GammaCorrect.hlsl new file mode 100644 index 0000000..7c19dfd --- /dev/null +++ b/GlitchyEditor/content/Shaders/GammaCorrect.hlsl @@ -0,0 +1,33 @@ +Texture2D Texture : register(t0); +SamplerState TextureSampler : register(s0); + +struct VS_IN +{ + float2 Position : POSITION; + float2 TexCoord : TEXCOORD0; +}; + +struct PS_IN +{ + float4 Position : SV_POSITION; + float2 TexCoord : TEXCOORD; +}; + +PS_IN VS(VS_IN input) +{ + PS_IN output; + + output.Position = float4(input.Position, 0, 1); + output.TexCoord = input.TexCoord; + + return output; +} + +float4 PS(PS_IN input) : SV_TARGET +{ + float4 color = Texture.Sample(TextureSampler, input.TexCoord); + + return float4(pow(color.rgb, 1.0f / 2.2f), color.a); +} + +#pragma Effect[VS = VS; PS = PS] diff --git a/GlitchyEditor/content/Shaders/GammaCorrect.hlsl.ass b/GlitchyEditor/content/Shaders/GammaCorrect.hlsl.ass new file mode 100644 index 0000000..0dc97c1 --- /dev/null +++ b/GlitchyEditor/content/Shaders/GammaCorrect.hlsl.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "EffectAssetLoader", + Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){} +} \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/LightingFunctions.hlsl b/GlitchyEditor/content/Shaders/LightingFunctions.hlsl new file mode 100644 index 0000000..fcd5b5b --- /dev/null +++ b/GlitchyEditor/content/Shaders/LightingFunctions.hlsl @@ -0,0 +1,28 @@ +#define PI 3.14159265358979323846f + +/** + * Calculates the diffuse lighting of a lambertian surface + * @param diffuseColor (rho/ pi) * C_diffuse + * @param illuminanceColor The product of the "brightness" and the light color. + * @param n_dot_l The dot product of the surface normal and the light direction. + */ +float3 CalculateDiffuseReflection(float3 diffuseColor, float3 illuminanceColor, float3 n_dot_l) +{ + float3 directColor = illuminanceColor * saturate(n_dot_l); + + return (directColor * diffuseColor); +} + +/** + * Calculates the blinn-phong-specular reflection + * @param n The normalized surface normal + * @param h The normalized half way vector (nrm(l + v)) + * @param alpha The reflections alpha-value + * @param illuminanceColor The product of the "brightness" and the light color. + * @param n_dot_l The dot product of the surface normal and the light direction. + */ +float3 CalculateSpecularReflection(float3 n, float3 h, float alpha, float3 illuminanceColor, float n_dot_l) +{ + float highlight = pow(saturate(dot(n, h)), alpha) * float(n_dot_l > 0.0); + return (illuminanceColor * highlight); // Todo: * SpecularColor +} \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/LightingFunctions.hlsl.ass b/GlitchyEditor/content/Shaders/LightingFunctions.hlsl.ass new file mode 100644 index 0000000..0dc97c1 --- /dev/null +++ b/GlitchyEditor/content/Shaders/LightingFunctions.hlsl.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "EffectAssetLoader", + Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){} +} \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/PBR.hlsl b/GlitchyEditor/content/Shaders/PBR.hlsl new file mode 100644 index 0000000..73ca42e --- /dev/null +++ b/GlitchyEditor/content/Shaders/PBR.hlsl @@ -0,0 +1,78 @@ +/* + * This File contains Function for PBR. + */ + +#ifndef __PBR_HLSL__ +#define __PBR_HLSL__ + +#include "ShaderHelpers.hlsl" + +// #define PBR_IBL + + /** + * Normal Distribution Function. (Trowbridge-Reits GGX) + * Calculates the relative surface area of microfacets exactly aligned to the halfway vector. + * @param normal The surface normal. + * @param halfway The halfway vector between the surface normal and the view direction. + * @param roughness Roughness value. + * @returns The relative surface area of microfacets exactly aligned to the halfway vector. + */ +float NormalDistributionGGX(float3 normal, float3 halfway, float roughness) +{ + // Square roughness because it looks better + float a = roughness * roughness; + float aa = a * a; + + float n_dot_h = max(dot(normal, halfway), 0.0f); + + float denom = (n_dot_h * n_dot_h) * (aa - 1.0f) + 1.0f; + denom = PI * denom * denom; + + return aa / denom; +} + +/** + * Geometry Function calculating the overshadowing of microfacets based on roughness. (Schlick-Beckmann GGX). + * @param dot-product of normal vector and vector from surface to camera. + * @param k Roughness value. + */ +float GeometrySchlickGGX(float n_dot_v, float k) +{ + return n_dot_v / (n_dot_v * (1 - k) + k); +} + +/** + * Geometry Function calculating the overshadowing of microfacets based on roughness. (Smith) + * @param normal The surface normal. + * @param viewDir Vector from surface to viewer. + * @param lightDir Vector from surface to light source. + * @param roughness Roughness value. + */ +float GeometrySmith(float3 normal, float3 viewDir, float3 lightDir, float roughness) +{ +#ifdef PBR_IBL + // IBL + float k = roughness * roughness / 2; +#else + // Direct lighting + float k = (roughness + 1.0f); + k = (k * k) / 8; +#endif + + const float n_dot_v = max(dot(normal, viewDir), 0.0f); + float n_dot_l = max(dot(normal, lightDir), 0.0f); + + return GeometrySchlickGGX(n_dot_v, k) * GeometrySchlickGGX(n_dot_l, k); +} + +/** + * Calculates the fresnel value. + * @param n_dot_v Dot product of the normal and view direction + * @param F0 base reflectivity. + */ +float3 FresnelSchlick(float n_dot_v, float3 F0) +{ + return F0 + (1.0 - F0) * pow(clamp(1.0 - n_dot_v, 0.0, 1.0), 5.0); +} + +#endif // __PBR_HLSL__ diff --git a/GlitchyEditor/content/Shaders/PBR.hlsl.ass b/GlitchyEditor/content/Shaders/PBR.hlsl.ass new file mode 100644 index 0000000..0dc97c1 --- /dev/null +++ b/GlitchyEditor/content/Shaders/PBR.hlsl.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "EffectAssetLoader", + Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){} +} \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/ShaderHelpers.hlsl b/GlitchyEditor/content/Shaders/ShaderHelpers.hlsl new file mode 100644 index 0000000..e47cf55 --- /dev/null +++ b/GlitchyEditor/content/Shaders/ShaderHelpers.hlsl @@ -0,0 +1,90 @@ +#ifndef __SHADER_HELPERS_HLSL__ +#define __SHADER_HELPERS_HLSL__ + +#define PI 3.14159265358979323846f + +/* +* Calculates the weighted sum of two normal vectors. +* nrm1: The first normal vector +* nrm2: The second normal vector +* a: The weight factor for nrm1 +* b: The weight factor for nrm2 +*/ +float3 BlendNormals(float3 nrm1, float3 nrm2, float a, float b) +{ + return normalize(float3(a * nrm1.x / nrm1.z + b * nrm2.x / nrm2.z, + a * nrm1.y / nrm1.z + b * nrm2.y / nrm2.z, + 1.0f)); +} + +/* +* Scales a normal vector by a factor where 0 results in the vector (0, 0, 1) +* nrm: The normal vector +* a: The scaling factor +*/ +float3 ScaleNormal(float3 nrm1, float a) +{ + return normalize(float3(a * nrm1.x / nrm1.z, + a * nrm1.y / nrm1.z, + 1.0f)); +} + +/* +* Scales a normal vector by a factor where 0 results in the vector (0, 0, 1) +* nrm: The normal vector +* a: The scaling factor +*/ +float3 ScaleNormal(float3 nrm1, float2 a) +{ + return normalize(float3(a.x * nrm1.x / nrm1.z, + a.y * nrm1.y / nrm1.z, + 1.0f)); +} + +/* +* Reconstructs the z-component of a normalized normal vector from a two-component value +* cnrm: The x- and y-components of a normalized normal vector +*/ +float3 DecompressNormal(float2 cnrm) +{ + return float3(cnrm, sqrt(1.0 - cnrm.x * cnrm.x - cnrm.y * cnrm.y)); +} + +/* +* Reconstructs the tangent space from a normal and a tangent +* normal: The surface normal +* tangent: The surface tangent +* sigma: Defines the handedness of the tangent space matrix. 1.0 if it is right handend. -1.0 if it is left handed +*/ +float3x3 ConstructTangentSpace(float3 normal, float3 tangent, float3 sigma) +{ + float3 n = normalize(normal); + float3 t = normalize(tangent - n * dot(tangent, n)); + float3 b = cross(n, t) * sigma; + + return float3x3(t, b, n); +} + +/** + * Calculates the luminance of an rgb-value. + * @param rgb The rgb color. + * @return The luminance of the given rgb color. + */ +float ColorToLuminance(float3 rgb) +{ + return rgb.r * 0.212639 + rgb.g * 0.715169 + rgb.b * 0.072192; +} + +/** + * Extrancts the handedness of the bitangent that is encoded in the z-component of the tangent. + * @param tangentz The z-component of the tangent with the handedness encoded. + * @return The handedness of the bitangent (bitangent = handedness * tangent x normal) + */ +float GetBitangentHandedness(float tangentz) +{ + // handedness is in least significant bit of tangent.z + uint z = asuint(tangentz); + return (z & 1) > 0 ? 1.0 : -1.0; +} + +#endif // __SHADER_HELPERS_HLSL__ \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/ShaderHelpers.hlsl.ass b/GlitchyEditor/content/Shaders/ShaderHelpers.hlsl.ass new file mode 100644 index 0000000..0dc97c1 --- /dev/null +++ b/GlitchyEditor/content/Shaders/ShaderHelpers.hlsl.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "EffectAssetLoader", + Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){} +} \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/SimpleTonemapping.hlsl b/GlitchyEditor/content/Shaders/SimpleTonemapping.hlsl new file mode 100644 index 0000000..82434e4 --- /dev/null +++ b/GlitchyEditor/content/Shaders/SimpleTonemapping.hlsl @@ -0,0 +1,39 @@ +#include "ACES.hlsl" + +Texture2D CameraTarget : register(t0); +SamplerState CameraTargetSampler : register(s0); + +struct VS_IN +{ + float2 Position : POSITION; + float2 TexCoord : TEXCOORD0; +}; + +struct PS_IN +{ + float4 Position : SV_POSITION; + float2 TexCoord : TEXCOORD; +}; + +PS_IN VS(VS_IN input) +{ + PS_IN output; + + output.Position = float4(input.Position, 0, 1); + output.TexCoord = input.TexCoord; + + return output; +} + +float4 PS(PS_IN input) : SV_TARGET +{ + float4 rawColor = CameraTarget.Sample(CameraTargetSampler, input.TexCoord); + + // float3 color = rawColor.rgb / (rawColor.rgb + 1.0f); + + float3 color = ACESFitted(rawColor.rgb); + + return float4(color, 1); +} + +#pragma Effect[VS = VS; PS = PS] diff --git a/GlitchyEditor/content/Shaders/SimpleTonemapping.hlsl.ass b/GlitchyEditor/content/Shaders/SimpleTonemapping.hlsl.ass new file mode 100644 index 0000000..0dc97c1 --- /dev/null +++ b/GlitchyEditor/content/Shaders/SimpleTonemapping.hlsl.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "EffectAssetLoader", + Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){} +} \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/circlebatch.hlsl b/GlitchyEditor/content/Shaders/circlebatch.hlsl index d05cae5..206adc7 100644 --- a/GlitchyEditor/content/Shaders/circlebatch.hlsl +++ b/GlitchyEditor/content/Shaders/circlebatch.hlsl @@ -1,3 +1,5 @@ +#define EDITOR + Texture2D Texture : register(t0); SamplerState Sampler : register(s0); @@ -14,6 +16,9 @@ struct VS_Input float4 Color : COLOR; float4 UVTransform : TEXCOORD1; float InnerRadius : TEXCOORD2; +#ifdef EDITOR + uint EntityId : ENTITYID; +#endif }; struct PS_Input @@ -22,6 +27,9 @@ struct PS_Input float2 RawPos : TEXCOORD0; float2 Texcoord : TEXCOORD1; float4 Color : COLOR; +#ifdef EDITOR + nointerpolation uint EntityId : ENTITYID; +#endif float InnerRadius : TEXCOORD2; }; @@ -35,11 +43,25 @@ PS_Input VS(VS_Input input) output.Color = input.Color; output.InnerRadius = input.InnerRadius; +#ifdef EDITOR + output.EntityId = input.EntityId; +#endif + return output; } -float4 PS(PS_Input input) : SV_Target0 +struct PS_Output { + float4 Color : SV_Target0; +#ifdef EDITOR + uint EntityId : SV_TARGET1; +#endif +}; + +PS_Output PS(PS_Input input) +{ + PS_Output output; + float2 uv = input.RawPos * 2; float distance = 1.0f - length(uv); @@ -53,10 +75,14 @@ float4 PS(PS_Input input) : SV_Target0 // Discard invisible pixels clip(amount - 0.5f); - float4 color = Texture.Sample(Sampler, input.Texcoord) * input.Color; - color.a *= amount; + output.Color = Texture.Sample(Sampler, input.Texcoord) * input.Color; + output.Color.a *= amount; - return color; +#ifdef EDITOR + output.EntityId = input.EntityId; +#endif + + return output; } -#effect[VS=VS, PS=PS] \ No newline at end of file +#pragma Effect[VS=VS; PS=PS] \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/circlebatch.hlsl.ass b/GlitchyEditor/content/Shaders/circlebatch.hlsl.ass new file mode 100644 index 0000000..0dc97c1 --- /dev/null +++ b/GlitchyEditor/content/Shaders/circlebatch.hlsl.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "EffectAssetLoader", + Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){} +} \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/lineShader.hlsl b/GlitchyEditor/content/Shaders/lineShader.hlsl index 1353f92..8ab4906 100644 --- a/GlitchyEditor/content/Shaders/lineShader.hlsl +++ b/GlitchyEditor/content/Shaders/lineShader.hlsl @@ -28,4 +28,4 @@ float4 PS(PS_Input input) : SV_Target0 return Color; } -#effect[VS=VS, PS=PS] +#pragma Effect[VS=VS; PS=PS] diff --git a/GlitchyEditor/content/Shaders/lineShader.hlsl.ass b/GlitchyEditor/content/Shaders/lineShader.hlsl.ass new file mode 100644 index 0000000..0dc97c1 --- /dev/null +++ b/GlitchyEditor/content/Shaders/lineShader.hlsl.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "EffectAssetLoader", + Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){} +} \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/linebatch.hlsl b/GlitchyEditor/content/Shaders/linebatch.hlsl new file mode 100644 index 0000000..272d90b --- /dev/null +++ b/GlitchyEditor/content/Shaders/linebatch.hlsl @@ -0,0 +1,61 @@ +#define EDITOR + +cbuffer Constants : register(b0) +{ + float4x4 ViewProjection; +}; + +struct VS_Input +{ + float4 Position : POSITION; + float4 Color : COLOR; +#ifdef EDITOR + uint EntityId : ENTITYID; +#endif +}; + +typedef struct PS_Input +{ + float4 Position : SV_Position; + float4 Color : COLOR; +#ifdef EDITOR + nointerpolation uint EntityId : ENTITYID; +#endif +} VS_Output; + +VS_Output VS(VS_Input input) +{ + VS_Output output; + + output.Position = mul(ViewProjection, input.Position); + output.Color = input.Color; + +#ifdef EDITOR + output.EntityId = input.EntityId; +#endif + + return output; +} + +struct PS_Output +{ + float4 Color : SV_TARGET0; +#ifdef EDITOR + uint EntityId : SV_TARGET1; +#endif +}; + +PS_Output PS(PS_Input input) +{ + PS_Output output; + + output.Color = input.Color; + +#ifdef EDITOR + output.EntityId = input.EntityId; +#endif + + return output; +} + +#pragma Effect[VS=VS; PS=PS] \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/linebatch.hlsl.ass b/GlitchyEditor/content/Shaders/linebatch.hlsl.ass new file mode 100644 index 0000000..ccd76fa --- /dev/null +++ b/GlitchyEditor/content/Shaders/linebatch.hlsl.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "EffectAssetLoader", + Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */ +} \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/msdfShader.hlsl b/GlitchyEditor/content/Shaders/msdfShader.hlsl index 015213e..69207c4 100644 --- a/GlitchyEditor/content/Shaders/msdfShader.hlsl +++ b/GlitchyEditor/content/Shaders/msdfShader.hlsl @@ -90,4 +90,4 @@ float4 PS(PS_Input input) : SV_Target0 } */ -#effect[VS=VS, PS=PS] \ No newline at end of file +#pragma Effect[VS=VS; PS=PS] \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/msdfShader.hlsl.ass b/GlitchyEditor/content/Shaders/msdfShader.hlsl.ass new file mode 100644 index 0000000..0dc97c1 --- /dev/null +++ b/GlitchyEditor/content/Shaders/msdfShader.hlsl.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "EffectAssetLoader", + Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){} +} \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/myEffect.hlsl b/GlitchyEditor/content/Shaders/myEffect.hlsl new file mode 100644 index 0000000..afa9e70 --- /dev/null +++ b/GlitchyEditor/content/Shaders/myEffect.hlsl @@ -0,0 +1,159 @@ +#define OutputEntityId + +#include "ShaderHelpers.hlsl" + +Texture2D AlbedoTexture : register(t0); +SamplerState AlbedoSampler : register(s0); + +Texture2D NormalTexture : register(t1); +SamplerState NormalSampler : register(s1); + +Texture2D MetallicTexture : register(t2); +SamplerState MetallicSampler : register(s2); + +Texture2D RoughnessTexture : register(t3); +SamplerState RoughnessSampler : register(s3); + +// Texture2D AmbientTexture : register(t4); +// SamplerState AmbientSampler : register(s4); + +#pragma EngineBuffer[ Name = "SceneConstants"; Binding = "Scene" ] +cbuffer SceneConstants : register(b0) +{ + float4x4 ViewProjection; +} + +#pragma EngineBuffer[ Name = "ObjectConstants"; Binding = "Object" ] +cbuffer ObjectConstants : register(b1) +{ + float4x4 Transform; + /** + * \brief Inverted and transposed transform matrix. + * \remarks This matrix is used in order to correctly transform normal vectors. + */ + float4x3 Transform_InvT; +#ifdef OutputEntityId + uint EntityId; +#endif +} + +cbuffer MaterialConstants : register(b2) +{ + #pragma EditorVariable[ Name = "AlbedoColor"; Preview = "Albedo Color"; Type="Color" ] + float4 AlbedoColor = float4(1.0, 1.0, 1.0, 1.0); + #pragma EditorVariable[ Name = "NormalScaling"; Preview = "Normal Scaling" ] + float2 NormalScaling = float2(1.0, 1.0); + #pragma EditorVariable[ Name = "MetallicFactor"; Preview = "Metallic Factor"; Min = 0.0f; Max = 1.0f ] + float MetallicFactor = 1.0; + #pragma EditorVariable[ Name = "RoughnessFactor"; Preview = "Rougness Factor"; Min = 0.0f; Max = 1.0f ] + float RoughnessFactor = 1.0; + // float AmbientFactor = 1.0; +} + +struct VS_IN +{ + float3 Position : POSITION; + float3 Normal : NORMAL; + // Todo: Tangent.w... handedness + float3 Tangent : TANGENT; + float2 TexCoord : TEXCOORD; +}; + +struct PS_IN +{ + float4 Position : SV_POSITION; + float3 WorldPosition : WORLDPOSITION; + float3 Normal : NORMAL; + float3 Tangent : TANGENT; + float2 TexCoord : TEXCOORD; + //nointerpolation float Handedness : HANDEDNESS; +#ifdef OutputEntityId + nointerpolation uint EntityId : ENTITYID; +#endif +}; + +PS_IN VS(VS_IN input) +{ + PS_IN output; + + float4 worldPosition = mul(Transform, float4(input.Position, 1)); + + output.Position = mul(ViewProjection, worldPosition); + output.WorldPosition = worldPosition.xyz / worldPosition.w; + + output.Normal = mul(Transform_InvT, input.Normal); + output.Tangent = mul((float3x3)Transform, input.Tangent); + // TODO: output.Handedness = input.Tangent.w + + output.TexCoord = input.TexCoord; + + output.EntityId = EntityId; + + return output; +} + +struct PS_OUT +{ + float4 Albedo : SV_TARGET0; + // RG: TextureNormal.XY BA: GeoNrm.XY + float4 Normal : SV_TARGET1; + // R: GeoNrm.Z GBA: GeoTan.XYZ + float4 Tangent : SV_TARGET2; + float4 Position : SV_TARGET3; + // R: Metallicity G: Roughness B: Ambient + float4 Material : SV_TARGET4; +#ifdef OutputEntityId + uint EntityId : SV_TARGET5; +#endif +}; + +PS_OUT PS(PS_IN input) +{ + // Build tangent space + float3 normal = normalize(input.Normal); + float3 tangent = normalize(input.Tangent - dot(input.Tangent, normal) * input.Normal); + // TODO: float3 bitangent = input.Handedness * cross(normal, tangent); + float3 bitangent = -cross(normal, tangent); + + //float3x3 tangentTransform = float3x3(tangent, bitangent, normal); + //tangentTransform = transpose(tangentTransform); + + float4 texAlbedo = AlbedoTexture.Sample(AlbedoSampler, input.TexCoord); + float3 texNormal = NormalTexture.Sample(NormalSampler, input.TexCoord); + texNormal.xy = texNormal.xy * 2.0 - 1.0; + float texMetallic = MetallicTexture.Sample(MetallicSampler, input.TexCoord); + float texRoughness = RoughnessTexture.Sample(RoughnessSampler, input.TexCoord); + + //float3 objectNormal = mul(tangentTransform, texNormal); + //float3 worldNormal = mul(objectNormal, (float3x3)Transform); + + float4 finalAlbedo = texAlbedo * AlbedoColor; + float3 finalNormal = ScaleNormal(texNormal, NormalScaling); + float finalMetallic = texMetallic * MetallicFactor; + float finalRoughness = texRoughness * RoughnessFactor; + +/////////////TODO: REMOVEME + + //worldNormal = max(worldNormal - 10000000, normal); + //texAlbedo = max(texAlbedo - 10000000, 1.0); + //texMetallic = max(texMetallic - 10000000, 0.0); + //texRoughness = max(texRoughness - 10000000, 0.1); + +/////////////TODO: END_REMOVEME + + PS_OUT output; + output.Albedo = finalAlbedo; + //output.Normal = float4(objectNormal, 1.0); + output.Normal = float4(finalNormal.xy, normal.xy); + output.Tangent = float4(normal.z, tangent.xyz); + output.Position = float4(input.WorldPosition, 1.0); + output.Material = float4(finalMetallic, finalRoughness, 1.0, 0); + +#ifdef OutputEntityId + output.EntityId = input.EntityId; +#endif + + return output; +} + +#pragma Effect[VS = VS; PS = PS] diff --git a/GlitchyEditor/content/Shaders/myEffect.hlsl.ass b/GlitchyEditor/content/Shaders/myEffect.hlsl.ass new file mode 100644 index 0000000..0dc97c1 --- /dev/null +++ b/GlitchyEditor/content/Shaders/myEffect.hlsl.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "EffectAssetLoader", + Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){} +} \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/simpleLight.hlsl b/GlitchyEditor/content/Shaders/simpleLight.hlsl new file mode 100644 index 0000000..687a1d1 --- /dev/null +++ b/GlitchyEditor/content/Shaders/simpleLight.hlsl @@ -0,0 +1,126 @@ +#include "ShaderHelpers.hlsl" +#include "PBR.hlsl" + +#define Render 0 +#define Inspect_NormalDistribution 1 +#define Inspect_GeometryFunction 2 +#define Inspect_Fresnel 3 +#define Inspect_Normal 4 + +#define OUTPUT Render + +SamplerState Sampler : register(s0); + +Texture2D GBuffer_Albedo : register(t0); +Texture2D GBuffer_Normal : register(t1); +Texture2D GBuffer_Tangent : register(t2); +Texture2D GBuffer_Position : register(t3); +Texture2D GBuffer_Material : register(t4); + +cbuffer Constants +{ + float3 CameraPos; + float2 Scaling; +} + +cbuffer LightConstants +{ + float3 LightColor; + float Illuminance; + float3 LightDir; +} + +struct VS_IN +{ + float2 Position : POSITION; + float2 TexCoord : TEXCOORD0; +}; + +struct PS_IN +{ + float4 Position : SV_POSITION; + float2 TexCoord : TEXCOORD; +}; + +PS_IN VS(VS_IN input) +{ + PS_IN output; + + output.Position = float4(input.Position, 0, 1); + output.TexCoord = input.TexCoord * Scaling; + + return output; +} + +float4 PS(PS_IN input) : SV_TARGET +{ + // Load Data from GBuffer + float4 rawAlbedo = GBuffer_Albedo.Sample(Sampler, input.TexCoord); + float4 rawNormal = GBuffer_Normal.Sample(Sampler, input.TexCoord); + float4 rawTangent = GBuffer_Tangent.Sample(Sampler, input.TexCoord); + float4 rawPosition = GBuffer_Position.Sample(Sampler, input.TexCoord); + float4 rawMaterial = GBuffer_Material.Sample(Sampler, input.TexCoord); + + // Extract data from GBuffer + float3 albedo = rawAlbedo.rgb; + //float3 surfaceNormal = normalize(rawNormal.xyz); + float3 worldPosition = rawPosition.xyz; + + float metallic = rawMaterial.r; + float roughness = rawMaterial.g; + + float3 textureNormal = DecompressNormal(rawNormal.rg); + float3 rawGeoNrm = float3(rawNormal.ba, rawTangent.r); + float3 rawGeoTan = rawTangent.gba; + + // Reconstruct normal space + float3 normal = normalize(rawGeoNrm); + float3 tangent = normalize(rawGeoTan - dot(rawGeoTan, normal) * normal); + float3 bitangent = -cross(normal, tangent); + + float3x3 tangentTransform = float3x3(tangent, bitangent, normal); + + float3 surfaceNormal = mul(textureNormal, tangentTransform); + + float3 lightDir = normalize(LightDir); + float3 viewDir = normalize(CameraPos - worldPosition.xyz); + float3 halfway = normalize(lightDir + viewDir); + + float n_dot_v = max(dot(surfaceNormal, viewDir), 0.0f); + float n_dot_h = max(dot(surfaceNormal, halfway), 0.0f); + float n_dot_l = max(dot(surfaceNormal, lightDir), 0.0f); + + float nrmDist = NormalDistributionGGX(surfaceNormal, halfway, roughness); + float geo = GeometrySmith(surfaceNormal, viewDir, lightDir, roughness); + + float3 F0 = 0.04f; + F0 = lerp(F0, albedo, metallic); + float3 fresnel = FresnelSchlick(n_dot_h, F0); + + float3 ks = fresnel; + float3 kd = 1.0f - ks; + + // Metals have no diffuse light + kd *= 1.0f - metallic; + + float3 diffuse = albedo / PI; + float3 specular = (nrmDist * fresnel * geo) / max(4 * n_dot_v * n_dot_l, 0.0001f); + + float3 luminanceColor = LightColor * Illuminance; + + float3 final = (kd * diffuse + specular) * luminanceColor * n_dot_l; + +#if OUTPUT == Inspect_NormalDistribution + final = max(final - 10000000, nrmDist.xxx); +#elif OUTPUT == Inspect_GeometryFunction + final = max(final - 10000000, geo.xxx); +#elif OUTPUT == Inspect_Fresnel + final = max(final - 10000000, fresnel); +#elif OUTPUT == Inspect_Normal + final = max(final - 10000000, surfaceNormal / 2 + 0.5f); +#endif + + return float4(final, 1); +} + +#pragma Effect[VS = VS; PS = PS] diff --git a/GlitchyEditor/content/Shaders/simpleLight.hlsl.ass b/GlitchyEditor/content/Shaders/simpleLight.hlsl.ass new file mode 100644 index 0000000..0dc97c1 --- /dev/null +++ b/GlitchyEditor/content/Shaders/simpleLight.hlsl.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "EffectAssetLoader", + Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){} +} \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/spritebatch.hlsl b/GlitchyEditor/content/Shaders/spritebatch.hlsl index be544ec..dd15a2b 100644 --- a/GlitchyEditor/content/Shaders/spritebatch.hlsl +++ b/GlitchyEditor/content/Shaders/spritebatch.hlsl @@ -1,3 +1,5 @@ +#define EDITOR + Texture2D Texture : register(t0); SamplerState Sampler : register(s0); @@ -13,6 +15,9 @@ struct VS_Input float4x4 Transform : TRANSFORM; float4 Color : COLOR; float4 UVTransform : TEXCOORD1; +#ifdef EDITOR + uint EntityId : ENTITYID; +#endif }; struct PS_Input @@ -20,6 +25,9 @@ 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) @@ -30,12 +38,37 @@ PS_Input VS(VS_Input input) output.Texcoord = input.UVTransform.xy + input.UVTransform.zw * input.Texcoord; output.Color = input.Color; + // Premultiply Alpha + output.Color.rgb *= output.Color.a; + +#ifdef EDITOR + output.EntityId = input.EntityId; +#endif + return output; } -float4 PS(PS_Input input) : SV_Target0 +struct PS_Output { - return Texture.Sample(Sampler, input.Texcoord) * input.Color; + float4 Color : SV_Target0; +#ifdef EDITOR + uint EntityId : SV_TARGET1; +#endif +}; + +PS_Output PS(PS_Input input) +{ + PS_Output output; + + output.Color = Texture.Sample(Sampler, input.Texcoord) * input.Color; + + clip(output.Color.a - 0.001f); + +#ifdef EDITOR + output.EntityId = input.EntityId; +#endif + + return output; } -#effect[VS=VS, PS=PS] \ No newline at end of file +#pragma Effect[VS = VS; PS = PS] diff --git a/GlitchyEditor/content/Shaders/spritebatch.hlsl.ass b/GlitchyEditor/content/Shaders/spritebatch.hlsl.ass new file mode 100644 index 0000000..0dc97c1 --- /dev/null +++ b/GlitchyEditor/content/Shaders/spritebatch.hlsl.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "EffectAssetLoader", + Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){} +} \ No newline at end of file diff --git a/GlitchyEditor/content/Shaders/textureViewerShader.hlsl b/GlitchyEditor/content/Shaders/textureViewerShader.hlsl new file mode 100644 index 0000000..b1d0cf2 --- /dev/null +++ b/GlitchyEditor/content/Shaders/textureViewerShader.hlsl @@ -0,0 +1,52 @@ +Texture2D Texture : register(t0); +SamplerState Sampler : register(s0); + +cbuffer Constants +{ + float4x4 ViewProjection; + + float ColorOffset = 0.5f; + float AlphaOffset = 0.0f; + float ColorScale = 0.5f; + float AlphaScale = 1.0f; + + float2 TextureSizeInPixels; +} + +struct VS_Input +{ + float2 Position : POSITION; + float2 Texcoord : TEXCOORD0; + float4x4 Transform : 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.Transform, float4(input.Position, 0.0f, 1.0f))); + output.Texcoord = input.UVTransform.xy + input.UVTransform.zw * input.Texcoord; + output.Color = input.Color; + + return output; +} + +float4 PS(PS_Input input) : SV_Target0 +{ + float4 color = Texture.Sample(Sampler, input.Texcoord); + + float4 final = float4(ColorOffset.xxx, AlphaOffset) + color * float4(ColorScale.xxx, AlphaScale); + + return final; +} + +#pragma Effect[VS=VS; PS=PS] diff --git a/GlitchyEditor/content/Shaders/textureViewerShader.hlsl.ass b/GlitchyEditor/content/Shaders/textureViewerShader.hlsl.ass new file mode 100644 index 0000000..0dc97c1 --- /dev/null +++ b/GlitchyEditor/content/Shaders/textureViewerShader.hlsl.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "EffectAssetLoader", + Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){} +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/DefaultNormal.png b/GlitchyEditor/content/Textures/DefaultNormal.png new file mode 100644 index 0000000..42ef004 Binary files /dev/null and b/GlitchyEditor/content/Textures/DefaultNormal.png differ diff --git a/GlitchyEditor/content/Textures/DefaultNormal.png.ass b/GlitchyEditor/content/Textures/DefaultNormal.png.ass new file mode 100644 index 0000000..1f70168 --- /dev/null +++ b/GlitchyEditor/content/Textures/DefaultNormal.png.ass @@ -0,0 +1,23 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Linear, + MipFilter = .Linear, + ComparisonFunction = .Never, + AddressModeU = .Clamp, + AddressModeV = .Clamp, + AddressModeW = .Clamp, + MipMinLOD = -340282346638528859811704183484516925440, + MipMaxLOD = 340282346638528859811704183484516925440, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/EditorIcons.dds b/GlitchyEditor/content/Textures/EditorIcons.dds new file mode 100644 index 0000000..5245d70 Binary files /dev/null and b/GlitchyEditor/content/Textures/EditorIcons.dds differ diff --git a/GlitchyEditor/content/Textures/EditorIcons.dds.ass b/GlitchyEditor/content/Textures/EditorIcons.dds.ass new file mode 100644 index 0000000..1f70168 --- /dev/null +++ b/GlitchyEditor/content/Textures/EditorIcons.dds.ass @@ -0,0 +1,23 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Linear, + MipFilter = .Linear, + ComparisonFunction = .Never, + AddressModeU = .Clamp, + AddressModeV = .Clamp, + AddressModeW = .Clamp, + MipMinLOD = -340282346638528859811704183484516925440, + MipMaxLOD = 340282346638528859811704183484516925440, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/EditorIcons.psd b/GlitchyEditor/content/Textures/EditorIcons.psd new file mode 100644 index 0000000..c39d285 Binary files /dev/null and b/GlitchyEditor/content/Textures/EditorIcons.psd differ diff --git a/GlitchyEditor/content/Textures/RocketMaterial.mat b/GlitchyEditor/content/Textures/RocketMaterial.mat new file mode 100644 index 0000000..52eb50f --- /dev/null +++ b/GlitchyEditor/content/Textures/RocketMaterial.mat @@ -0,0 +1,31 @@ +{ + Effect = "Shaders/myEffect.hlsl", + Textures = [ + "AlbedoTexture": "Textures/rocket.png", + "NormalTexture": "Textures/TestMat/rustediron2_normal.png", + "MetallicTexture": "Textures/TestMat/rustediron2_metallic.png", + "RoughnessTexture": "Textures/TestMat/rustediron2_roughness.png" + ], + Variables = [ + "AlbedoColor": .ColorRGBA{ + Value = { + R = 1, + G = 1, + B = 1, + A = 1 + } + }, + "NormalScaling": .Float2{ + Value = { + X = 1, + Y = 1 + } + }, + "MetallicFactor": .Float{ + Value = 0 + }, + "RoughnessFactor": .Float{ + Value = 1 + } + ] +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/RocketMaterial.mat.ass b/GlitchyEditor/content/Textures/RocketMaterial.mat.ass new file mode 100644 index 0000000..4eb3f21 --- /dev/null +++ b/GlitchyEditor/content/Textures/RocketMaterial.mat.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "MaterialAssetLoader", + Config = (GlitchyEditor.Assets.ModelAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.ModelAssetLoaderConfig. Add [BonTarget] or force it */ +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/TestMat/nrm.png b/GlitchyEditor/content/Textures/TestMat/nrm.png new file mode 100644 index 0000000..e65ca29 Binary files /dev/null and b/GlitchyEditor/content/Textures/TestMat/nrm.png differ diff --git a/GlitchyEditor/content/Textures/TestMat/nrm.png.ass b/GlitchyEditor/content/Textures/TestMat/nrm.png.ass new file mode 100644 index 0000000..1f70168 --- /dev/null +++ b/GlitchyEditor/content/Textures/TestMat/nrm.png.ass @@ -0,0 +1,23 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Linear, + MipFilter = .Linear, + ComparisonFunction = .Never, + AddressModeU = .Clamp, + AddressModeV = .Clamp, + AddressModeW = .Clamp, + MipMinLOD = -340282346638528859811704183484516925440, + MipMaxLOD = 340282346638528859811704183484516925440, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/TestMat/rustediron2_albedo.png b/GlitchyEditor/content/Textures/TestMat/rustediron2_albedo.png new file mode 100644 index 0000000..91896ad Binary files /dev/null and b/GlitchyEditor/content/Textures/TestMat/rustediron2_albedo.png differ diff --git a/GlitchyEditor/content/Textures/TestMat/rustediron2_albedo.png.ass b/GlitchyEditor/content/Textures/TestMat/rustediron2_albedo.png.ass new file mode 100644 index 0000000..f0b6937 --- /dev/null +++ b/GlitchyEditor/content/Textures/TestMat/rustediron2_albedo.png.ass @@ -0,0 +1,27 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _generateMipMaps = true, + _isSrgb = true, + _samplerStateDescription = { + MinFilter = .Anisotropic, + MagFilter = .Anisotropic, + MipFilter = .Anisotropic, + FilterMode = .Default, + ComparisonFunction = .Never, + AddressModeU = .Wrap, + AddressModeV = .Wrap, + AddressModeW = .Wrap, + MipLODBias = 0, + MipMinLOD = 0, + MipMaxLOD = 3, + MaxAnisotropy = 16, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/TestMat/rustediron2_metallic.png b/GlitchyEditor/content/Textures/TestMat/rustediron2_metallic.png new file mode 100644 index 0000000..76ca751 Binary files /dev/null and b/GlitchyEditor/content/Textures/TestMat/rustediron2_metallic.png differ diff --git a/GlitchyEditor/content/Textures/TestMat/rustediron2_metallic.png.ass b/GlitchyEditor/content/Textures/TestMat/rustediron2_metallic.png.ass new file mode 100644 index 0000000..ccdb9a9 --- /dev/null +++ b/GlitchyEditor/content/Textures/TestMat/rustediron2_metallic.png.ass @@ -0,0 +1,27 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _generateMipMaps = false, + _isSrgb = false, + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Linear, + MipFilter = .Linear, + FilterMode = .Default, + ComparisonFunction = .Never, + AddressModeU = .Wrap, + AddressModeV = .Wrap, + AddressModeW = .Clamp, + MipLODBias = 0, + MipMinLOD = -3.40282347e+38, + MipMaxLOD = 3.40282347e+38, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/TestMat/rustediron2_normal.png b/GlitchyEditor/content/Textures/TestMat/rustediron2_normal.png new file mode 100644 index 0000000..cc191ec Binary files /dev/null and b/GlitchyEditor/content/Textures/TestMat/rustediron2_normal.png differ diff --git a/GlitchyEditor/content/Textures/TestMat/rustediron2_normal.png.ass b/GlitchyEditor/content/Textures/TestMat/rustediron2_normal.png.ass new file mode 100644 index 0000000..d366f65 --- /dev/null +++ b/GlitchyEditor/content/Textures/TestMat/rustediron2_normal.png.ass @@ -0,0 +1,27 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _generateMipMaps = false, + _isSrgb = false, + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Linear, + MipFilter = .Linear, + FilterMode = .Default, + ComparisonFunction = .Never, + AddressModeU = .Wrap, + AddressModeV = .Wrap, + AddressModeW = .Clamp, + MipLODBias = 2.5999999, + MipMinLOD = -Infinity, + MipMaxLOD = Infinity, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/TestMat/rustediron2_roughness.png b/GlitchyEditor/content/Textures/TestMat/rustediron2_roughness.png new file mode 100644 index 0000000..c5cecf7 Binary files /dev/null and b/GlitchyEditor/content/Textures/TestMat/rustediron2_roughness.png differ diff --git a/GlitchyEditor/content/Textures/TestMat/rustediron2_roughness.png.ass b/GlitchyEditor/content/Textures/TestMat/rustediron2_roughness.png.ass new file mode 100644 index 0000000..dd43ef6 --- /dev/null +++ b/GlitchyEditor/content/Textures/TestMat/rustediron2_roughness.png.ass @@ -0,0 +1,27 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _generateMipMaps = false, + _isSrgb = true, + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Linear, + MipFilter = .Linear, + FilterMode = .Default, + ComparisonFunction = .Never, + AddressModeU = .Wrap, + AddressModeV = .Wrap, + AddressModeW = .Clamp, + MipLODBias = 0, + MipMinLOD = -3.40282347e+38, + MipMaxLOD = 3.40282347e+38, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/TestMaterial.mat b/GlitchyEditor/content/Textures/TestMaterial.mat new file mode 100644 index 0000000..a8d3b45 --- /dev/null +++ b/GlitchyEditor/content/Textures/TestMaterial.mat @@ -0,0 +1,31 @@ +{ + Effect = "Shaders\\myEffect.hlsl", + Textures = [ + "AlbedoTexture": "Textures\\TestMat\\rustediron2_albedo.png", + "NormalTexture": "Textures\\TestMat\\rustediron2_normal.png", + "MetallicTexture": "Textures\\TestMat\\rustediron2_metallic.png", + "RoughnessTexture": "Textures\\TestMat\\rustediron2_roughness.png" + ], + Variables = [ + "AlbedoColor": .ColorRGBA{ + Value = { + R = 1, + G = 1, + B = 1, + A = 1 + } + }, + "NormalScaling": .Float2{ + Value = { + X = 1, + Y = 1 + } + }, + "MetallicFactor": .Float{ + Value = 1 + }, + "RoughnessFactor": .Float{ + Value = 1 + } + ] +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/TestMaterial.mat.ass b/GlitchyEditor/content/Textures/TestMaterial.mat.ass new file mode 100644 index 0000000..105823c --- /dev/null +++ b/GlitchyEditor/content/Textures/TestMaterial.mat.ass @@ -0,0 +1,4 @@ +{ + AssetLoader = "MaterialAssetLoader", + Config = (GlitchyEditor.Assets.ModelAssetLoaderConfig){} +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/White.png b/GlitchyEditor/content/Textures/White.png new file mode 100644 index 0000000..818c71d Binary files /dev/null and b/GlitchyEditor/content/Textures/White.png differ diff --git a/GlitchyEditor/content/Textures/White.png.ass b/GlitchyEditor/content/Textures/White.png.ass new file mode 100644 index 0000000..1f70168 --- /dev/null +++ b/GlitchyEditor/content/Textures/White.png.ass @@ -0,0 +1,23 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Linear, + MipFilter = .Linear, + ComparisonFunction = .Never, + AddressModeU = .Clamp, + AddressModeV = .Clamp, + AddressModeW = .Clamp, + MipMinLOD = -340282346638528859811704183484516925440, + MipMaxLOD = 340282346638528859811704183484516925440, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/rocket.dds.ass b/GlitchyEditor/content/Textures/rocket.dds.ass new file mode 100644 index 0000000..c350a18 --- /dev/null +++ b/GlitchyEditor/content/Textures/rocket.dds.ass @@ -0,0 +1,24 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _isSrgb = true, + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Linear, + MipFilter = .Linear, + ComparisonFunction = .Never, + AddressModeU = .Clamp, + AddressModeV = .Clamp, + AddressModeW = .Clamp, + MipMinLOD = -3.40282347e+38, + MipMaxLOD = 3.40282347e+38, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/rocket.png.ass b/GlitchyEditor/content/Textures/rocket.png.ass new file mode 100644 index 0000000..ffe1195 --- /dev/null +++ b/GlitchyEditor/content/Textures/rocket.png.ass @@ -0,0 +1,22 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _isSrgb = true, + _samplerStateDescription = { + MinFilter = .Linear, + ComparisonFunction = .Never, + AddressModeU = .Clamp, + AddressModeV = .Clamp, + AddressModeW = .Clamp, + MipMinLOD = -340282346638528859811704183484516925440, + MipMaxLOD = 340282346638528859811704183484516925440, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/src/AssetFile.bf b/GlitchyEditor/src/AssetFile.bf new file mode 100644 index 0000000..75c814e --- /dev/null +++ b/GlitchyEditor/src/AssetFile.bf @@ -0,0 +1,121 @@ +using System; +using GlitchyEngine; +using System.IO; +using Bon; +using GlitchyEngine.Content; + +namespace GlitchyEditor; + +[BonTarget] +class AssetConfig +{ + [BonIgnore] + public bool IgnoreFile = false; + + [BonInclude] + public String AssetLoader ~ delete _; + + [BonInclude] + public AssetLoaderConfig Config ~ delete _; +} + +class AssetFile +{ + private EditorContentManager _contentManager; + + private String _path; + private String _identifier; + private String _assetConfigPath; + + private AssetConfig _assetConfig ~ delete _; + + private bool _isDirectory; + + private Asset _loadedAsset; + + public bool IsDirectory => _isDirectory; + + public StringView FilePath => _path; + public StringView Identifier => _identifier; + + public const String ConfigFileExtension = ".ass"; + + public AssetConfig AssetConfig => _assetConfig; + + public Asset LoadedAsset => _loadedAsset; + + [AllowAppend] + public this(EditorContentManager contentManager, StringView identifier, StringView path, bool isDirectory) + { + String identifierBuffer = append String(identifier); + String pathBuffer = append String(path); + String configPathBuffer = append String(path.Length + ConfigFileExtension.Length); + + _identifier = identifierBuffer; + _path = pathBuffer; + + configPathBuffer..Append(path).Append(ConfigFileExtension); + _assetConfigPath = configPathBuffer; + + _contentManager = contentManager; + + _isDirectory = isDirectory; + + Log.EngineLogger.AssertDebug(File.Exists(_path), "File doesn't exist."); + + FindAssetConfig(); + } + + // Loads the asset config (.ass) file or creates it. + private void FindAssetConfig() + { + if (File.Exists(_assetConfigPath)) + { + LoadAssetConfig(); + } + else + { + CreateDefaultAssetLoader(); + } + } + + private void CreateDefaultAssetLoader() + { + String fileExtension = Path.GetExtension(_path, .. scope .()); + + _assetConfig = new AssetConfig(); + var assetLoader = _contentManager.GetDefaultAssetLoader(fileExtension); + + // We don't have a loader -> we don't need a config + if (assetLoader == null) + return; + + _assetConfig.AssetLoader = new String(); + assetLoader.GetType().GetName(_assetConfig.AssetLoader); + + _assetConfig.Config = assetLoader?.GetDefaultConfig(); + _assetConfig.Config?.[Friend]_changed = true; + + SaveAssetConfig(); + } + + private void LoadAssetConfig() + { + if (Bon.DeserializeFromFile(ref _assetConfig, _assetConfigPath) case .Err) + { + Log.EngineLogger.Error($"Failed to load asset config {_assetConfigPath}"); + + // TODO: Handle failure of asset config loading + Runtime.NotImplemented(); + } + } + + public void SaveAssetConfig() + { + gBonEnv.serializeFlags |= .Verbose; + + Bon.SerializeIntoFile(_assetConfig, _assetConfigPath); + + _assetConfig.Config.[Friend]_changed = false; + } +} \ No newline at end of file diff --git a/GlitchyEditor/src/Assets/AssetHierarchy.bf b/GlitchyEditor/src/Assets/AssetHierarchy.bf new file mode 100644 index 0000000..da8b003 --- /dev/null +++ b/GlitchyEditor/src/Assets/AssetHierarchy.bf @@ -0,0 +1,426 @@ +using GlitchyEngine; +using GlitchyEngine.Collections; +using GlitchyEngine.Renderer; +using System; +using System.Collections; +using System.IO; +using System.Linq; + +namespace GlitchyEditor.Assets; + +public class AssetNode +{ + public String Name ~ delete _; + public String Path ~ delete _; + + public bool IsDirectory; + + public AssetFile AssetFile ~ delete _; + + public List SubAssets ~ { + SubAssets?.ClearAndDeleteItems(); + delete SubAssets; + } + + public Texture2D PreviewImage ~ _?.ReleaseRef(); +} + +public class SubAsset +{ + public AssetNode Asset; + public String Name ~ delete _; + //public String AssetInternalPath ~ delete _; + + public Texture2D PreviewImage ~ _?.ReleaseRef(); +} + +public static class AssetIdentifier +{ + public const char8 DirectorySeparatorChar = '/'; + + public static void Fixup(String assetIdentifier) + { + const String DotSeperator = $".{DirectorySeparatorChar}"; + const String SeperatorDot = $"{DirectorySeparatorChar}."; + + assetIdentifier.Replace('\\', DirectorySeparatorChar); + assetIdentifier.Replace(DotSeperator, ""); + assetIdentifier.Replace(SeperatorDot, ""); + + if (assetIdentifier.StartsWith(DirectorySeparatorChar)) + assetIdentifier.Remove(0, 1); + } +} + +class AssetHierarchy +{ + FileSystemWatcher fsw ~ { + _.StopRaisingEvents(); + delete _; + }; + + bool _fileSystemDirty = false; + + internal TreeNode _assetHierarchy = null ~ DeleteTreeAndChildren!(_); + private append Dictionary> _pathToAssetNode = .(); + + private append String _contentDirectory = .(); + + private EditorContentManager _contentManager; + + public StringView ContentDirectory + { + get => _contentDirectory; + private set + { + _contentDirectory.Clear(); + _contentDirectory.Append(value); + Path.Fixup(_contentDirectory); + } + } + + public this(EditorContentManager contentManager) + { + _contentManager = contentManager; + } + + public void SetContentDirectory(StringView contentDirectory) + { + ContentDirectory = contentDirectory; + + _fileSystemDirty = true; + + SetupFileSystemWatcher(); + + Update(); + } + + /// Initializes the FSW for the current ContentDirectory and registers the events. + private void SetupFileSystemWatcher() + { + delete fsw; + fsw = new FileSystemWatcher(_contentDirectory); + fsw.IncludeSubdirectories = true; + + fsw.OnChanged.Add(new (filename) => { + // Note: Gets fired for a directory if a file inside it is created/removed + + Log.EngineLogger.Trace($"File content changed (\"{filename}\")"); + //_fileSystemDirty = true; + + FileContentChanged(filename); + }); + + fsw.OnCreated.Add(new (filename) => { + Log.EngineLogger.Trace($"File created (\"{filename}\")"); + + _fileSystemDirty = true; + }); + + fsw.OnDeleted.Add(new (filename) => { + Log.EngineLogger.Trace($"File deleted (\"{filename}\")"); + + _fileSystemDirty = true; + }); + + fsw.OnRenamed.Add(new (oldName, newName) => { + Log.EngineLogger.Trace($"File renamed (From \"{oldName}\" to \"{newName}\")"); + + //_fileSystemDirty = true; + FileRenamed(oldName, newName); + /*String contentFilePath = scope String(); + + Path.InternalCombine(contentFilePath, ContentDirectory, oldName); + + //_fileSystemDirty = true; + TreeNode fileNode = GetNodeFromPath(contentFilePath); + fileNode->*/ + }); + + fsw.StartRaisingEvents(); + } + + /// Gets the tree node for the given filePath or .Err, if the file/directory doesn't exist. + /// @param filePath the path for which to return the tree node. + /// @remarks Do not hold a reference to the TreeNode because it can become invalid when the file hierarchy changes. + public Result> GetNodeFromPath(StringView filePath) + { + if (_pathToAssetNode.TryGetValue(filePath, let treeNode)) + { + return treeNode; + } + + return .Err; + } + + public bool FileExists(StringView filePath) + { + return _pathToAssetNode.ContainsKey(filePath); + } + + public void Update() + { + // TODO: do we really need to do this in the update loop? + if (_fileSystemDirty) + { + UpdateFiles(); + } + } + + + /// Rebuilds the asset file hierarchy. + private void UpdateFiles() + { + Log.EngineLogger.Trace($"Updating asset hierarchy"); + + if (_assetHierarchy == null) + { + // TODO: move to init? + + _assetHierarchy = new TreeNode(new AssetNode()); + _assetHierarchy->Path = new String(ContentDirectory); + _assetHierarchy->Name = new String("Content"); + _assetHierarchy->IsDirectory = true; + + Log.EngineLogger.Trace($"Created directory node for: \"{_assetHierarchy->Path}\""); + + _pathToAssetNode.Add(ContentDirectory, _assetHierarchy); + } + + void HandleFile(AssetNode node) + { + String identifier = scope .(node.Path.Length); + Path.GetRelativePath(node.Path, _contentDirectory, identifier); + AssetIdentifier.Fixup(identifier); + + node.AssetFile = new AssetFile(_contentManager, identifier, node.Path, node.IsDirectory); + } + + /// Determines the files that belong to the given directory and adds them to the tree. + void AddFilesOfDirectory(TreeNode directory) + { + // Filter that accepts all files. + String filter = scope $"{directory->Path}/*"; + + // Buffer used to hold the path of the files iterated below. + String filepathBuffer = scope String(256); + // Buffer used to hold the file extension of the files iterated below. + String extensionBuffer = scope String(16); + + for (var entry in Directory.Enumerate(filter, .Files)) + { + entry.GetFilePath(filepathBuffer..Clear()); + + Path.GetExtension(filepathBuffer, .. extensionBuffer..Clear()); + + // Ignore meta files. + if (extensionBuffer.Equals(AssetFile.ConfigFileExtension, .OrdinalIgnoreCase)) + continue; + + TreeNode treeNode = directory.Children.Where(scope (node) => node.Value.Path == filepathBuffer).FirstOrDefault(); + + if (treeNode == null) + { + AssetNode assetNode = new AssetNode(); + assetNode.Name = new String(); + Path.GetFileName(filepathBuffer, assetNode.Name); + + filepathBuffer.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + + assetNode.Path = new String(filepathBuffer); + assetNode.IsDirectory = false; + + treeNode = directory.AddChild(assetNode); + _pathToAssetNode.Add(assetNode.Path, treeNode); + + //GrabSubAssets(node); + HandleFile(treeNode.Value); + + Log.EngineLogger.Trace($"Created file node for: \"{assetNode.Path}\""); + } + } + } + + void RemoveOrphanedEntries(TreeNode node) + { + /// Removes the node and its children from _pathToAssetNode + void RemoveSubtree(TreeNode tree) + { + _pathToAssetNode.Remove(tree->Path); + + for (var child in tree.Children) + { + RemoveSubtree(child); + } + } + + for (TreeNode child in node.Children) + { + if (!Directory.Exists(child->Path) && !File.Exists(child->Path)) + { + Log.EngineLogger.Trace($"Removed orphaned node for: \"{child->Path}\""); + + @child.Remove(); + + RemoveSubtree(child); + + DeleteTreeAndChildren!(child); + } + } + } + + /// Adds the given directory to the specified tree. + /// Recursively adds all Files and Subdirectories. + void AddDirectoryToTree(String path, TreeNode parentNode) + { + path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + + // Try to find the node for the specified path in the given parent + TreeNode treeNode = parentNode.Children.Where(scope (node) => node.Value.Path == path).FirstOrDefault(); + + // Create new Node for the Directory, if no TreeNode exists. + if (treeNode == null) + { + AssetNode assetNode = new AssetNode(); + assetNode.Path = new String(path); + assetNode.Name = new String(); + assetNode.IsDirectory = true; + Path.GetFileName(assetNode.Path, assetNode.Name); + + treeNode = parentNode.AddChild(assetNode); + _pathToAssetNode.Add(assetNode.Path, treeNode); + + Log.EngineLogger.Trace($"Created directory node for: \"{assetNode.Path}\""); + } + + String directoryNameBuffer = scope String(256); + + // Filter that finds all entries of a directory. + String filter = scope $"{path}/*"; + + for (var directory in Directory.Enumerate(filter, .Directories)) + { + directory.GetFilePath(directoryNameBuffer..Clear()); + + AddDirectoryToTree(directoryNameBuffer, treeNode); + } + + AddFilesOfDirectory(treeNode); + + RemoveOrphanedEntries(treeNode); + } + + String filter = scope $"{ContentDirectory}/*"; + + String directoryNameBuffer = scope String(256); + + for (var directory in Directory.Enumerate(filter, .Directories)) + { + directory.GetFilePath(directoryNameBuffer..Clear()); + + AddDirectoryToTree(directoryNameBuffer, _assetHierarchy); + } + + RemoveOrphanedEntries(_assetHierarchy); + + _fileSystemDirty = false; + } + + private void FileContentChanged(StringView fileName) + { + var fileName; + + // Config files aren't really tracked but changing them effectively changes the corresponding file + // so we fire the event for them. + if (fileName.EndsWith(AssetFile.ConfigFileExtension)) + fileName.RemoveFromEnd(AssetFile.ConfigFileExtension.Length); + + String fileNameWithContentRoot = scope .(); + Path.InternalCombine(fileNameWithContentRoot, _contentDirectory, fileName); + + var nodeResult = GetNodeFromPath(fileNameWithContentRoot); + + TreeNode node = null; + + if (!(nodeResult case .Ok(out node))) + { + // This happens, when we create new files. + Log.EngineLogger.Trace($"Could not find node for file \"{fileNameWithContentRoot}\""); + return; + } + + // Don't fire event for directories. + if (node->IsDirectory) + return; + + OnFileContentChanged(node.Value); + } + + private void FileRenamed(StringView oldFilePath, StringView newFilePath) + { + var oldFilePath; + + // Ignore Config files. + if (oldFilePath.EndsWith(AssetFile.ConfigFileExtension)) + return; + + + String oldFileNameWithContentRoot = scope .(); + Path.InternalCombine(oldFileNameWithContentRoot, _contentDirectory, oldFilePath); + + String newFileNameWithContentRoot = scope .(); + Path.InternalCombine(newFileNameWithContentRoot, _contentDirectory, newFilePath); + + // Rename config file + { + String oldConfigFileName = scope $"{oldFileNameWithContentRoot}{AssetFile.ConfigFileExtension}"; + String newConfigFileName = scope $"{newFileNameWithContentRoot}{AssetFile.ConfigFileExtension}"; + + if (File.Exists(oldConfigFileName) && !File.Exists(newConfigFileName)) + { + if (File.Move(oldConfigFileName, newConfigFileName) case .Err(let value)) + { + Log.EngineLogger.Error($"Failed to move file {oldConfigFileName} to {newConfigFileName}. Code: {value}"); + } + } + } + + var nodeResult = GetNodeFromPath(oldFileNameWithContentRoot); + + TreeNode node = null; + + if (!(nodeResult case .Ok(out node))) + { + // This happens, when we create new files. + Log.EngineLogger.Trace($"Could not find node for file \"{oldFileNameWithContentRoot}\""); + return; + } + + _pathToAssetNode.Remove(oldFileNameWithContentRoot); + + node->Path.Set(newFileNameWithContentRoot); + _pathToAssetNode.Add(node->Path, node); + + node->Name.Clear(); + Path.GetFileName(newFileNameWithContentRoot, node->Name); + + String oldIdentifier = scope .(node->AssetFile.[Friend]_identifier); + + node->AssetFile.[Friend]_path.Set(node->Path); + + node->AssetFile.[Friend]_identifier.Set(newFilePath); + AssetIdentifier.Fixup(node->AssetFile.[Friend]_identifier); + + node->AssetFile.[Friend]_assetConfigPath..Set(node->Path).Append(AssetFile.ConfigFileExtension); + + OnFileRenamed(node.Value, oldIdentifier); + } + + public delegate void FileContentChangedFunc(AssetNode node); + + public Event OnFileContentChanged ~ _.Dispose(); + + public delegate void FileRenamedFunc(AssetNode node, StringView oldName); + + public Event OnFileRenamed ~ _.Dispose(); +} diff --git a/GlitchyEditor/src/Assets/AssetPropertiesEditor.bf b/GlitchyEditor/src/Assets/AssetPropertiesEditor.bf new file mode 100644 index 0000000..96e6535 --- /dev/null +++ b/GlitchyEditor/src/Assets/AssetPropertiesEditor.bf @@ -0,0 +1,15 @@ +namespace GlitchyEditor.Assets; + +abstract class AssetPropertiesEditor +{ + private AssetFile _asset; + + public AssetFile Asset => _asset; + + public this(AssetFile asset) + { + _asset = asset; + } + + public abstract void ShowEditor(); +} \ No newline at end of file diff --git a/GlitchyEditor/src/Assets/EffectAssetLoader.bf b/GlitchyEditor/src/Assets/EffectAssetLoader.bf new file mode 100644 index 0000000..8878780 --- /dev/null +++ b/GlitchyEditor/src/Assets/EffectAssetLoader.bf @@ -0,0 +1,62 @@ +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 _fileExtensions = new .(){".hlsl"} ~ delete _; + + public static List 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; + } +} \ No newline at end of file diff --git a/GlitchyEditor/src/Assets/IAssetSaver.bf b/GlitchyEditor/src/Assets/IAssetSaver.bf new file mode 100644 index 0000000..b873531 --- /dev/null +++ b/GlitchyEditor/src/Assets/IAssetSaver.bf @@ -0,0 +1,10 @@ +using GlitchyEngine.Content; +using System; +using System.IO; + +namespace GlitchyEditor.Assets; + +interface IAssetSaver +{ + Result EditorSaveAsset(Stream file, Asset asset, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager); +} \ No newline at end of file diff --git a/GlitchyEditor/src/Assets/IReloadingAssetLoader.bf b/GlitchyEditor/src/Assets/IReloadingAssetLoader.bf new file mode 100644 index 0000000..284ac59 --- /dev/null +++ b/GlitchyEditor/src/Assets/IReloadingAssetLoader.bf @@ -0,0 +1,8 @@ +using System.IO; + +namespace GlitchyEditor.Assets; + +interface IReloadingAssetLoader +{ + public void ReloadAsset(AssetFile assetFile, Stream data); +} \ No newline at end of file diff --git a/GlitchyEditor/src/Assets/MaterialAssetLoader.bf b/GlitchyEditor/src/Assets/MaterialAssetLoader.bf new file mode 100644 index 0000000..7868331 --- /dev/null +++ b/GlitchyEditor/src/Assets/MaterialAssetLoader.bf @@ -0,0 +1,491 @@ +using Bon; +using GlitchyEngine.Content; +using System; +using System.Collections; +using System.IO; +using GlitchyEngine; +using GlitchyEngine.Renderer; +using ImGui; +using GlitchyEngine.Math; +using System.Diagnostics; +using Bon.Integrated; + +namespace GlitchyEditor.Assets; + +class MaterialAssetPropertiesEditor : AssetPropertiesEditor +{ + public static bool TryGetValue(Dictionary parameters, String name, out Variant value) + { + if (parameters.TryGetValue(name, let param)) + { + value = param; + return true; + } + + value = ?; + + return false; + } + + mixin DropAssetTarget() where T : Asset + { + AssetHandle handle = .Invalid; + + if (ImGui.BeginDragDropTarget()) + { + ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM"); + + if (payload != null) + { + StringView fullpath = .((char8*)payload.Data, (int)payload.DataSize); + + handle = Content.LoadAsset(fullpath); + } + + ImGui.EndDragDropTarget(); + } + + handle + } + + public this(AssetFile asset) : base(asset) + { + + } + + public override void ShowEditor() + { + Material material = Asset.LoadedAsset as Material; + + if (material == null) + return; + + Effect effect = material?.Effect; + + if (effect == null) + return; + + ShowTextures(material, effect); + + ShowVariables(material, effect); + } + + private void ShowTextures(Material material, Effect effect) + { + for (let texture in effect.Textures) + { + ImGui.Button(texture.key); + + if (ImGui.BeginDragDropTarget()) + { + ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM"); + + if (payload != null) + { + StringView path = .((char8*)payload.Data, (int)payload.DataSize); + + AssetHandle newTexture = Content.LoadAsset(path); + + //newTexture.Get().SamplerState = SamplerStateManager.AnisotropicWrap; + material.SetTexture(texture.key, newTexture.Cast()); + } + + ImGui.EndDragDropTarget(); + } + } + } + + private void ShowVariables(Material material, Effect effect) + { + for (let (name, arguments) in effect.[Friend]_variableDescriptions) + { + let variable = effect.Variables[name]; + + bool hasPreviewName = TryGetValue(arguments, "Preview", var previewName); + + StringView displayName = hasPreviewName ? previewName.Get() : name; + + bool hasPreviewType = TryGetValue(arguments, "Type", var previewType); + + if (hasPreviewType && previewType.Get() == "Color") + { + Log.EngineLogger.AssertDebug(variable.Type == .Float && variable.Rows == 1); + + if (variable.Columns == 3) + { + material.GetVariable(variable.Name, var value); + + value = (Vector3)ColorRGB.LinearToSRGB((ColorRGB)value); + + if (ImGui.ColorEdit3(displayName.Ptr, *(float[3]*)&value)) + { + value = (Vector3)ColorRGB.SRgbToLinear((ColorRGB)value); + material.SetVariable(variable.Name, value); + } + } + else if (variable.Columns == 4) + { + material.GetVariable(variable.Name, var value); + + value = (Vector4)ColorRGBA.LinearToSRGB((ColorRGBA)value); + + if (ImGui.ColorEdit4(displayName.Ptr, *(float[4]*)&value)) + { + value = (Vector4)ColorRGBA.SRgbToLinear((ColorRGBA)value); + material.SetVariable(variable.Name, value); + } + } + } + else if (variable.Type == .Float && variable.Rows == 1) + { + bool hasMin = TryGetValue(arguments, "Min", var min); + bool hasMax = TryGetValue(arguments, "Max", var max); + + for (int r < variable.Rows) + { + switch (variable.Columns) + { + case 1: + material.GetVariable(variable.Name, var value); + + float[1] minV = hasMin ? min.Get() : .(float.MinValue); + float[1] maxV = hasMax ? max.Get() : .(float.MaxValue); + + if (ImGui.EditVector<1>(displayName, ref *(float[1]*)&value, .(), 0.1f, 100.0f, minV, maxV)) + material.SetVariable(variable.Name, value); + case 2: + material.GetVariable(variable.Name, var value); + + Vector2 minV = hasMin ? min.Get() : .(float.MinValue); + Vector2 maxV = hasMax ? max.Get() : .(float.MaxValue); + + if (ImGui.EditVector2(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV)) + material.SetVariable(variable.Name, value); + case 3: + material.GetVariable(variable.Name, var value); + + Vector3 minV = hasMin ? min.Get() : .(float.MinValue); + Vector3 maxV = hasMax ? max.Get() : .(float.MaxValue); + + if (ImGui.EditVector3(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV)) + material.SetVariable(variable.Name, value); + case 4: + material.GetVariable(variable.Name, var value); + + Vector4 minV = hasMin ? min.Get() : .(float.MinValue); + Vector4 maxV = hasMax ? max.Get() : .(float.MaxValue); + + if (ImGui.EditVector4(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV)) + material.SetVariable(variable.Name, value); + } + } + } + } + } + + public static AssetPropertiesEditor Factory(AssetFile assetFile) + { + return new Self(assetFile); + } +} + +[BonTarget, BonPolyRegister] +class MaterialAssetLoaderConfig : AssetLoaderConfig +{ + +} + +[BonTarget] +public enum VariableValue +{ + case Float(float Value); + case Float2(Vector2 Value); + case Float3(Vector3 Value); + case Float4(Vector4 Value); + case Int(int Value); + case Int2(Int2 Value); + case Int3(Int3 Value); + case Int4(Int4 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(); + + 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 VariableValueDeserialize(BonReader reader, ValueView val, BonEnvironment env) + { + return .Ok; + }*/ +} + +[BonTarget] +class MaterialFile +{ + public String Effect ~ delete _; + + public Dictionary Textures ~ DeleteDictionaryAndKeysAndValues!(_); + public Dictionary 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(); + + 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 MaterialDeserialize(BonReader reader, ValueView val, BonEnvironment env) + { + return .Ok; + }*/ +} + +class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader +{ + private static readonly List _fileExtensions = new .(){".mat"} ~ delete _; + + public static List 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(ref materialFile, text); + + if (result case .Err) + { + Log.EngineLogger.Error("Failed to load material."); + Debug.SafeBreak(); + return null; + } + + Effect fx = Content.GetAsset(contentManager.LoadAsset(materialFile.Effect, true), contentManager); + + Material material = new Material(fx); + + for (let (slotName, textureIdentifier) in materialFile.Textures) + { + AssetHandle texture = contentManager.LoadAsset(textureIdentifier); + + if (texture.IsInvalid) + { + Log.EngineLogger.Error($"Failed to load texture \"{textureIdentifier}\"."); + } + + 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 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) + { + Texture textureAsset = texture.Get(); + + materialFile.Textures.Add(new String(slotName), new String(textureAsset?.Identifier ?? "")); + } + + Effect effect = material.Effect; + + if (effect == null) + return .Ok; + + 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() == "Color") + { + Log.EngineLogger.AssertDebug(variable.Type == .Float && variable.Rows == 1); + + if (variable.Columns == 3) + { + material.GetVariable(variable.Name, var value); + + value = ColorRGB.LinearToSRGB((ColorRGB)value); + + //variantValue = new box value; + variableValue = .ColorRGB(value); + } + else if (variable.Columns == 4) + { + material.GetVariable(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(variable.Name, let value); + variableValue = .Float(value); + case 2: + material.GetVariable(variable.Name, let value); + variableValue = .Float2(value); + case 3: + material.GetVariable(variable.Name, let value); + variableValue = .Float3(value); + case 4: + material.GetVariable(variable.Name, let value); + variableValue = .Float4(value); + } + } + + materialFile.Variables.Add(new String(name), variableValue); + } + + String text = scope .(); + + gBonEnv.serializeFlags |= .IncludeDefault | .Verbose; + + Bon.Serialize(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; + } +} \ No newline at end of file diff --git a/GlitchyEditor/src/Assets/ModelAssetLoader.bf b/GlitchyEditor/src/Assets/ModelAssetLoader.bf new file mode 100644 index 0000000..41cc8cd --- /dev/null +++ b/GlitchyEditor/src/Assets/ModelAssetLoader.bf @@ -0,0 +1,61 @@ +using Bon; +using GlitchyEngine.Content; +using System; +using System.Collections; +using System.IO; +using GlitchyEngine; + +namespace GlitchyEditor.Assets; + +class ModelAssetPropertiesEditor : AssetPropertiesEditor +{ + public this(AssetFile asset) : base(asset) + { + + } + + public override void ShowEditor() + { + + } + + public static AssetPropertiesEditor Factory(AssetFile assetFile) + { + return new ModelAssetPropertiesEditor(assetFile); + } +} + +[BonTarget, BonPolyRegister] +class ModelAssetLoaderConfig : AssetLoaderConfig +{ + +} + +class ModelAssetLoader : IAssetLoader //, IReloadingAssetLoader +{ + private static readonly List _fileExtensions = new .(){".gltf", ".glb"} ~ delete _; + + public static List FileExtensions => _fileExtensions; + + public AssetLoaderConfig GetDefaultConfig() + { + return new ModelAssetLoaderConfig(); + } + + public Asset LoadAsset(Stream file, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager) + { + //Log.EngineLogger.Assert(subAsset != null); + + return ModelLoader.LoadMesh(file, subAsset ?? assetIdentifier, 0); + } + + public Asset GetPlaceholderAsset(Type assetType) + { + return default; + } + + public Asset GetErrorAsset(Type assetType) + { + return default; + } +} \ No newline at end of file diff --git a/GlitchyEditor/src/Assets/SerializeExtension.bf b/GlitchyEditor/src/Assets/SerializeExtension.bf new file mode 100644 index 0000000..8e4323f --- /dev/null +++ b/GlitchyEditor/src/Assets/SerializeExtension.bf @@ -0,0 +1,6 @@ +using System; +namespace Bon.Integrated; + +extension Serialize +{ +} \ No newline at end of file diff --git a/GlitchyEditor/src/Assets/TextureAssetLoader.bf b/GlitchyEditor/src/Assets/TextureAssetLoader.bf new file mode 100644 index 0000000..cf88063 --- /dev/null +++ b/GlitchyEditor/src/Assets/TextureAssetLoader.bf @@ -0,0 +1,371 @@ +using System; +using System.Collections; +using Bon; +using System.IO; +using GlitchyEngine; +using GlitchyEngine.Content; +using GlitchyEngine.Renderer; +using GlitchyEngine.Math; +using DirectXTK; +using ImGui; + +namespace GlitchyEditor.Assets; + +class TextureAssetPropertiesEditor : AssetPropertiesEditor +{ + EditorTextureAssetLoaderConfig _textureConfig; + + public this(AssetFile asset) : base(asset) + { + _textureConfig = asset.AssetConfig.Config as EditorTextureAssetLoaderConfig; + } + + static char8*[3] _filterFuncNames = char8*[]("Point", "Linear", "Anisotropic"); + + public override void ShowEditor() + { + if (_textureConfig == null) + return; + + bool generateMips = _textureConfig.GenerateMipMaps; + if (ImGui.Checkbox("Generate Mip Maps", &generateMips)) + _textureConfig.GenerateMipMaps = generateMips; + + bool isSrgb = _textureConfig.IsSRGB; + if (ImGui.Checkbox("Is sRGB", &isSrgb)) + _textureConfig.IsSRGB = isSrgb; + + SamplerStateDescription samplerStateDescription = _textureConfig.SamplerStateDescription; + + void ShowFilterCombo(String label, ref FilterFunction filterFunction) + { + int32 selectedFilter = filterFunction.Underlying; + if (ImGui.Combo(label, &selectedFilter, &_filterFuncNames, 3)) + filterFunction = (.)selectedFilter; + } + + ImGui.Separator(); + ImGui.TextUnformatted("Texture Filtering:"); + ImGui.Separator(); + + ImGui.EnumCombo("Min Filter", ref samplerStateDescription.MinFilter); + ImGui.AttachTooltip(""" + Sampling method used for minification. + If set to "Anisotropic" all Filters are set to "Anisotropic" internally. + """); + ImGui.EnumCombo("Mag Filter", ref samplerStateDescription.MagFilter); + ImGui.AttachTooltip(""" + Sampling method used for magnification. + If set to "Anisotropic" all Filters are set to "Anisotropic" internally. + """); + ImGui.EnumCombo("Mip Map Filter", ref samplerStateDescription.MipFilter); + ImGui.AttachTooltip(""" + Method used for mip-level sampling. + If set to "Anisotropic" all Filters are set to "Anisotropic" internally. + """); + + if (samplerStateDescription.MagFilter == .Anisotropic || + samplerStateDescription.MinFilter == .Anisotropic || + samplerStateDescription.MipFilter == .Anisotropic) + { + ImGui.SliderScalar("Anisotropy Level", ref samplerStateDescription.MaxAnisotropy, 1, 16); + } + + ImGui.NewLine(); + + ImGui.EnumCombo("Filter Mode", ref samplerStateDescription.FilterMode); + ImGui.AttachTooltip("Filtering method to use when sampling a texture."); + + if (samplerStateDescription.FilterMode == .Comparison) + { + ImGui.EnumCombo("Comparison Function", ref samplerStateDescription.ComparisonFunction); + ImGui.AttachTooltip(""" + The function that is used to compare the sampled data against the existing sampled data. + Only applies if Filter Mode is set to FilterMode.Comparison. + """); + } + + ImGui.Separator(); + ImGui.TextUnformatted("Wrapping"); + ImGui.Separator(); + + ImGui.EnumCombo("Wrap Mode U", ref samplerStateDescription.AddressModeU); + ImGui.AttachTooltip("Method to use for resolving a u texture coordinate that is outside the 0 to 1 range."); + + ImGui.EnumCombo("Wrap Mode V", ref samplerStateDescription.AddressModeV); + ImGui.AttachTooltip("Method to use for resolving a v texture coordinate that is outside the 0 to 1 range."); + + ImGui.EnumCombo("Wrap Mode W", ref samplerStateDescription.AddressModeW); + ImGui.AttachTooltip("Method to use for resolving a w texture coordinate that is outside the 0 to 1 range."); + + if (samplerStateDescription.AddressModeU == .Border || + samplerStateDescription.AddressModeV == .Border || + samplerStateDescription.AddressModeW == .Border) + { + ImGui.ColorEdit4("Border Color", ref samplerStateDescription.BorderColor); + } + + ImGui.Separator(); + ImGui.TextUnformatted("Mip Maps"); + ImGui.Separator(); + + ImGui.DragFloat("Mip LOD Bias", &samplerStateDescription.MipLODBias, 0.1f); + ImGui.AttachTooltip(""" + Offset from the calculated mipmap level. + For example, if the GPU calculates that a texture should be sampled at mipmap level 3 and "Mip LOD Bias" is 2, then the texture will be sampled at mipmap level 5. + """); + + ImGui.DragFloat("Min Mip LOD", &samplerStateDescription.MipMinLOD); + ImGui.AttachTooltip("Lower end of the mipmap range to clamp access to, where 0 is the largest and most detailed mipmap level and any level higher than that is less detailed."); + + ImGui.DragFloat("Max LOD Bias", &samplerStateDescription.MipMaxLOD); + ImGui.AttachTooltip(""" + Upper end of the mipmap range to clamp access to, where 0 is the largest and most detailed mipmap level and any level higher than that is less detailed. + This value must be greater than or equal to "Min Mip LOD". To have no upper limit on LOD set this to a large value. + """); + + _textureConfig.SamplerStateDescription = samplerStateDescription; + } + + public static AssetPropertiesEditor Factory(AssetFile assetFile) + { + return new TextureAssetPropertiesEditor(assetFile); + } +} + +[BonTarget, BonPolyRegister] +class EditorTextureAssetLoaderConfig : AssetLoaderConfig +{ + [BonInclude] + private bool _generateMipMaps; + + [BonInclude] + private bool _isSrgb; + + [BonInclude] + private SamplerStateDescription _samplerStateDescription = .(); + + public bool GenerateMipMaps + { + get => _generateMipMaps; + set => SetIfChanged(ref _generateMipMaps, value); + } + + public bool IsSRGB + { + get => _isSrgb; + set => SetIfChanged(ref _isSrgb, value); + } + + public SamplerStateDescription SamplerStateDescription + { + get => _samplerStateDescription; + set => SetIfChanged(ref _samplerStateDescription, value); + } +} + +class EditorTextureAssetLoader : IAssetLoader//, IReloadingAssetLoader +{ + private static readonly List _fileExtensions = new .(){".png", ".dds"} ~ delete _; // ".jpg", ".bmp" + + public static List FileExtensions => _fileExtensions; + + public AssetLoaderConfig GetDefaultConfig() + { + return new EditorTextureAssetLoaderConfig(); + } + + public Asset LoadAsset(Stream data, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager) + { + var config; + + if (config == null) + { + config = GetDefaultConfig(); + defer:: delete config; + } + + Log.EngineLogger.AssertDebug(config is EditorTextureAssetLoaderConfig, "config has wrong type."); + + return LoadTexture(data, (EditorTextureAssetLoaderConfig)config); + } + + const String PngMagicWord = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"; + const String DdsMagicWord = "DDS "; + + enum TextureType + { + Unknown, + DDS, + PNG + } + + private static TextureType GetTextureType(Stream data) + { + int64 position = data.Position; + + var readResult = data.Read(); + + data.Position = position; + + char8[8] magicWord; + if (readResult case .Ok(out magicWord)) + { + StringView strView = .(&magicWord, magicWord.Count); + + if (strView.StartsWith(PngMagicWord)) + { + return .PNG; + } + else if (strView.StartsWith(DdsMagicWord)) + { + return .DDS; + } + else + { + Runtime.FatalError("Unknown image format."); + } + } + + return .Unknown; + } + + private static Texture LoadTexture(Stream data, EditorTextureAssetLoaderConfig config) + { + Debug.Profiler.ProfileResourceFunction!(); + + Texture texture = null; + + switch(GetTextureType(data)) + { + case .DDS: + texture = LoadDds(data, config); + case .PNG: + texture = LoadPng(data, config); + case .Unknown: + Log.EngineLogger.Error("Unknown texture format."); + texture = null; + } + + if (texture != null) + { + SetSampler(texture, config); + texture.[Friend]Complete = true; + } + + return texture; + } + + private static Texture2D LoadPng(Stream data, EditorTextureAssetLoaderConfig config) + { + Debug.Profiler.ProfileResourceFunction!(); + + uint8[] pngData = new:ScopedAlloc! uint8[data.Length]; + + var result = data.TryRead(pngData); + + if (result case .Err(let err)) + { + Log.EngineLogger.Error($"Failed to read data from stream. Texture: Error: {err}"); + return null; + } + + uint8* rawData = null; + defer + { + if (rawData != null) + LodePng.LodePng.Free(rawData); + } + + uint32 width = 0, height = 0; + + { + Debug.Profiler.ProfileResourceScope!("LodePng.LodePng.Decode32"); + uint32 errorCode = LodePng.LodePng.Decode32(&rawData, &width, &height, pngData.Ptr, (.)pngData.Count); + if (errorCode != 0) + { + Log.EngineLogger.Error($"Failed to decode PNG file {errorCode}."); + return null; + } + } + + Texture2DDesc desc = .(width, height, config.IsSRGB ? .R8G8B8A8_UNorm_SRGB : .R8G8B8A8_UNorm, 1, 1, .Immutable); + Texture2D texture = new Texture2D(desc); + texture.SetData((.)rawData); + + // TODO: Generate mip maps + + return texture; + } + + private static Texture LoadDds(Stream data, EditorTextureAssetLoaderConfig config) + { + // TODO: Move the loading of Dds files here. + Texture2D texture = new [Friend]Texture2D(data); + + return texture; + } + + private static void SetSampler(Texture texture, EditorTextureAssetLoaderConfig config) + { + using (SamplerState samplerState = SamplerStateManager.GetSampler(config.SamplerStateDescription)) + { + texture.SamplerState = samplerState; + } + } + + private static Texture2D _placeholder2D; + private static Texture2D _error2D; + + public Asset GetPlaceholderAsset(Type assetType) + { + switch (assetType) + { + case typeof(Texture2D): + fallthrough; + default: + if (_placeholder2D == null) + { + Texture2DDesc desc = .(1, 1, .R8G8B8A8_UNorm, 1, 1, .Immutable, .None); + + _placeholder2D = new Texture2D(desc); + _placeholder2D.SamplerState = SamplerStateManager.PointWrap; + Color color = Color.Cyan; + _placeholder2D.SetData(&color); + + Content.ManageAsset(_placeholder2D); + _placeholder2D.ReleaseRef(); + + _placeholder2D.[Friend]Complete = false; + } + + return _placeholder2D; + } + } + + public Asset GetErrorAsset(Type assetType) + { + switch (assetType) + { + case typeof(Texture2D): + fallthrough; + default: + if (_error2D == null) + { + Texture2DDesc desc = .(2, 2, .R8G8B8A8_UNorm, 1, 1, .Immutable, .None); + + _error2D = new Texture2D(desc); + _error2D.SamplerState = SamplerStateManager.PointWrap; + Color[4] color = .(Color.HotPink, Color.Black, Color.Black, Color.HotPink); + _error2D.SetData(&color); + + Content.ManageAsset(_error2D); + _error2D.ReleaseRef(); + + _placeholder2D.[Friend]Complete = true; + } + + return _error2D; + } + } +} diff --git a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf index 2b97f63..54c48f7 100644 --- a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf +++ b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf @@ -3,6 +3,9 @@ using GlitchyEngine.World; using System; using GlitchyEngine.Math; using System.Collections; +using GlitchyEngine.Renderer; +using GlitchyEngine; +using GlitchyEngine.Content; namespace GlitchyEditor.EditWindows { @@ -57,7 +60,14 @@ namespace GlitchyEditor.EditWindows ShowComponentEditor("Transform", entity, => ShowTransformComponentEditor); ShowComponentEditor("Camera", entity, => ShowCameraComponentEditor, => ShowComponentContextMenu); - ShowComponentEditor("Sprite Renderer", entity, => ShowSpriteRendererComponentEditor, => ShowComponentContextMenu); + ShowComponentEditor("Sprite Renderer", entity, => ShowSpriteRendererComponentEditor, => ShowComponentContextMenu); + ShowComponentEditor("Circle Renderer", entity, => ShowCircleRendererComponentEditor, => ShowComponentContextMenu); + ShowComponentEditor("Mesh Renderer", entity, => ShowMeshRendererComponentEditor, => ShowComponentContextMenu); + ShowComponentEditor("Light", entity, => ShowLightComponentEditor, => ShowComponentContextMenu); + ShowComponentEditor("Mesh", entity, => ShowMeshComponentEditor, => ShowComponentContextMenu); + ShowComponentEditor("Rigidbody 2D", entity, => ShowRigidBody2DComponentEditor, => ShowComponentContextMenu); + ShowComponentEditor("Box collider 2D", entity, => ShowBoxCollider2DComponentEditor, => ShowComponentContextMenu); + ShowComponentEditor("Circle collider 2D", entity, => ShowCircleCollider2DComponentEditor, => ShowComponentContextMenu); ShowAddComponentButton(entity); } @@ -108,18 +118,18 @@ namespace GlitchyEditor.EditWindows private static void ShowNameComponentEditor(Entity entity) { - if (!entity.HasComponent()) + if (!entity.HasComponent()) return; char8[256] nameBuffer = default; - DebugNameComponent* component = entity.GetComponent(); + NameComponent* component = entity.GetComponent(); - String name = null; + StringView name = null; if(component != null) { - name = component.DebugName; + name = component.Name; } else { @@ -133,11 +143,10 @@ namespace GlitchyEditor.EditWindows { if(component == null) { - component = entity.AddComponent(); + component = entity.AddComponent(); } - component.DebugName.Clear(); - component.DebugName.Append(&nameBuffer); + component.Name = StringView(&nameBuffer); } } @@ -241,9 +250,285 @@ namespace GlitchyEditor.EditWindows } } - private static void ShowSpriteRendererComponentEditor(Entity entity, SpriterRendererComponent* spriteRendererComponent) + private static void ShowSpriteRendererComponentEditor(Entity entity, SpriteRendererComponent* spriteRendererComponent) { - ImGui.ColorEdit4("Color", ref spriteRendererComponent.Color); + ColorRGBA spriteColor = ColorRGBA.LinearToSRGB(spriteRendererComponent.Color); + if (ImGui.ColorEdit4("Color", ref spriteColor)) + spriteRendererComponent.Color = ColorRGBA.SRgbToLinear(spriteColor); + + ImGui.Button("Texture"); + + if (ImGui.BeginDragDropTarget()) + { + ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM"); + + if (payload != null) + { + Log.EngineLogger.Warning(""); + + StringView path = .((char8*)payload.Data, (int)payload.DataSize); + + spriteRendererComponent.Sprite = Content.LoadAsset(path); + } + + ImGui.EndDragDropTarget(); + } + + + ImGui.EditVector<4>("UV Transform", ref *(float[4]*)&spriteRendererComponent.UvTransform); + } + + private static void ShowCircleRendererComponentEditor(Entity entity, CircleRendererComponent* circleRendererComponent) + { + ColorRGBA spriteColor = ColorRGBA.LinearToSRGB(circleRendererComponent.Color); + if (ImGui.ColorEdit4("Color", ref spriteColor)) + circleRendererComponent.Color = ColorRGBA.SRgbToLinear(spriteColor); + + ImGui.Button("Texture"); + + if (ImGui.BeginDragDropTarget()) + { + ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM"); + + if (payload != null) + { + Log.EngineLogger.Warning(""); + + StringView path = .((char8*)payload.Data, (int)payload.DataSize); + + circleRendererComponent.Sprite = Content.LoadAsset(path); + } + + ImGui.EndDragDropTarget(); + } + + + ImGui.EditVector<4>("UV Transform", ref *(float[4]*)&circleRendererComponent.UvTransform); + + ImGui.DragFloat("Inner Radius", &circleRendererComponent.InnerRadius, 0.1f, 0.0f, 1.0f); + } + + private static void ShowMeshRendererComponentEditor(Entity entity, MeshRendererComponent* meshRendererComponent) + { + ImGui.TextUnformatted("Material:"); + ImGui.SameLine(); + + Material material = meshRendererComponent.Material; + + StringView identifier = material?.Identifier ?? "None"; + ImGui.Button(identifier.ToScopeCStr!()); + + if (ImGui.BeginDragDropTarget()) + { + ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM"); + + 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! + } + + private static void ShowRigidBody2DComponentEditor(Entity entity, Rigidbody2DComponent* rigidBodyComponent) + { + const String[?] bodyTypeStrings = .("Static", "Dynamic", "Kinematic"); + String bodyTypeName = bodyTypeStrings[rigidBodyComponent.BodyType.Underlying]; + + if (ImGui.BeginCombo("Type", bodyTypeName.CStr())) + { + for (int i = 0; i < 3; i++) + { + bool isSelected = (bodyTypeName == bodyTypeStrings[i]); + + if (ImGui.Selectable(bodyTypeStrings[i], isSelected)) + { + rigidBodyComponent.BodyType = (.)i; + } + + if (isSelected) + ImGui.SetItemDefaultFocus(); + } + + ImGui.EndCombo(); + } + + + ImGui.Checkbox("Fixed Rotation", &rigidBodyComponent.FixedRotation); + } + + private static void ShowBoxCollider2DComponentEditor(Entity entity, BoxCollider2DComponent* boxCollider) + { + float textWidth = ImGui.CalcTextSize("Offset".CStr()).x; + textWidth += ImGui.GetStyle().FramePadding.x * 3.0f; + + + Vector2 offset = boxCollider.Offset; + if (ImGui.EditVector2("Offset", ref offset, .Zero, 0.1f, textWidth)) + boxCollider.Offset = offset; + + Vector2 size = boxCollider.Size; + if (ImGui.EditVector2("Size", ref size, .Zero, 0.1f, textWidth)) + boxCollider.Size = size; + + float density = boxCollider.Density; + if (ImGui.DragFloat("Density", &density, 0.0f, 0.1f, textWidth)) + boxCollider.Density = density; + + float friction = boxCollider.Friction; + if (ImGui.DragFloat("Friction", &friction, 0.0f, 0.1f, textWidth)) + boxCollider.Friction = friction; + + float restitution = boxCollider.Restitution; + if (ImGui.DragFloat("Restitution", &restitution, 0.0f, 0.1f, textWidth)) + boxCollider.Restitution = restitution; + + float restitutionThreshold = boxCollider.RestitutionThreshold; + if (ImGui.DragFloat("RestitutionThreshold", &restitutionThreshold, 0.0f, 0.1f, textWidth)) + boxCollider.RestitutionThreshold = restitutionThreshold; + } + + private static void ShowCircleCollider2DComponentEditor(Entity entity, CircleCollider2DComponent* circleCollider) + { + float textWidth = ImGui.CalcTextSize("Offset".CStr()).x; + textWidth += ImGui.GetStyle().FramePadding.x * 3.0f; + + + Vector2 offset = circleCollider.Offset; + if (ImGui.EditVector2("Offset", ref offset, .Zero, 0.1f, textWidth)) + circleCollider.Offset = offset; + + float radius = circleCollider.Radius; + if (ImGui.DragFloat("Radius", &radius, 0.0f, 0.1f, textWidth)) + circleCollider.Radius = radius; + + float density = circleCollider.Density; + if (ImGui.DragFloat("Density", &density, 0.0f, 0.1f, textWidth)) + circleCollider.Density = density; + + float friction = circleCollider.Friction; + if (ImGui.DragFloat("Friction", &friction, 0.0f, 0.1f, textWidth)) + circleCollider.Friction = friction; + + float restitution = circleCollider.Restitution; + if (ImGui.DragFloat("Restitution", &restitution, 0.0f, 0.1f, textWidth)) + circleCollider.Restitution = restitution; + + float restitutionThreshold = circleCollider.RestitutionThreshold; + if (ImGui.DragFloat("RestitutionThreshold", &restitutionThreshold, 0.0f, 0.1f, textWidth)) + circleCollider.RestitutionThreshold = restitutionThreshold; + } + + private static void LabelColumn(StringView label) + { + ImGui.TextUnformatted(label); + ImGui.NextColumn(); + } + + private static void ShowLightComponentEditor(Entity entity, LightComponent* lightComponent) + { + ImGui.Columns(2); + defer ImGui.Columns(1); + + const String[?] strings = String[]("Directional", "Point", "Spot"); + + var light = ref lightComponent.SceneLight; + + String typeName = strings[light.LightType.Underlying]; + + LabelColumn("Type"); + + if (ImGui.BeginCombo("##Type", typeName.CStr())) + { + for (int i = 0; i < 3; i++) + { + bool isSelected = (typeName == strings[i]); + + if (ImGui.Selectable(strings[i], isSelected)) + { + light.LightType = (.)i; + } + + if (isSelected) + ImGui.SetItemDefaultFocus(); + } + + ImGui.EndCombo(); + } + + ImGui.NextColumn(); + LabelColumn("Color"); + + ColorRGB color = ColorRGB.LinearToSRGB(light.Color); + if (ImGui.ColorEdit3("##Color", ref color)) + light.Color = ColorRGB.SRgbToLinear(color); + + ImGui.NextColumn(); + LabelColumn("Illuminance"); + + float illuminance = light.Illuminance; + if (ImGui.DragFloat("##Illuminance", &illuminance, 0.1f, 0.0f, float.MaxValue)) + light.Illuminance = illuminance; + } + + private static void ShowMeshComponentEditor(Entity entity, MeshComponent* meshComponent) + { + ImGui.TextUnformatted("Mesh:"); + ImGui.SameLine(); + + GeometryBinding mesh = meshComponent.Mesh; + + StringView identifier = mesh?.Identifier ?? "None"; + ImGui.Button(identifier.ToScopeCStr!()); + + if (ImGui.BeginDragDropTarget()) + { + ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM"); + + 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(); + } } private static void ShowAddComponentButton(Entity entity) @@ -259,6 +544,10 @@ namespace GlitchyEditor.EditWindows void ShowComponentButton(String name) where TComponent : struct, new { + // If the entity already has this component, don't show the option to add it + if (entity.HasComponent()) + return; + float textWidth = ImGui.CalcTextSize(name.CStr()).x; buttonWidth = Math.Max(buttonWidth, textWidth + ImGui.GetStyle().FramePadding.x * 2); @@ -287,7 +576,14 @@ namespace GlitchyEditor.EditWindows ImGui.Separator(); ShowComponentButton("Camera"); - ShowComponentButton("Sprite Renderer"); + ShowComponentButton("Sprite Renderer"); + ShowComponentButton("Circle Renderer"); + ShowComponentButton("Light"); + ShowComponentButton("Rigidbody 2D"); + ShowComponentButton("Box collider 2D"); + ShowComponentButton("Circle collider 2D"); + ShowComponentButton("Mesh"); + ShowComponentButton("Mesh Renderer"); ImGui.EndCombo(); } diff --git a/GlitchyEditor/src/EditWindows/ContentBrowserWindow.bf b/GlitchyEditor/src/EditWindows/ContentBrowserWindow.bf new file mode 100644 index 0000000..5a7d07c --- /dev/null +++ b/GlitchyEditor/src/EditWindows/ContentBrowserWindow.bf @@ -0,0 +1,402 @@ +using ImGui; +using System; +using System.IO; +using GlitchyEngine.Collections; +using System.Collections; +using GlitchyEngine.Renderer; +using GlitchyEngine.Math; +using GlitchyEngine; +using GlitchyEditor.Assets; + +namespace GlitchyEditor.EditWindows +{ + using internal GlitchyEditor.EditorContentManager; + + class ContentBrowserWindow : EditorWindow + { + public const String s_WindowTitle = "Content Browser"; + + private append String _currentDirectory = .(); + + private append String _selectedFile = .(); + + public static SubTexture2D s_FolderTexture; + public static SubTexture2D s_FileTexture; + + public EditorContentManager _manager; + + public StringView SelectedFile => _selectedFile; + + public this(EditorContentManager contentManager) + { + _manager = contentManager; + } + + protected override void InternalShow() + { + _manager.Update(); + + // Make sure we are in an existing directory. + if (!_manager.AssetHierarchy.FileExists(_currentDirectory)) + { + _currentDirectory.Set(_manager.ContentDirectory); + } + + if(!ImGui.Begin(s_WindowTitle, &_open, .None)) + { + ImGui.End(); + return; + } + + // Context menu when clicking on the background. + if (ImGui.BeginPopupContextWindow()) + { + ShowCurrentFolderContextMenu(); + ImGui.EndPopup(); + } + + ImGui.Columns(2); + + ImGui.BeginChild("Sidebar"); + + DrawDirectorySideBar(); + + ImGui.EndChild(); + + ImGui.NextColumn(); + + ImGui.BeginChild("Files"); + + DrawCurrentDirectory(); + + ImGui.EndChild(); + + ImGui.Columns(1); + + ImGui.End(); + } + + /// Renders the context menu that is shown when the user right clicks on the background of the file browser. + private void ShowCurrentFolderContextMenu() + { + if (ImGui.MenuItem("Open in file browser...")) + { + if (Path.OpenFolder(_currentDirectory) case .Err) + Log.EngineLogger.Error("Failed to open directory in file browser."); + } + } + + /// Renders a sidebar that shows a tree of all directories in the asset folder. + private void DrawDirectorySideBar() + { + for(var child in _manager.AssetHierarchy.[Friend]_assetHierarchy.Children) + { + ImGuiPrintEntityTree(child); + } + } + + /// Renders an ImGui tree of all directories in the given tree. + /// @param tree The file hierarchy of which to render all directories. + private void ImGuiPrintEntityTree(TreeNode tree) + { + if (!tree->IsDirectory) + return; + + String name = tree->Name; + + ImGui.TreeNodeFlags flags = .OpenOnArrow | .SpanAvailWidth; + + if(tree.Children.Count == 0) + flags |= .Leaf; + + if (tree->Path == _currentDirectory) + { + flags |= .Selected; + } + + bool isOpen = ImGui.TreeNodeEx(name, flags, $"{name}"); + + if (ImGui.IsItemClicked(.Left)) + { + _currentDirectory.Set(tree->Path); + } + + if(isOpen) + { + for(var child in tree.Children) + { + ImGuiPrintEntityTree(child); + } + + ImGui.TreePop(); + } + } + + private static Vector2 DirectoryItemSize = .(110, 110); + + const Vector2 padding = .(24, 24); + + /// Renders the contents of _currentDirectory. + private void DrawCurrentDirectory() + { + if (_currentDirectory.IsEmpty) + return; + + ImGui.Style* style = ImGui.GetStyle(); + + float window_visible_x2 = ImGui.GetWindowPos().x + ImGui.GetWindowContentRegionMax().x; + + // Get the node of the current directory. + var currentDirectoryNode = _manager.AssetHierarchy.GetNodeFromPath(_currentDirectory); + + if (currentDirectoryNode case .Err) + { + Log.EngineLogger.Error($"No node exists for {_currentDirectory}."); + ImGui.TextUnformatted("Failed to display contents of directory."); + return; + } + + if (currentDirectoryNode->Parent != null) + { + ImGui.PushID("Back"); + + DrawBackButton(currentDirectoryNode->Parent); + + // X-Coordinate of the right side of the current entry. + float currentButtonRight = ImGui.GetItemRectMax().x; + // Expected right-Coordinate if next entry was on the same line. + float expectedButtonRight = currentButtonRight + style.ItemSpacing.x + DirectoryItemSize.X; + + // If the next button won't fit on the same line we start a new line. + if (expectedButtonRight < window_visible_x2) + ImGui.SameLine(); + + ImGui.PopID(); + } + + for (var entry in currentDirectoryNode->Children) + { + ImGui.PushID(entry->Name); + + DrawDirectoryItem(entry); + + // X-Coordinate of the right side of the current entry. + float currentButtonRight = ImGui.GetItemRectMax().x; + // Expected right-Coordinate if next entry was on the same line. + float expectedButtonRight = currentButtonRight + style.ItemSpacing.x + DirectoryItemSize.X; + + // If we aren't the last entry and the next button won't fit on the same line we start a new line. + if (entry != currentDirectoryNode->Children.Back && expectedButtonRight < window_visible_x2) + ImGui.SameLine(); + + ImGui.PopID(); + } + } + + /// Renders the button for the given directory item. + private void DrawBackButton(TreeNode entry) + { + ImGui.BeginChild("item", (.)DirectoryItemSize); + + if (entry->Path == _selectedFile) + { + var color = ImGui.GetStyleColorVec4(.ButtonHovered); + ImGui.PushStyleColor(.Button, *color); + } + else + { + ImGui.PushStyleColor(.Button, ImGui.Vec4(0, 0, 0, 0)); + } + + SubTexture2D image = s_FolderTexture; + + ImGui.ImageButton(image, (.)(DirectoryItemSize - padding)); + + ImGui.PopStyleColor(); + + if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(.Left)) + { + if (_selectedFile != entry->Path) + { + _selectedFile.Set(entry->Path); + } + } + + if (ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(.Left)) + { + EntryDoubleClicked(entry); + } + + ImGui.TextUnformatted(".."); + + ImGui.EndChild(); + } + + /// Renders the button for the given directory item. + private void DrawDirectoryItem(TreeNode entry) + { + ImGui.BeginChild("item", (.)DirectoryItemSize); + + if (entry->Path == _selectedFile) + { + var color = ImGui.GetStyleColorVec4(.ButtonHovered); + ImGui.PushStyleColor(.Button, *color); + } + else + { + ImGui.PushStyleColor(.Button, ImGui.Vec4(0, 0, 0, 0)); + } + + // TODO: preview images + SubTexture2D image = entry->IsDirectory ? s_FolderTexture : s_FileTexture; + + ImGui.ImageButton(image, (.)(DirectoryItemSize - padding)); + + ImGui.PopStyleColor(); + + if (ImGui.BeginDragDropSource()) + { + String fullpath = scope String(entry->Path); + + // TODO: this is dirty + if (fullpath.StartsWith(_manager.ContentDirectory, .OrdinalIgnoreCase)) + fullpath.Remove(0, _manager.ContentDirectory.Length); + + Path.Fixup(fullpath); + + ImGui.SetDragDropPayload("CONTENT_BROWSER_ITEM", fullpath.CStr(), (.)fullpath.Length, .Once); + + ImGui.EndDragDropSource(); + } + + if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(.Left)) + { + if (_selectedFile != entry->Path) + { + _selectedFile.Set(entry->Path); + } + } + + if (ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(.Left)) + { + EntryDoubleClicked(entry); + } + + ImGui.TextUnformatted(entry->Name); + + if (ImGui.BeginPopupContextWindow()) + { + ShowItemContextMenu(entry); + ImGui.EndPopup(); + } + + if (entry->SubAssets?.Count > 0) + { + // Button for revealing sub assets (e.g. Meshes in 3D-Model) + + ImGui.SameLine(); + if (ImGui.Button(">")) + ImGui.OpenPopup("SubAssets"); + } + + if (ImGui.BeginPopup("SubAssets", .Popup)) + { + for (var subAsset in entry->SubAssets) + { + ImGui.Button(subAsset.Name); + + if (ImGui.BeginDragDropSource()) + { + String fullpath = scope String(entry->Path); + fullpath.AppendF($"#{subAsset.Name}"); + + ImGui.SetDragDropPayload("CONTENT_BROWSER_ITEM", fullpath.CStr(), (.)fullpath.Length, .Once); + + ImGui.EndDragDropSource(); + } + } + + ImGui.EndPopup(); + } + + DeleteItemPopup(entry); + + ImGui.EndChild(); + } + + private void DeleteItemPopup(TreeNode fileOrFolder) + { + // Always center this window when appearing + ImGui.Vec2 center = ImGui.GetMainViewport().GetCenter(); + ImGui.SetNextWindowPos(center, .Appearing, ImGui.Vec2(0.5f, 0.5f)); + + // TODO: fix delete popup + + if (ImGui.BeginPopupModal("Delete?", null, .AlwaysAutoResize)) + { + ImGui.Text($""" + Delete "{fileOrFolder->Name}"? + + + """); + + ImGui.Separator(); + + if (ImGui.Button("Yes", ImGui.Vec2(120, 0))) + { + ImGui.CloseCurrentPopup(); + } + + ImGui.SetItemDefaultFocus(); + ImGui.SameLine(); + + if (ImGui.Button("Cancel", ImGui.Vec2(120, 0))) + { + ImGui.CloseCurrentPopup(); + } + + ImGui.EndPopup(); + } + } + + /// Shows the context menu for the given file/folder. + private void ShowItemContextMenu(TreeNode fileOrFolder) + { + bool isFile = !fileOrFolder->IsDirectory; + + if (ImGui.MenuItem("Show in file browser...")) + { + if (Path.OpenFolderAndSelectItem(fileOrFolder->Path) case .Err) + { + Log.EngineLogger.Error("Failed to show path in file browser."); + } + } + + if (isFile && ImGui.MenuItem("Open file with...")) + { + if (Path.OpenWithDialog(fileOrFolder->Path) case .Err) + { + Log.EngineLogger.Error("Failed to show \"Open with...\" dialog."); + } + } + + if (ImGui.MenuItem("Delete")) + { + ImGui.OpenPopup("Delete?"); + } + } + + private void EntryDoubleClicked(TreeNode entry) + { + if (entry->IsDirectory) + { + _currentDirectory.Set(entry->Path); + } + else + { + if (Path.OpenFolder(entry->Path) case .Err) + Log.EngineLogger.Error("Failed to open directory in file browser."); + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/src/EditWindows/EditorViewportWindow.bf b/GlitchyEditor/src/EditWindows/EditorViewportWindow.bf new file mode 100644 index 0000000..56a48f3 --- /dev/null +++ b/GlitchyEditor/src/EditWindows/EditorViewportWindow.bf @@ -0,0 +1,334 @@ +using System; +using ImGui; +using GlitchyEngine.Renderer; +using GlitchyEngine.Math; +using GlitchyEngine; +using GlitchyEngine.World; +using ImGuizmo; +using GlitchyEngine.Events; + +namespace GlitchyEditor.EditWindows +{ + class EditorViewportWindow : EditorWindow + { + public const String s_WindowTitle = "Scene"; + + private ImGui.Vec2 _oldViewportSize = .(100, 100); + private bool _viewPortChanged; + + /// True if the cursor wrapped from one side of the viewport to the other last frame. + private bool _wrappedCursor; + + private RenderTargetGroup _renderTarget ~ _?.ReleaseRef(); + + private ImGuizmo.OPERATION _gizmoType = .TRANSLATE; + private ImGuizmo.MODE _gizmoMode = .LOCAL; + private float _snap = 0.5f; + private float _angleSnap = 45.0f; + private bool _doSnap = false; + + private bool _visible; + + public uint32 SelectedEntityId {get; private set; } + public bool SelectionChanged { get; private set; } + + public Vector2 ViewportSize => (Vector2)_oldViewportSize; + // Occurs when the viewport is resized. + public Event> ViewportSizeChanged ~ _.Dispose(); + // Occurs when an entity was clicked. + public Event> EntityClicked ~ _.Dispose(); + + /// The render target that is shown in the viewport window. + public RenderTargetGroup RenderTarget + { + get => _renderTarget; + set + { + if(_renderTarget == value) + return; + + SetReference!(_renderTarget, value); + } + } + + /// Gets or sets whether the editor functionality (gizmo, picking etc.) is enabled. + public bool EditorMode { get; set; } = true + + public bool Visible => _visible; + + public this(Editor editor) + { + _editor = editor; + } + + protected override void InternalShow() + { + ImGui.PushStyleVar(.WindowPadding, ImGui.Vec2(1, 1)); + defer ImGui.PopStyleVar(); + + if(!ImGui.Begin(s_WindowTitle, &_open, .NoScrollbar | .MenuBar)) + { + ImGui.End(); + + _visible = false; + return; + } + + _visible = true; + + let viewportSize = ImGui.GetContentRegionAvail(); + + if(ImGui.IsWindowHovered() && Input.IsMouseButtonPressing(.RightButton)) + { + let currentWindow = ImGui.GetCurrentWindow(); + ImGui.FocusWindow(currentWindow); + } + + _hasFocus = ImGui.IsWindowFocused(); + + ShowMenuBar(); + + if (_editor.CurrentCamera.[Friend]BindMouse && _hasFocus) + WrapMouseInViewport(); + + // If we wrapped this frame we weren't hovering because the cursor has to be be out of bounds to wrap + _editor.CurrentCamera.AllowMove = _hasFocus && (ImGui.IsWindowHovered() || _wrappedCursor); + + if (_hasFocus && !_editor.CurrentCamera.InUse) + { + if (Input.IsKeyPressing(.Q)) + _gizmoType = .TRANSLATE; + if (Input.IsKeyPressing(.W)) + _gizmoType = .ROTATE; + if (Input.IsKeyPressing(.E)) + _gizmoType = .SCALE; + + if (Input.IsKeyPressing(.G)) + _gizmoMode = .WORLD; + if (Input.IsKeyPressing(.L)) + _gizmoMode = .LOCAL; + } + + if(_renderTarget != null) + { + ImGui.Image(_renderTarget.GetViewBinding(0), viewportSize); + //ImGui.Image(_editor.CurrentCamera.RenderTarget.GetViewBinding(0), viewportSize); + //ImGui.Image(_editor.CurrentScene.[Friend]_compositeTarget.GetViewBinding(0), viewportSize); + } + + HandleDropTarget(); + + bool gizmoUsed = DrawImGuizmo(viewportSize); + + MousePicking(viewportSize, gizmoUsed); + + ImGui.End(); + + if(_oldViewportSize != viewportSize) + { + ViewportSizeChanged.Invoke(this, (Vector2)viewportSize); + _viewPortChanged = true; + _oldViewportSize = viewportSize; + } + } + + /// Provides the ImGui Drop target and handles dropped payload. + private void HandleDropTarget() + { + if (ImGui.BeginDragDropTarget()) + { + ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM"); + + if (payload != null) + { + StringView path = .((char8*)payload.Data, (int)payload.DataSize); + _editor.RequestOpenScene(this, path); + } + + ImGui.EndDragDropTarget(); + } + } + + /// Wraps the mouse, so that it always stays in the viewport. + private void WrapMouseInViewport() + { + _wrappedCursor = false; + + let mousePos = (Vector2)ImGui.GetMousePos(); + let winPos = (Vector2)ImGui.GetWindowPos(); + let regionMin = winPos + (Vector2)ImGui.GetWindowContentRegionMin(); + let regionMax = winPos + (Vector2)ImGui.GetWindowContentRegionMax(); + + ImGui.DrawRect((.)regionMin, (.)regionMax, .(0, 255, 0)); + + Vector2 newMousePos = mousePos; + + if (mousePos.X < regionMin.X + 1) + { + newMousePos.X = regionMax.X - 2; + } + else if (mousePos.X > regionMax.X - 1) + { + newMousePos.X = regionMin.X + 2; + } + + if (mousePos.Y < regionMin.Y + 1) + { + newMousePos.Y = regionMax.Y - 100; + } + else if (mousePos.Y > regionMax.Y - 1) + { + newMousePos.Y = regionMin.Y + 10; + } + + if (newMousePos != mousePos) + { + Input.SetMousePosition((Int2)newMousePos); + // After wrapping the cursor the the other side, the camera controller must not compare the positions, + // because the delta doesn't represent the correct movement of the cursor. + // TODO: can be solved by using direct mouse movement instead of comparing positions + _editor.CurrentCamera.[Friend]MouseCooldown = 2; + _wrappedCursor = true; + } + } + + /// If the user clicks, the entity beneath the cursor will be selected. + private void MousePicking(ImGui.Vec2 viewportSize, bool gizmoUsed) + { + Vector2 relativeMouse = (Vector2)ImGui.GetMousePos() - (Vector2)ImGui.GetItemRectMin(); + + int rtWidth = _editor.EditorSceneRenderer.CompositeTarget.Width; + int rtHeight = _editor.EditorSceneRenderer.CompositeTarget.Height; + + if (Input.IsMouseButtonPressing(.LeftButton) && + ImGui.IsWindowHovered() && !gizmoUsed && !_editor.CurrentCamera.InUse && + relativeMouse.X >= 0 && relativeMouse.Y >= 0 && + relativeMouse.X < viewportSize.x && relativeMouse.Y < viewportSize.y && + relativeMouse.X < rtWidth && relativeMouse.Y < rtHeight) + { + uint32 id = uint32.MaxValue; + + _editor.EditorSceneRenderer.CompositeTarget.GetData(&id, 1, (.)relativeMouse.X, (.)relativeMouse.Y, 1, 1); + + SelectionChanged = true; + SelectedEntityId = id; + + EntityClicked(this, id); + } + else + { + SelectionChanged = false; + } + } + + private void ShowMenuBar() + { + if(ImGui.BeginMenuBar()) + { + if (ImGui.RadioButton("Position", _gizmoType.HasFlag(.TRANSLATE))) + { + if (Input.IsKeyPressed(Key.Control)) + _gizmoType ^= .TRANSLATE; + else + _gizmoType = .TRANSLATE; + } + + if (ImGui.RadioButton("Rotation", _gizmoType.HasFlag(.ROTATE))) + { + if (Input.IsKeyPressed(Key.Control)) + _gizmoType ^= .ROTATE; + else + _gizmoType = .ROTATE; + } + + if (ImGui.RadioButton("Scale", _gizmoType.HasFlag(.SCALE))) + { + if (Input.IsKeyPressed(Key.Control)) + _gizmoType ^= .SCALE; + else + _gizmoType = .SCALE; + } + + if (ImGui.RadioButton("All", _gizmoType == .TRANSLATE | .ROTATE | .SCALE)) + _gizmoType = .TRANSLATE | .ROTATE | .SCALE; + + // If we scale, the mode must be local otherwise we could skew the matrix. + if (_gizmoType.HasFlag(.SCALE)) + _gizmoMode = .LOCAL; + + if (ImGui.MenuItem(_gizmoMode == .WORLD ? "Global" : "Local", null, true, !_gizmoType.HasFlag(.SCALE))) + { + if (_gizmoMode == .WORLD) + _gizmoMode = .LOCAL; + else + _gizmoMode = .WORLD; + } + + _doSnap = Input.IsKeyPressed(.Shift); + + ImGui.PushItemWidth(100); + + if (_gizmoType.HasFlag(.ROTATE)) + { + ImGui.DragFloat("Angle Snap", &_angleSnap, 1.0f, 0.0f, 180.0f); + } + + if (_gizmoType.HasFlag(.TRANSLATE) || _gizmoType.HasFlag(.SCALE)) + { + ImGui.DragFloat("Snap", &_snap, 0.1f, 0.0f, float.MaxValue); + } + + ImGui.PopItemWidth(); + + ImGui.EndMenuBar(); + } + } + + private bool DrawImGuizmo(ImGui.Vec2 viewportSize) + { + // TODO: Needed if we have a orthographic editor-camera (or support gizmos in the Play-Window, where we can also have ortho projections) + ImGuizmo.SetOrthographic(false); + ImGuizmo.SetDrawlist(); + + var topLeft = ImGui.GetWindowPos(); + var cntMin = ImGui.GetWindowContentRegionMin(); + + topLeft.x += cntMin.x; + topLeft.y += cntMin.y; + ImGuizmo.SetRect(topLeft.x, topLeft.y, viewportSize.x, viewportSize.y); + + var view = _editor.CurrentCamera.View; + var projection = _editor.CurrentCamera.Projection; + + if(_editor.EntityHierarchyWindow.SelectedEntities.Count == 0) + return false; + + var entity = _editor.EntityHierarchyWindow.SelectedEntities.Back; + + var transformCmp = entity.GetComponent(); + + var worldTransform = transformCmp.WorldTransform; + + Matrix parentView = .Identity; + if (transformCmp.Parent != .InvalidEntity) + { + var parentTransformCmp = Entity(transformCmp.Parent, entity.Scene).GetComponent(); + parentView = parentTransformCmp.WorldTransform.Invert(); + } + + Vector3 snap = .(_snap); + if (_gizmoType.HasFlag(.ROTATE)) + snap = .(_angleSnap); + + if (ImGuizmo.Manipulate((.)&view, (.)&projection, _gizmoType, _gizmoMode, (.)&worldTransform, null, _doSnap ? (.)&snap : null)) + { + // TODO: Fix when parent is scaled + // Seems to work fine for parent rotation and translation but scaled parent ruins everything + // (probably because scaling a rotated matrix results in a skewed matrix, but unity can do it and so should we) + transformCmp.LocalTransform = parentView * worldTransform; + } + + return ImGuizmo.IsUsing(); + } + } +} diff --git a/GlitchyEditor/src/EditWindows/EditorWindow.bf b/GlitchyEditor/src/EditWindows/EditorWindow.bf index 35c724d..b79ca1c 100644 --- a/GlitchyEditor/src/EditWindows/EditorWindow.bf +++ b/GlitchyEditor/src/EditWindows/EditorWindow.bf @@ -1,3 +1,4 @@ +using System; namespace GlitchyEditor.EditWindows { abstract class EditorWindow diff --git a/GlitchyEditor/src/EditWindows/EntityHierarchyWindow.bf b/GlitchyEditor/src/EditWindows/EntityHierarchyWindow.bf index f160afa..b969fd6 100644 --- a/GlitchyEditor/src/EditWindows/EntityHierarchyWindow.bf +++ b/GlitchyEditor/src/EditWindows/EntityHierarchyWindow.bf @@ -23,16 +23,58 @@ namespace GlitchyEditor.EditWindows public List SelectedEntities => _selectedEntities; - public this(Scene scene) + public this(Editor editor, Scene scene) { + _editor = editor; SetContext(scene); } public void SetContext(Scene scene) { + ClearEntitySelection(); _scene = scene; } - + + /*public bool SelectEntityWithId(uint32 id, bool addToSelection = false) + { + if (!addToSelection) + _selectedEntities.Clear(); + + _scene.[Friend]_ecsWorld.IsValid(id); + + _selectedEntities.Add(); + }*/ + + /// Deselects all entities. + public void ClearEntitySelection() + { + _selectedEntities.Clear(); + } + + /// Selects the given entity. + /// @param entity The entity to select. + /// @param clearOldSelection If true the previously selected entities will be deselected. If false, the given entity will be added to the current selection. + public void SelectEntity(Entity entity, bool clearOldSelection = false) + { + if (clearOldSelection) + ClearEntitySelection(); + + _selectedEntities.Add(entity); + } + + /// Deselects the given entity. + /// @param entity The entity to deselect. + public bool DeselectEntity(Entity entity) + { + return _selectedEntities.Remove(entity); + } + + /// Returns whether or not the given entity is currently selected. + public bool IsEntitySelected(Entity entity) + { + return _selectedEntities.Contains(entity); + } + protected override void InternalShow() { if(!ImGui.Begin(s_WindowTitle, &_open, .MenuBar)) @@ -50,10 +92,26 @@ namespace GlitchyEditor.EditWindows ImGui.EndPopup(); } + if (_editor.SceneViewportWindow.SelectionChanged) + { + var handle = _scene.[Friend]_ecsWorld.GetCurrentVersion(EcsEntity.[Friend]CreateEntityID(_editor.SceneViewportWindow.SelectedEntityId, 0)); + + if (handle case .Ok(let h)) + { + Entity e = .(h, _scene); + + SelectEntity(e, Input.IsKeyReleased(.Control)); + } + else if (Input.IsKeyReleased(.Control)) + { + ClearEntitySelection(); + } + } + ShowEntityHierarchy(); if ((ImGui.IsMouseDown(.Left) || ImGui.IsMouseDown(.Right)) && !ImGui.IsAnyItemHovered() && !ImGui.GetIO().KeyCtrl && ImGui.IsWindowHovered(.AllowWhenBlockedByPopup)) - _selectedEntities.Clear(); + ClearEntitySelection(); ImGui.End(); } @@ -102,6 +160,8 @@ namespace GlitchyEditor.EditWindows { _scene.DestroyEntity(entity, true); } + + _selectedEntities.Clear(); } private void ShowEntityHierarchyMenuBar() @@ -241,11 +301,11 @@ namespace GlitchyEditor.EditWindows { String name = null; - var nameComponent = tree.Value.GetComponent(); + var nameComponent = tree.Value.GetComponent(); if(nameComponent != null) { - name = nameComponent.DebugName; + name = scope:: .(nameComponent.Name); } else { @@ -257,7 +317,7 @@ namespace GlitchyEditor.EditWindows if(tree.Children.Count == 0) flags |= .Leaf; - bool inSelectedList = _selectedEntities.Contains(tree.Value); + bool inSelectedList = IsEntitySelected(tree.Value); if(inSelectedList) flags |= .Selected; @@ -350,17 +410,12 @@ namespace GlitchyEditor.EditWindows { if (inSelectedList && !clickedRight) { - _selectedEntities.Remove(tree.Value); + DeselectEntity(tree.Value); inSelectedList = false; } else { - if (!ImGui.GetIO().KeyCtrl && !clickedRight) - { - _selectedEntities.Clear(); - } - - _selectedEntities.Add(tree.Value); + SelectEntity(tree.Value, !ImGui.GetIO().KeyCtrl && !clickedRight); inSelectedList = true; } } @@ -446,13 +501,13 @@ namespace GlitchyEditor.EditWindows { Entity entity = .(entityId, _scene); - String name = null; + StringView name = null; - var nameComponent = entity.GetComponent(); + var nameComponent = entity.GetComponent(); if(nameComponent != null) { - name = nameComponent.DebugName; + name = nameComponent.Name; } else { diff --git a/GlitchyEditor/src/EditWindows/GameViewportWindow.bf b/GlitchyEditor/src/EditWindows/GameViewportWindow.bf new file mode 100644 index 0000000..271e7bf --- /dev/null +++ b/GlitchyEditor/src/EditWindows/GameViewportWindow.bf @@ -0,0 +1,106 @@ +using System; +using ImGui; +using GlitchyEngine.Renderer; +using GlitchyEngine.Math; +using GlitchyEngine; +using GlitchyEngine.World; +using ImGuizmo; +using GlitchyEngine.Events; + +namespace GlitchyEditor.EditWindows +{ + class GameViewportWindow : EditorWindow + { + public const String s_WindowTitle = "Game"; + + private ImGui.Vec2 _oldViewportSize = .(100, 100); + private bool _viewPortChanged; + + private bool _visible; + + private RenderTargetGroup _renderTarget ~ _?.ReleaseRef(); + + public Vector2 ViewportSize => (Vector2)_oldViewportSize; + // Occurs when the viewport is resized. + public Event> ViewportSizeChanged ~ _.Dispose(); + // Occurs when an entity was clicked. + public Event> EntityClicked ~ _.Dispose(); + + /// The render target that is shown in the viewport window. + public RenderTargetGroup RenderTarget + { + get => _renderTarget; + set + { + if(_renderTarget == value) + return; + + SetReference!(_renderTarget, value); + } + } + + public bool Visible => _visible; + + public this(Editor editor) + { + _editor = editor; + } + + protected override void InternalShow() + { + ImGui.PushStyleVar(.WindowPadding, ImGui.Vec2(1, 1)); + defer ImGui.PopStyleVar(); + + if(!ImGui.Begin(s_WindowTitle, &_open, .NoScrollbar | .MenuBar)) + { + ImGui.End(); + + _visible = false; + return; + } + + _visible = true; + + let viewportSize = ImGui.GetContentRegionAvail(); + + if(ImGui.IsWindowHovered() && Input.IsMouseButtonPressing(.RightButton)) + { + let currentWindow = ImGui.GetCurrentWindow(); + ImGui.FocusWindow(currentWindow); + } + + _hasFocus = ImGui.IsWindowFocused(); + + if(_renderTarget != null) + { + ImGui.Image(_renderTarget.GetViewBinding(0), viewportSize); + } + + ImGui.End(); + + if(_oldViewportSize != viewportSize) + { + ViewportSizeChanged.Invoke(this, (Vector2)viewportSize); + _viewPortChanged = true; + _oldViewportSize = viewportSize; + } + } + + /// Provides the ImGui Drop target and handles dropped payload. + private void HandleDropTarget() + { + if (ImGui.BeginDragDropTarget()) + { + ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM"); + + if (payload != null) + { + StringView path = .((char8*)payload.Data, (int)payload.DataSize); + _editor.RequestOpenScene(this, path); + } + + ImGui.EndDragDropTarget(); + } + } + } +} diff --git a/GlitchyEditor/src/EditWindows/PropertiesWindow.bf b/GlitchyEditor/src/EditWindows/PropertiesWindow.bf new file mode 100644 index 0000000..a4195f8 --- /dev/null +++ b/GlitchyEditor/src/EditWindows/PropertiesWindow.bf @@ -0,0 +1,127 @@ +using ImGui; +using System; +using GlitchyEngine.Collections; +using GlitchyEngine.Content; +using System.Reflection; +using GlitchyEngine; +using GlitchyEditor.Assets; + +namespace GlitchyEditor.EditWindows; + +class PropertiesWindow : EditorWindow +{ + public const String s_WindowTitle = "Properties"; + + private AssetPropertiesEditor _currentPropertiesEditor ~ delete _; + + private bool _lockCurrentAsset; + + private bool _selectedNewAsset; + + private append String _selectedFileName = .(); + + private AssetHandle _currentAssetHandle; + + public this(Editor editor) + { + _editor = editor; + } + + protected override void InternalShow() + { + defer { ImGui.End(); } + if(!ImGui.Begin(s_WindowTitle, &_open, .None)) + return; + + // TODO: make a little button in title bar? + ImGui.Checkbox("Lock", &_lockCurrentAsset); + ImGui.Separator(); + + ShowAssetProperties(); + } + + /// Gets the AssetFile for the asset currently selected in the ContentBrowserWindow + /// @returns the AssetFile for the currently selected asset of null, if no file is selected. + private AssetFile GetCurrentAssetFile() + { + // Only grab the currently selected file if we aren't locked + if (!_lockCurrentAsset) + { + StringView selectedInFileBrowser = _editor.ContentBrowserWindow.SelectedFile; + + if (_selectedFileName != selectedInFileBrowser) + { + _selectedFileName.Set(_editor.ContentBrowserWindow.SelectedFile); + } + } + + Result> treeNode = _editor.ContentManager.AssetHierarchy.GetNodeFromPath(_selectedFileName); + + if (treeNode case .Ok(let assetNode)) + return assetNode->AssetFile; + + return null; + } + + private void ShowAssetProperties() + { + AssetFile assetFile = GetCurrentAssetFile(); + + if (_currentPropertiesEditor?.Asset != assetFile) + { + delete _currentPropertiesEditor; + _currentPropertiesEditor = _editor.ContentManager.GetNewPropertiesEditor(assetFile); + } + + if (assetFile == null) + return; + + Asset asset = _editor.ContentManager.GetAsset(null, _currentAssetHandle); + + // We need the actual asset for preview and sometimes for editing + if (asset?.Identifier != assetFile.Identifier) + { + _currentAssetHandle = _editor.ContentManager.LoadAsset(assetFile.Identifier); + } + + // TODO: allow changing AssetLoader + // assetFile.AssetConfig.AssetLoade + + // TODO: ignore file + /*ImGui.Checkbox("Ignore", &assetFile.AssetConfig.IgnoreFile); + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("If checked this file will be ignored and not treated as an asset.");*/ + + ShowPropertiesEditor(assetFile); + + ImGui.Separator(); + + // TODO: preview asset + } + + private void ShowPropertiesEditor(AssetFile assetFile) + { + if (_currentPropertiesEditor == null) + return; + + _currentPropertiesEditor.ShowEditor(); + + if (ImGui.Button("Save Asset")) + { + Asset asset = _editor.ContentManager.GetAsset(null, _currentAssetHandle); + _editor.ContentManager.SaveAsset(asset); + } + + if (!assetFile.AssetConfig.Config.Changed) + { + ImGui.BeginDisabled(); + defer:: { ImGui.EndDisabled(); } + } + + ImGui.Separator(); + + if (ImGui.Button("Apply")) + assetFile.SaveAssetConfig(); + } +} diff --git a/GlitchyEditor/src/EditWindows/SceneViewportWindow.bf b/GlitchyEditor/src/EditWindows/SceneViewportWindow.bf deleted file mode 100644 index 357cbd2..0000000 --- a/GlitchyEditor/src/EditWindows/SceneViewportWindow.bf +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using ImGui; -using GlitchyEngine.Renderer; -using GlitchyEngine.Math; -using GlitchyEngine; -using GlitchyEngine.World; -using ImGuizmo; - -namespace GlitchyEditor.EditWindows -{ - class SceneViewportWindow : EditorWindow - { - //public OldCamera _camera; - - public const String s_WindowTitle = "Scene"; - - private RenderTarget2D _renderTarget ~ _?.ReleaseRef(); - - public Event> ViewportSizeChangedEvent ~ _.Dispose(); - - public RenderTarget2D RenderTarget - { - get => _renderTarget; - set - { - if(_renderTarget == value) - return; - - SetReference!(_renderTarget, value); - } - } - - public this(Editor editor) - { - _editor = editor; - } - - private ImGui.Vec2 oldViewportSize; - private bool viewPortChanged; - - public Entity CameraEntity { get; set; } - - protected override void InternalShow() - { - ImGui.PushStyleVar(.WindowPadding, ImGui.Vec2(1, 1)); - defer ImGui.PopStyleVar(); - - if(!ImGui.Begin(s_WindowTitle, &_open, .NoScrollbar)) - { - ImGui.End(); - return; - } - - if(ImGui.IsWindowHovered() && Input.IsMouseButtonPressing(.RightButton)) - { - var currentWindow = ImGui.GetCurrentWindow(); - ImGui.FocusWindow(currentWindow); - } - - _hasFocus = ImGui.IsWindowFocused(); - - var viewportSize = ImGui.GetContentRegionAvail(); - - if(_renderTarget != null) - { - ImGui.Image(_renderTarget, viewportSize); - } - - DrawImGuizmo(viewportSize); - - ImGui.End(); - - if(oldViewportSize != viewportSize) - { - ViewportSizeChangedEvent.Invoke(this, (Vector2)viewportSize); - viewPortChanged = true; - oldViewportSize = viewportSize; - } - } - - private void DrawImGuizmo(ImGui.Vec2 viewportSize) - { - ImGuizmo.SetDrawlist(); - - var topLeft = ImGui.GetWindowPos(); - var cntMin = ImGui.GetWindowContentRegionMin(); - - topLeft.x += cntMin.x; - topLeft.y += cntMin.y; - ImGuizmo.SetRect(topLeft.x, topLeft.y, viewportSize.x, viewportSize.y); - - var cameraTransformCmp = CameraEntity.GetComponent(); - var view = cameraTransformCmp.WorldTransform.Invert(); - - var cameraCmp = CameraEntity.GetComponent(); - var projection = cameraCmp.Camera.Projection; - - Matrix mat = .Identity; - ImGuizmo.DrawGrid((.)&view, (.)&projection, (.)&mat, 10); - - if(_editor.SelectedEntities.Count > 0) - { - var entity = _editor.SelectedEntities.Front; - - var transformCmp = _editor.World.GetComponent(entity); - - var transform = transformCmp.LocalTransform; - - ImGuizmo.SetRect(topLeft.x, topLeft.y, viewportSize.x, viewportSize.y); - - ImGuizmo.Manipulate((.)&view, (.)&projection, .TRANSLATE, .LOCAL, (.)&transform); - - transformCmp.LocalTransform = transform; - } - } - } -} diff --git a/GlitchyEditor/src/Editor.bf b/GlitchyEditor/src/Editor.bf index 27027ca..5e1b274 100644 --- a/GlitchyEditor/src/Editor.bf +++ b/GlitchyEditor/src/Editor.bf @@ -4,115 +4,80 @@ using System; using System.Collections; using GlitchyEngine.Collections; using GlitchyEditor.EditWindows; +using GlitchyEngine; +using GlitchyEditor.Assets; namespace GlitchyEditor { class Editor { - private EcsWorld _ecsWorld; private Scene _scene; - + + private EditorContentManager _contentManager; + private EntityHierarchyWindow _entityHierarchyWindow ~ delete _; private ComponentEditWindow _componentEditWindow ~ delete _; - private SceneViewportWindow _sceneViewportWindow = new .(this) ~ delete _; + private EditorViewportWindow _sceneViewportWindow ~ delete _; + private GameViewportWindow _gameViewportWindow ~ delete _; + private ContentBrowserWindow _contentBrowserWindow ~ delete _; + private PropertiesWindow _propertiesWindow ~ delete _; - private List _selectedEntities = new .() ~ delete _; + public Scene CurrentScene + { + get => _scene; + set + { + if (_scene == value) + return; - public EcsWorld World => _ecsWorld; - - public List SelectedEntities => _selectedEntities; + _scene = value; + _entityHierarchyWindow.SetContext(_scene); + } + } + public EditorContentManager ContentManager => _contentManager; + public EntityHierarchyWindow EntityHierarchyWindow => _entityHierarchyWindow; public ComponentEditWindow ComponentEditWindow => _componentEditWindow; - public SceneViewportWindow SceneViewportWindow => _sceneViewportWindow; + public EditorViewportWindow SceneViewportWindow => _sceneViewportWindow; + public GameViewportWindow GameViewportWindow => _gameViewportWindow; + public ContentBrowserWindow ContentBrowserWindow => _contentBrowserWindow; + public PropertiesWindow PropertiesWindow => _propertiesWindow; + + public EditorCamera* CurrentCamera { get; set; } + + public Event> RequestOpenScene ~ _.Dispose(); + + public SceneRenderer GameSceneRenderer {get; set;} + public SceneRenderer EditorSceneRenderer {get; set;} /// Creates a new editor for the given world - public this(Scene scene) + public this(Scene scene, EditorContentManager contentManager) { _scene = scene; - _ecsWorld = _scene.[Friend]_ecsWorld; + _contentManager = contentManager; - _entityHierarchyWindow = new EntityHierarchyWindow(_scene); + InitWindows(); + } + + private void InitWindows() + { + _sceneViewportWindow = new EditorViewportWindow(this); + _gameViewportWindow = new GameViewportWindow(this); + _entityHierarchyWindow = new EntityHierarchyWindow(this, _scene); _componentEditWindow = new ComponentEditWindow(_entityHierarchyWindow); + _contentBrowserWindow = new ContentBrowserWindow((.)Application.Get().ContentManager); + _propertiesWindow = new PropertiesWindow(this); } public void Update() { + _sceneViewportWindow.Show(); + _gameViewportWindow.Show(); _entityHierarchyWindow.Show(); _componentEditWindow.Show(); - _sceneViewportWindow.Show(); - } - - /// Creates a new entity with a transform component. - internal EcsEntity CreateEntityWithTransform() - { - var entity = _ecsWorld.NewEntity(); - - var transformComponent = ref *_ecsWorld.AssignComponent(entity); - transformComponent = TransformComponent(); - - var nameComponent = ref *_ecsWorld.AssignComponent(entity); - nameComponent.SetName("Entity"); - - return entity; - } - - - /// Returns whether or not all selected entities have the same parent. - internal bool AllSelectionsOnSameLevel() - { - EcsEntity? parent = .InvalidEntity; - - for(var selectedEntity in _selectedEntities) - { - var parentComponent = _ecsWorld.GetComponent(selectedEntity); - - if(parent == .InvalidEntity) - { - parent = parentComponent?.Entity; - } - else if(parentComponent?.Entity != parent) - { - return false; - } - } - - return true; - } - - /// Finds all children of the given entity and stores their IDs in the given list. - internal void FindChildren(EcsEntity entity, List entities) - { - for(var (child, childParent) in _ecsWorld.Enumerate()) - { - if(childParent.Entity == entity) - { - if(!entities.Contains(child)) - entities.Add(child); - - FindChildren(child, entities); - } - } - } - - /// Deletes all selected entities and their children. - internal void DeleteSelectedEntities() - { - List entities = scope .(); - - for(var entity in _selectedEntities) - { - entities.Add(entity); - - FindChildren(entity, entities); - } - - for(var entity in entities) - { - _ecsWorld.RemoveEntity(entity); - } - - _selectedEntities.Clear(); + _contentBrowserWindow.Show(); + _propertiesWindow.Show(); } } } diff --git a/GlitchyEditor/src/EditorApp.bf b/GlitchyEditor/src/EditorApp.bf index e2ddfc7..dbc7802 100644 --- a/GlitchyEditor/src/EditorApp.bf +++ b/GlitchyEditor/src/EditorApp.bf @@ -1,13 +1,41 @@ using System; using GlitchyEngine; +using GlitchyEngine.Content; +using GlitchyEditor.Assets; namespace GlitchyEditor { class EditorApp : Application { + EditorContentManager _contentManager; + public this() { - PushLayer(new EditorLayer()); + PushLayer(new EditorLayer(_contentManager)); + } + + protected override IContentManager InitContentManager() + { + _contentManager = new EditorContentManager(); + _contentManager.RegisterAssetLoader(); + _contentManager.SetAsDefaultAssetLoader(".png", ".dds"); + _contentManager.SetAssetPropertiesEditor(=> TextureAssetPropertiesEditor.Factory); + + _contentManager.RegisterAssetLoader(); + _contentManager.SetAsDefaultAssetLoader(".glb", ".gltf"); + _contentManager.SetAssetPropertiesEditor(=> ModelAssetPropertiesEditor.Factory); + + _contentManager.RegisterAssetLoader(); + _contentManager.SetAsDefaultAssetLoader(".mat"); + _contentManager.SetAssetPropertiesEditor(=> MaterialAssetPropertiesEditor.Factory); + + _contentManager.RegisterAssetLoader(); + _contentManager.SetAsDefaultAssetLoader(".hlsl"); + _contentManager.SetAssetPropertiesEditor(=> EffectAssetPropertiesEditor.Factory); + + _contentManager.SetContentDirectory("./content"); + + return _contentManager; } [Export, LinkName("CreateApplication")] diff --git a/GlitchyEditor/src/EditorCameraController.bf b/GlitchyEditor/src/EditorCameraController.bf deleted file mode 100644 index e1f61eb..0000000 --- a/GlitchyEditor/src/EditorCameraController.bf +++ /dev/null @@ -1,63 +0,0 @@ -using GlitchyEngine; -using GlitchyEngine.Math; -using GlitchyEngine.World; - -namespace GlitchyEditor -{ - class EditorCameraController : ScriptableEntity - { - private float _cameraTranslationSpeed = 2.0f; - private float _cameraRotationSpeedX = 0.001f; - private float _cameraRotationSpeedY = 0.001f; - - public bool IsEnabled = false; - - protected override void OnUpdate(GameTime gt) - { - if (!IsEnabled) - return; - - Debug.Profiler.ProfileFunction!(); - - Vector3 movement = .(); - - if(Input.IsKeyPressed(Key.W)) - movement.Z += 1; - if(Input.IsKeyPressed(Key.S)) - movement.Z -= 1; - - if(Input.IsKeyPressed(Key.A)) - movement.X -= 1; - if(Input.IsKeyPressed(Key.D)) - movement.X += 1; - - if(Input.IsKeyPressed(Key.Space)) - movement.Y += 1; - if(Input.IsKeyPressed(Key.Control)) - movement.Y -= 1; - - var transformComponent = transform; - - if(movement != .Zero) - { - movement.Normalize(); - - movement *= (float)(gt.FrameTime.TotalSeconds) * _cameraTranslationSpeed; - - Matrix view = transformComponent.WorldTransform.Invert(); - - Vector4 delta = Vector4(movement, 1.0f) * view; - - transformComponent.Position = transformComponent.Position + delta.XYZ; - } - - // Camera rotation - var mouseDelta = Input.GetMouseMovement(); - - float rotY = mouseDelta.X * _cameraRotationSpeedX; - float rotX = mouseDelta.Y * _cameraRotationSpeedY; - - transformComponent.RotationEuler = transformComponent.RotationEuler + .(rotX, rotY, 0); - } - } -} \ No newline at end of file diff --git a/GlitchyEditor/src/EditorContentManager.bf b/GlitchyEditor/src/EditorContentManager.bf new file mode 100644 index 0000000..8ca2fcc --- /dev/null +++ b/GlitchyEditor/src/EditorContentManager.bf @@ -0,0 +1,639 @@ +using System; +using System.IO; +using GlitchyEngine.Collections; +using System.Collections; +using GlitchyEngine.Renderer; +using System.Threading; +using GlitchyEngine.Content; +using GlitchyEditor.Assets; +using GlitchyEngine; +using System.Linq; +using System.Threading.Tasks; +using internal GlitchyEngine.Content.Asset; + +namespace GlitchyEditor; + +class EditorContentManager : IContentManager +{ + private append String _contentDirectory = .(); + + public StringView ContentDirectory => _contentDirectory; + + //private append List _identifiers = .() ~ _.ClearAndDeleteItems(); + + private append Dictionary _identiferToHandle = .(); // TODO: Check if all resources are unloaded + + private append Dictionary _handleToAsset = .(); + + private append AssetHierarchy _assetHierarchy = .(this); + + public AssetHierarchy AssetHierarchy => _assetHierarchy; + + private append List _reloadQueue = .(); + + public this() + { + _assetHierarchy.OnFileContentChanged.Add(new => OnFileContentChanged); + _assetHierarchy.OnFileRenamed.Add(new => OnFileRenamed); + } + + public ~this() + { + UnmanageAllAssets(); + } + + private void OnFileContentChanged(AssetNode assetNode) + { + // Asset isn't loaded so we don't need to reload it. + if (assetNode.AssetFile.LoadedAsset == null) + return; + + _reloadQueue.Add(assetNode.AssetFile.LoadedAsset.Handle); + } + + public void OnFileRenamed(AssetNode assetNode, StringView oldIdentifier) + { + // Asset isn't loaded so we don't need to reload it. + if (assetNode.AssetFile.LoadedAsset == null) + return; + + Asset asset = assetNode.AssetFile.LoadedAsset; + + _identiferToHandle.Remove(oldIdentifier); + asset.Identifier = assetNode.AssetFile.Identifier; + _identiferToHandle.Add(asset.Identifier, asset.Handle); + } + + public void SetContentDirectory(StringView contentDirectory) + { + _contentDirectory.Clear(); + _contentDirectory.Append(contentDirectory); + Path.Fixup(_contentDirectory); + + _assetHierarchy.SetContentDirectory(contentDirectory); + } + + public void Update() + { + SwapInLoadedAssets(); + + if (!_reloadQueue.IsEmpty) + { + for (AssetHandle handle in _reloadQueue) + { + ReloadAsset(handle); + } + _reloadQueue.Clear(); + } + + _assetHierarchy.Update(); + } + + /// Replaces placeholders with the loaded assets + private void SwapInLoadedAssets() + { + // Don't take the lock if we have nothing to do. + if (_finishedEntries.Count == 0) + return; + + using (_finishedEntriesLock.Enter()) + { + while (_finishedEntries.Count > 0) + { + let (placeholder, asset) = _finishedEntries[0]; + + delete placeholder.LoadingTask; + + if (asset == null) + placeholder.PlaceholderType = .Error; + else + { + // TODO: I'm not sure whether AssetFiles are guaranteed to persist. + // Get the reference here because placeholder wont survive SwapAsset. + AssetFile file = placeholder.AssetFile; + file.[Friend]_loadedAsset = asset; + + SwapAsset(placeholder, asset); + // SwapAsset increases RefCount, but this scope also holds a reference. + asset.ReleaseRef(); + } + + _finishedEntries.RemoveAtFast(0); + } + } + } + + public IAssetLoader GetDefaultAssetLoader(StringView fileExtension) + { + if (_defaultAssetLoaders.TryGetValue(fileExtension, let value)) + return value; + + return null; + } + private append List _supportedExtensions = .() ~ ClearAndDeleteItems!(_); + private append List _assetLoaders = .() ~ ClearAndDeleteItems!(_); + private append Dictionary _defaultAssetLoaders = .(); + private append Dictionary _assetPropertiesEditors = .() ~ { + for (String key in _.Keys) + { + delete key; + } + }; + + public void RegisterAssetLoader() where T : new, class, IAssetLoader + { + // Log.EngineLogger.AssertDebug(!_assetLoaders.Any((l) => l.GetType() == typeof(T)), "Asset loader already registered."); + + T assetLoader = new T(); + + _assetLoaders.Add(assetLoader); + + for (StringView ext in T.FileExtensions) + _supportedExtensions.Add(new String(ext)); + } + + public void SetAsDefaultAssetLoader(params Span fileExtensions) where T : IAssetLoader + { + for (var ext in fileExtensions) + { + // Find file extension in registered file extensions + String foundExtension = null; + + for (var supportedExt in _supportedExtensions) + { + if (supportedExt == ext) + { + foundExtension = supportedExt; + break; + } + } + + Log.EngineLogger.Assert(foundExtension != null, "File Extension is not registered."); + + for (var loader in _assetLoaders) + { + if (loader.GetType() == typeof(T)) + { + _defaultAssetLoaders[foundExtension] = loader; + break; + } + } + } + } + + public void SetAssetPropertiesEditor(Type assetLoaderType, function AssetPropertiesEditor(AssetFile) editorFactory) + { + String loaderTypeName = new String(); + assetLoaderType.GetName(loaderTypeName); + + _assetPropertiesEditors[loaderTypeName] = editorFactory; + } + + public void SetAssetPropertiesEditor(function AssetPropertiesEditor(AssetFile) editorFactory) where TAssetLoader : IAssetLoader + { + SetAssetPropertiesEditor(typeof(TAssetLoader), editorFactory); + } + + public AssetPropertiesEditor GetNewPropertiesEditor(AssetFile assetFile) + { + if (assetFile?.AssetConfig.AssetLoader == null) + return null; + + if (_assetPropertiesEditors.TryGetValue(assetFile.AssetConfig.AssetLoader, let propertiesEditorfactory)) + return propertiesEditorfactory(assetFile); + + return null; + } + + public bool IsLoaded(StringView identifier) + { + return _identiferToHandle.ContainsKey(identifier); + } + + public Asset GetAsset(Type assetType, AssetHandle handle) + { + Asset asset = null; + + _handleToAsset.TryGetValue(handle, out asset); + + if (var placeholder = asset as PlaceholderAsset) + { + if (placeholder.PlaceholderType == .Loading) + return placeholder.AssetLoader.GetPlaceholderAsset(assetType); + else if (placeholder.PlaceholderType == .Error) + return placeholder.AssetLoader.GetErrorAsset(assetType); + } + + if (assetType == null) + { + return asset; + } + else if (asset?.GetType().IsSubtypeOf(assetType) ?? false) + { + return asset; + } + else + { + // TODO: get default asset + + return null; + } + } + + private void ReloadAsset(AssetHandle handle) + { + Debug.Profiler.ProfileResourceFunction!(); + + Asset oldAsset = null; + + if (!_handleToAsset.TryGetValue(handle, out oldAsset)) + { + Log.EngineLogger.Error("Can't reload! No asset exists for handle."); + + return; + } + + Log.EngineLogger.AssertDebug(oldAsset != null); + + StringView oldIdentifier = oldAsset.Identifier; + + GetResourceAndSubassetName(oldIdentifier, let resourceName, let subassetName); + + String filePath = scope .(); + GetResourceFilePath(resourceName, filePath); + + Result> resultNode = AssetHierarchy.GetNodeFromPath(filePath); + + if (resultNode case .Err) + { + Log.EngineLogger.Error($"Could not find asset \"{filePath}\"."); + return; + } + + AssetFile file = resultNode->Value.AssetFile; + + IAssetLoader assetLoader = GetAssetLoader(file); + + Stream stream = GetStream(filePath); + + // TODO: Add async loading! + Asset loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this); + + delete stream; + + if (loadedAsset == null) + return; + + file.[Friend]_loadedAsset = loadedAsset; + + SwapAsset(oldAsset, loadedAsset); + // SwapAsset increases RefCount, but this scope also holds a reference. + loadedAsset.ReleaseRef(); + } + + /// Returns the resource name and, if it exists, the subasset name. + private void GetResourceAndSubassetName(StringView identifier, out StringView resourceName, out StringView? subassetName) + { + int poundIndex = identifier.IndexOf('#'); + + resourceName = (poundIndex != -1) ? identifier.Substring(0, poundIndex) : identifier; + subassetName = (poundIndex != -1) ? identifier.Substring(poundIndex + 1) : null; + } + + private void GetResourceFilePath(StringView resourceName, String filePath) + { + Path.Combine(filePath, _contentDirectory, resourceName); + + Path.Fixup(filePath); + } + + private enum PlaceholderType + { + Loading, + Error + } + + private class PlaceholderAsset : Asset + { + public AssetFile AssetFile {get; private set;} + public Task LoadingTask {get;set;} + public IAssetLoader AssetLoader {get; private set;} + public PlaceholderType PlaceholderType {get; set;} + + public this(AssetFile assetFile, IAssetLoader assetLoader, PlaceholderType placeholderType) + { + AssetFile = assetFile; + AssetLoader = assetLoader; + PlaceholderType = placeholderType; + } + } + + private append Monitor _finishedEntriesLock = .(); + private append List<(PlaceholderAsset placeholder, Asset newAsset)> _finishedEntries = .(); + + private class MissingAsset : Asset {} + + public AssetHandle LoadAsset(StringView identifier, bool blocking = false) + { + Debug.Profiler.ProfileResourceFunction!(); + + // Todo: How strict should we be on paths? + String fixedIdentifier = scope String(identifier); + AssetIdentifier.Fixup(fixedIdentifier); + + if (_identiferToHandle.TryGetValue(fixedIdentifier, let asset)) + return asset; + + GetResourceAndSubassetName(fixedIdentifier, let resourceName, let subassetName); + + String filePath = scope .(); + GetResourceFilePath(resourceName, filePath); + + Result> resultNode = AssetHierarchy.GetNodeFromPath(filePath); + + if (resultNode case .Err) + { + Log.EngineLogger.Error($"Could not find asset \"{filePath}\"."); + return .Invalid; + } + + AssetFile file = resultNode->Value.AssetFile; + + IAssetLoader assetLoader = GetAssetLoader(file); + + // TODO: what are we supposed to do if we don't find a loader? Sure not crash... + Log.EngineLogger.AssertDebug(assetLoader != null); + + Asset loadedAsset; + + // TODO: Support lazy loading for all asset types + if (!(assetLoader is EditorTextureAssetLoader) || blocking) + { + Stream stream = GetStream(filePath); + + loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this); + + delete stream; + + if (loadedAsset == null) + return .Invalid; + } + else + { + PlaceholderAsset placeholder = new PlaceholderAsset(file, assetLoader, .Loading); + + String filePath2 = new String(filePath); + String newResourceName = new String(resourceName); + String newSesourceName = subassetName == null ? null : new String(subassetName.Value); + + placeholder.LoadingTask = new Task(new () => { + AsyncLoadAsset(placeholder, filePath2, assetLoader, file, + newResourceName, newSesourceName); + }); + + ThreadPool.QueueUserWorkItem(placeholder.LoadingTask); + + loadedAsset = placeholder; + } + + loadedAsset.Identifier = fixedIdentifier; + AssetHandle handle = ManageAsset(loadedAsset); + // ManageAsset increases RefCount, but this scope also holds a reference. + loadedAsset.ReleaseRef(); + + // Add to Identifier -> Handle map + _identiferToHandle.Add(loadedAsset.Identifier, handle); + + file.[Friend]_loadedAsset = loadedAsset; + + return handle; + } + + private void AsyncLoadAsset(PlaceholderAsset placeholder, String filePath, IAssetLoader assetLoader, AssetFile file, String resourceName, String subassetName) + { + Debug.Profiler.ProfileResourceFunction!(); + + Stream stream = GetStream(filePath); + + Asset loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this); + + delete stream; + delete filePath; + delete resourceName; + delete subassetName; + + using (_finishedEntriesLock.Enter()) + { + _finishedEntries.Add((placeholder, loadedAsset)); + } + } + + /// Gets the asset loader that has to be used for the given file. + IAssetLoader GetAssetLoader(AssetFile file) + { + IAssetLoader assetLoader = null; + + String loaderTypeName = scope .(128); + + for (IAssetLoader loader in _assetLoaders) + { + loader.GetType().GetName(loaderTypeName..Clear()); + + if (loaderTypeName == file.AssetConfig.AssetLoader) + { + assetLoader = loader; + break; + } + } + + return assetLoader; + } + + public enum SaveAssetError + { + case Unknown; + case Unsavable; + case PathNotFound; + } + + /// Saves the asset. + public Result SaveAsset(Asset asset) + { + if (asset == null) + return .Err(.Unknown); + + if (asset.Identifier.IsWhiteSpace) + return .Err(.Unsavable); + + GetResourceAndSubassetName(asset.Identifier, let resourceName, let subassetName); + + if (subassetName != null) + { + // TODO: should this ever be allowed? Couldn't we just save the entire asset? + // Would this ever be necessary? + Runtime.NotImplemented("Saving subassets is not currently allowed"); + } + + String filePath = scope String(); + GetResourceFilePath(resourceName, filePath); + + Result> assetNode = AssetHierarchy.GetNodeFromPath(filePath); + + if (assetNode case .Err) + return .Err(.PathNotFound); + + AssetFile file = assetNode.Get()->AssetFile; + + IAssetLoader assetLoader = GetAssetLoader(file); + + IAssetSaver assetSaver = assetLoader as IAssetSaver; + + if (assetSaver == null) + { + Log.EngineLogger.Error("The asset loader can't save!"); + return .Err(.Unsavable); + } + + Stream stream = OpenStream(filePath, false); + + assetSaver.EditorSaveAsset(stream, asset, file.AssetConfig.Config, resourceName, subassetName, this); + + // Trim off the end of the file. + stream.SetLength(stream.Position); + + delete stream; + + return .Ok; + } + + private Stream OpenStream(StringView assetIdentifier, bool openOnly) + { + var assetIdentifier; + + if (!assetIdentifier.StartsWith(_contentDirectory)) + { + String filePath = scope:: String(assetIdentifier.Length + _contentDirectory.Length + 2); + Path.Combine(filePath, _contentDirectory, assetIdentifier); + + assetIdentifier = filePath; + } + + FileStream fs = new FileStream(); + + FileMode fileMode = openOnly ? FileMode.Open : FileMode.OpenOrCreate; + + var result = fs.Open(assetIdentifier, fileMode, openOnly ? .Read : .ReadWrite, .ReadWrite); + + if (result case .Err) + return null; + + return fs; + } + + // TODO: probably not needed + public Stream GetStream(StringView assetIdentifier) + { + return OpenStream(assetIdentifier, true); + + /*var assetIdentifier; + + if (!assetIdentifier.StartsWith(_contentDirectory)) + { + String filePath = scope:: String(assetIdentifier.Length + _contentDirectory.Length + 2); + Path.Combine(filePath, _contentDirectory, assetIdentifier); + + assetIdentifier = filePath; + } + + FileStream fs = new FileStream(); + var result = fs.Open(assetIdentifier, .Open, .Read, .ReadWrite); + + if (result case .Err) + return null; + + return fs;*/ + } + + public AssetHandle ManageAsset(Asset asset) + { + Log.EngineLogger.AssertDebug(asset.Handle == .Invalid, "Asset is already managed."); + Log.EngineLogger.AssertDebug(asset.ContentManager == null, "Asset is already managed."); + + AssetHandle handle = .(); + + // Generate until we find a unique key (shouldn't happen too often) + while (handle.IsInvalid || _handleToAsset.ContainsKey(handle)) + { + handle = .(); + Log.EngineLogger.Trace("Handle was invalid or already taken."); + // TODO: perhaps test how often this happens. + // If this happens too often we could use a different random generator + } + + //_handles.Add(asset.Identifier, handle); + _handleToAsset.Add(handle, asset); + + asset.[Friend]_contentManager = this; + asset.[Friend]_handle = handle; + asset.AddRef(); + + return handle; + } + + private void SwapAsset(Asset oldAsset, Asset newAsset) + { + newAsset.Identifier = oldAsset.Identifier; + newAsset._contentManager = this; + newAsset._handle = oldAsset.Handle; + + _handleToAsset[oldAsset.Handle] = newAsset; + + if (_identiferToHandle.ContainsKey(oldAsset.Identifier)) + { + _identiferToHandle.Remove(oldAsset.Identifier); + _identiferToHandle.Add(newAsset.Identifier, newAsset.Handle); + } + + newAsset.AddRef(); + oldAsset.ReleaseRef(); + } + + public void UnmanageAsset(AssetHandle handle) + { + Log.EngineLogger.AssertDebug(_handleToAsset.ContainsKey(handle), "Handle doesn't correspond to an asset."); + + Asset asset = _handleToAsset[handle]; + + if (_identiferToHandle.ContainsKey(asset.Identifier)) + _identiferToHandle.Remove(asset.Identifier); + + _handleToAsset.Remove(handle); + asset.[Friend]_contentManager = null; + + asset.ReleaseRef(); + } + + /// This will unregister all assets from this content manager. + /// Note: This will not release any assets. + private void UnmanageAllAssets() + { + for (let (handle, _) in _handleToAsset) + { + UnmanageAsset(handle); + } + } + + public void AssetMoved(Asset asset, StringView oldIdentifier, StringView newIdentifier) + { + Runtime.NotImplemented(); + + if (oldIdentifier == newIdentifier) + return; + + Log.EngineLogger.Assert(_identiferToHandle.ContainsKey(newIdentifier), "An asset with the same identifier is already managed by this content manager."); + + // Since all we do in order to track assets is add them to a dictionary we can simply unmanage and manage it again. + //UnmanageAsset(asset); + //ManageAsset(asset); + } +} \ No newline at end of file diff --git a/GlitchyEditor/src/EditorIcons.bf b/GlitchyEditor/src/EditorIcons.bf new file mode 100644 index 0000000..1f13efe --- /dev/null +++ b/GlitchyEditor/src/EditorIcons.bf @@ -0,0 +1,61 @@ +using System; +using GlitchyEngine.Renderer; +using GlitchyEngine.Math; +using GlitchyEngine; +using GlitchyEngine.Content; + +namespace GlitchyEditor +{ + class EditorIcons : RefCounted + { + AssetHandle _texture; + + public SubTexture2D DirectionalLight ~ _.ReleaseRef(); + public SubTexture2D Camera ~ _.ReleaseRef(); + public SubTexture2D Folder ~ _.ReleaseRef(); + public SubTexture2D File ~ _.ReleaseRef(); + public SubTexture2D Play ~ _.ReleaseRef(); + public SubTexture2D Stop ~ _.ReleaseRef(); + public SubTexture2D Simulate ~ _.ReleaseRef(); + public SubTexture2D Pause ~ _.ReleaseRef(); + + public SamplerState SamplerState + { + get => _texture.Get().SamplerState; + set => _texture.Get().SamplerState = value; + } + + public this(String texturePath, Vector2 iconSize) + { + _texture = Content.LoadAsset(texturePath, null, true); + + Vector2 pen = .(); + + DirectionalLight = GetNextGridTexture(ref pen, iconSize); + Camera = GetNextGridTexture(ref pen, iconSize); + Folder = GetNextGridTexture(ref pen, iconSize); + File = GetNextGridTexture(ref pen, iconSize); + Play = GetNextGridTexture(ref pen, iconSize); + Stop = GetNextGridTexture(ref pen, iconSize); + Simulate = GetNextGridTexture(ref pen, iconSize); + Pause = GetNextGridTexture(ref pen, iconSize); + } + + private SubTexture2D GetNextGridTexture(ref Vector2 pen, Vector2 iconSize) + { + SubTexture2D subTexture = .CreateFromGrid(_texture, pen, iconSize); + + pen.X += 1.0f; + + if (pen.X >= (_texture.Width / iconSize.X)) + { + pen.X = 0; + pen.Y += 1.0f; + } + + Log.EngineLogger.AssertDebug(pen.Y <=(_texture.Height / iconSize.Y)); + + return subTexture; + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/src/EditorLayer.bf b/GlitchyEditor/src/EditorLayer.bf index 20a2b29..ec9fb9b 100644 --- a/GlitchyEditor/src/EditorLayer.bf +++ b/GlitchyEditor/src/EditorLayer.bf @@ -7,103 +7,102 @@ using GlitchyEngine.ImGui; using GlitchyEngine.Math; using GlitchyEngine.Renderer; using GlitchyEngine.World; +using GlitchyEngine.Content; +using System.Collections; +using GlitchyEngine.Renderer.Animation; +using System.IO; +using GlitchyEngine.Core; +using GlitchyEditor.Assets; namespace GlitchyEditor { class EditorLayer : Layer { + enum SceneState + { + Edit, + Play, + Simulate + } + RasterizerState _rasterizerState ~ _?.ReleaseRef(); RasterizerState _rasterizerStateClockWise ~ _?.ReleaseRef(); + // TODO: we shouldn't hold a reference to the context GraphicsContext _context ~ _.ReleaseRef(); BlendState _alphaBlendState ~ _.ReleaseRef(); BlendState _opaqueBlendState ~ _.ReleaseRef(); DepthStencilState _depthStencilState ~ _.ReleaseRef(); + + /// Reference to the scene that is currently being played and worked on. + Scene _activeScene ~ _?.ReleaseRef(); - Scene _scene = new Scene() ~ delete _; + /** + * Referece to the editor scene. + * We hold a reference to the editor scene because we need it in order + * to restore the original state once we stop the game/simulation. + * Before starting the simulation the editor scene will be copied and + * the reference in _activeScene will be replaced with the new scene. + */ + Scene _editorScene ~ _?.ReleaseRef(); + + SceneRenderer _gameSceneRenderer ~ delete _; + SceneRenderer _editorSceneRenderer ~ delete _; + + /// Path of the current scene. + append String _sceneFilePath = .(); Editor _editor ~ delete _; - RenderTarget2D _viewportTarget ~ _?.ReleaseRef(); + RenderTargetGroup _cameraTarget ~ _.ReleaseRef(); + RenderTargetGroup _editorViewportTarget ~ _.ReleaseRef(); + RenderTargetGroup _gameViewportTarget ~ _.ReleaseRef(); SettingsWindow _settingsWindow = new .() ~ delete _; - Entity _cameraEntity; - Entity _otherCameraEntity; + EditorCamera _camera ~ _.Dispose(); - class CameraController : ScriptableEntity + EditorIcons _editorIcons ~ _.ReleaseRef(); + + EditorContentManager _contentManager; + + SceneState _sceneState = .Edit; + bool _isPaused = false; + + /// Gets or sets the path of the current scene. + public StringView SceneFilePath { - protected override void OnCreate() + get => _sceneFilePath; + set { - Log.EngineLogger.Trace("Cam controller created!"); - } + _sceneFilePath.Clear(); - protected override void OnUpdate(GameTime gameTime) - { - var transformCmp = GetComponent(); - - Vector3 position = transformCmp.Position; - - if (Input.IsKeyPressed(Key.A)) - { - position.X -= gameTime.DeltaTime; - } - if (Input.IsKeyPressed(Key.D)) - { - position.X += gameTime.DeltaTime; - } - if (Input.IsKeyPressed(Key.W)) - { - position.Y += gameTime.DeltaTime; - } - if (Input.IsKeyPressed(Key.S)) - { - position.Y -= gameTime.DeltaTime; - } - - transformCmp.Position = position; - } - - protected override void OnDestroy() - { - Log.EngineLogger.Trace("Cam controller destroyed!"); + if (!value.IsWhiteSpace) + _sceneFilePath.Append(value); } } - public this() : base("Example") + public this(EditorContentManager contentManager) : base("Editor") { Application.Get().Window.IsVSync = false; + _contentManager = contentManager; + InitGraphics(); - { - _cameraEntity = _scene.CreateEntity("Camera Entity"); - let camera = _cameraEntity.AddComponent(); - camera.Camera.SetPerspective(MathHelper.ToRadians(75), 0.1f, 10000.0f); - camera.Primary = true; - camera.FixedAspectRatio = false; - let transform = _cameraEntity.GetComponent(); - transform.Position = .(0, 0, -5); + _editorScene = new Scene(); + SetReference!(_activeScene, _editorScene); - _cameraEntity.AddComponent().Bind(); - _cameraEntity.AddComponent(); - } - - { - _otherCameraEntity = _scene.CreateEntity("Other Camera Entity"); - let camera = _otherCameraEntity.AddComponent(); - camera.Camera.SetPerspective(MathHelper.ToRadians(45), 0.1f, 1000.0f); - camera.Primary = false; - camera.FixedAspectRatio = false; - let transform = _otherCameraEntity.GetComponent(); - transform.Position = .(0, 0, -5); - - _otherCameraEntity.AddComponent().Bind(); - _otherCameraEntity.AddComponent(); - } + _gameSceneRenderer = new SceneRenderer(); + _editorSceneRenderer = new SceneRenderer(); + _camera = EditorCamera(Vector3(3.5f, 1.25f, 2.75f), Quaternion.FromEulerAngles(MathHelper.ToRadians(40), MathHelper.ToRadians(25), 0), MathHelper.ToRadians(75), 0.1f, 1); + _camera.RenderTarget = _cameraTarget; + InitEditor(); + + NewScene(); } private void InitGraphics() @@ -123,129 +122,561 @@ namespace GlitchyEditor DepthStencilStateDescription dsDesc = .(); _depthStencilState = new DepthStencilState(dsDesc); + + _cameraTarget = new RenderTargetGroup(.(){ + Width = 100, + Height = 100, + ColorTargetDescriptions = TargetDescription[]( + .(.R16G16B16A16_Float), + .(.R32_UInt) + ), + DepthTargetDescription = .(.D24_UNorm_S8_UInt) + }); - _viewportTarget = new RenderTarget2D(RenderTarget2DDescription(.R8G8B8A8_UNorm, 100, 100) {DepthStencilFormat = .D32_Float}); - _viewportTarget.SamplerState = SamplerStateManager.LinearClamp; + _editorViewportTarget = new RenderTargetGroup(.() + { + Width = 100, + Height = 100, + ColorTargetDescriptions = TargetDescription[]( + .(.R8G8B8A8_UNorm)) + }); + + _gameViewportTarget = new RenderTargetGroup(.() + { + Width = 100, + Height = 100, + ColorTargetDescriptions = TargetDescription[]( + .(.R8G8B8A8_UNorm)) + }); + + _editorIcons = new EditorIcons("Textures/EditorIcons.dds", .(64, 64)); + _editorIcons.SamplerState = SamplerStateManager.AnisotropicClamp; + + ContentBrowserWindow.s_FolderTexture = _editorIcons.Folder; + ContentBrowserWindow.s_FileTexture = _editorIcons.File; } private void InitEditor() { - _editor = new Editor(_scene); - _editor.SceneViewportWindow.ViewportSizeChangedEvent.Add(new (s, e) => ViewportSizeChanged(s, e)); + _editor = new Editor(_editorScene, _contentManager); + _editor.SceneViewportWindow.ViewportSizeChanged.Add(new (s, e) => EditorViewportSizeChanged(s, e)); + _editor.GameViewportWindow.ViewportSizeChanged.Add(new (s, e) => GameViewportSizeChanged(s, e)); + _editor.CurrentCamera = &_camera; + _editor.GameSceneRenderer = _gameSceneRenderer; + _editor.EditorSceneRenderer = _editorSceneRenderer; - //_editor.[Friend]CreateEntityWithTransform(); - - _editor.SceneViewportWindow.CameraEntity = _cameraEntity; + _editor.RequestOpenScene.Add(new (s, fileName) => { + LoadSceneFile(fileName); + }); } public override void Update(GameTime gameTime) { - var scriptComponent = _cameraEntity.GetComponent(); + Debug.Profiler.ProfileFunction!(); - if (var camController = scriptComponent.Instance as EditorCameraController) + _editor.CurrentScene = _activeScene; + + Scene.UpdateMode updateMode; + + switch (_sceneState) { - camController.IsEnabled = (_editor.SceneViewportWindow.HasFocus && Input.IsMouseButtonPressed(.RightButton)); + case .Edit: + updateMode = .Editor; + case .Play: + updateMode = .Runtime; + case .Simulate: + updateMode = .Physics; } - //TransformSystem.Update(_world); + if (_sceneState != .Edit && _isPaused) + updateMode = .None; - RenderCommand.Clear(_viewportTarget, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0); - RenderCommand.SetRenderTarget(_viewportTarget, 0, true); - RenderCommand.BindRenderTargets(); + _activeScene.Update(gameTime, updateMode); - RenderCommand.SetViewport(Viewport(0, 0, _viewportTarget.Width, _viewportTarget.Height)); + // Clear the swapchain-buffer + RenderCommand.Clear(null, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0); + + RenderCommand.SetBlendState(_alphaBlendState); + RenderCommand.SetDepthStencilState(_depthStencilState); + + if (_editor.SceneViewportWindow.Visible) + { + _camera.Update(gameTime); + + _editorSceneRenderer.Scene = _activeScene; + + RenderCommand.Clear(_editorViewportTarget, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0); + _editorSceneRenderer.RenderEditor(gameTime, _camera, _editorViewportTarget, scope => DebugDraw3D, scope => DebugDraw2D); + } RenderCommand.SetBlendState(_alphaBlendState); RenderCommand.SetDepthStencilState(_depthStencilState); - //Renderer.BeginScene(_cameraController.Camera); + if (_editor.GameViewportWindow.Visible) + { + _gameSceneRenderer.Scene = _activeScene; - //DebugRenderer.Render(_scene.[Friend]_ecsWorld); - - //Renderer.EndScene(); - - _scene.Update(gameTime); - - RenderCommand.Clear(null, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0); + RenderCommand.Clear(_gameViewportTarget, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0); + _gameSceneRenderer.RenderRuntime(gameTime, _gameViewportTarget); + } + RenderCommand.UnbindRenderTargets(); RenderCommand.SetRenderTarget(null, 0, true); RenderCommand.BindRenderTargets(); RenderCommand.SetViewport(_context.SwapChain.BackbufferViewport); } + private void DebugDraw3D() + { + /*for (var (entity, transform, camera) in _scene.[Friend]_ecsWorld.Enumerate()) + { + if (_editor.EntityHierarchyWindow.SelectedEntities.Contains(.(entity, _scene))) + { + DebugRenderer.DrawViewFrustum(transform.WorldTransform, camera.Camera.Projection); + } + }*/ + } + + private void DebugDraw2D() + { + RenderCommand.SetBlendState(_alphaBlendState); + + Matrix billboard = _camera.View.Invert(); + billboard.Translation = .Zero; + + Matrix Billboard(Matrix transform) + { + Vector3 worldPos = transform.Translation; + + return Matrix.Translation(worldPos) * billboard; + } + + float CalculateAlpha(Vector3 pos) + { + return Math.Clamp(1.5f - Vector3.Distance(_editor.CurrentCamera.Position, pos) / 50, 0, 1); + } + + for (var (entity, transform, camera) in _activeScene.GetEntities()) + { + if (_editor.EntityHierarchyWindow.SelectedEntities.Contains(.(entity, _activeScene))) + { + DebugRenderer.DrawViewFrustum(transform.WorldTransform, camera.Camera.Projection, .White); + } + + Matrix world = Billboard(transform.WorldTransform); + + float alpha = CalculateAlpha(transform.WorldTransform.Translation); + Renderer2D.DrawQuad(world, _editorIcons.Camera, ColorRGBA(alpha, alpha, alpha, alpha), .(0, 0, 1, 1), entity.Index); + //Renderer2D.DrawQuad(world, _iconCamera, .White, .(0, 0, 1, 1), entity.Index); + } + + for (var (entity, transform, light) in _activeScene.GetEntities()) + { + if (_editor.EntityHierarchyWindow.SelectedEntities.Contains(.(entity, _activeScene))) + { + Renderer.DrawRay(.Zero, .(0, 0, 20), ColorRGBA(light.SceneLight.Color, 1.0f), transform.WorldTransform); + + for (float angle = 0; angle < MathHelper.TwoPi; angle += MathHelper.TwoPi / 5.0f) + { + Vector2 pos = MathHelper.CirclePoint(angle, 0.5f); + + Renderer.DrawRay(.(pos, 0), .(pos, 20), .White, transform.WorldTransform); + } + } + + Matrix world = Billboard(transform.WorldTransform); + + float alpha = CalculateAlpha(transform.WorldTransform.Translation); + Renderer2D.DrawQuad(world, _editorIcons.DirectionalLight, ColorRGBA(light.SceneLight.Color.R * alpha, light.SceneLight.Color.G * alpha, light.SceneLight.Color.B * alpha, alpha), .(0, 0, 1, 1), entity.Index); + } + + for (var (entity, transform, collider) in _activeScene.GetEntities()) + { + Renderer2D.DrawRect(transform.WorldTransform * Matrix.Translation(collider.Offset.X, collider.Offset.Y, 0) * Matrix.Scaling(collider.Size.X * 2, collider.Size.Y * 2, 0)); + } + + for (var (entity, transform, collider) in _activeScene.GetEntities()) + { + Renderer2D.DrawCircle(transform.WorldTransform * Matrix.Translation(collider.Offset.X, collider.Offset.Y, 0) * Matrix.Scaling(collider.Radius * 2), (Texture2D)null, ColorRGBA(0f, 1f, 0f), 0.01f); + } + } + public override void OnEvent(Event event) { EventDispatcher dispatcher = EventDispatcher(event); dispatcher.Dispatch(scope (e) => OnImGuiRender(e)); dispatcher.Dispatch(scope (e) => OnWindowResize(e)); + dispatcher.Dispatch(scope (e) => OnKeyPressed(e)); + dispatcher.Dispatch(scope (e) => OnMouseScrolled(e)); } ImGui.ID _mainDockspaceId; + + TextureViewer viewer = new TextureViewer() ~ delete _; private bool OnImGuiRender(ImGuiRenderEvent event) { - ImGui.Begin("Test"); + Input.ImGuiDebugDraw(); - static bool cameraA = true; - - if (ImGui.Checkbox("Camera A", &cameraA)) - { - _cameraEntity.GetComponent().Primary = cameraA; - _otherCameraEntity.GetComponent().Primary = !cameraA; - } - - ImGui.End(); + //viewer.ViewTexture(Renderer.[Friend]_gBuffer.Target); ImGui.Viewport* viewport = ImGui.GetMainViewport(); ImGui.DockSpaceOverViewport(viewport); DrawMainMenuBar(); - _editor.SceneViewportWindow.RenderTarget = _viewportTarget; + _editor.SceneViewportWindow.RenderTarget = _editorViewportTarget; + _editor.GameViewportWindow.RenderTarget = _gameViewportTarget; _editor.Update(); _settingsWindow.Show(); + UI_Toolbar(); + return false; } + private void UI_Toolbar() + { + ImGui.PushStyleVar(.WindowPadding, ImGui.Vec2(0, 2)); + 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); + + ImGui.Begin("##toolbar", null, .NoDecoration | .NoScrollbar | .NoScrollWithMouse); + + float padding = 2.0f; + + float size = ImGui.GetWindowHeight() - 2 * padding; + + float centerX = ImGui.GetContentRegionMax().x / 2; + + + if (_sceneState == .Edit) + EditorButtons: + { + // Display the buttons for edit state + + float totalWidth = size * 3 + padding * 4; + + ImGui.SameLine(); + ImGui.SetCursorPosX(centerX - totalWidth / 2); + + if (ImGui.ImageButton(_editorIcons.Play, .(size, size), .Zero, .Ones, 0)) + OnScenePlay(); + + ImGui.AttachTooltip("Play the game."); + + ImGui.SameLine(); + + ImGui.PushID(1); + + if (ImGui.ImageButton(_editorIcons.Simulate, .(size, size), .Zero, .Ones, 0)) + OnSceneSimulate(); + + ImGui.PopID(); + + ImGui.AttachTooltip("Enter simulation mode.\nThis only runs the physics engine."); + + if (_isPaused) + { + ImGui.PushStyleColor(.Button, *ImGui.GetStyleColorVec4(.ButtonActive)); + + defer:EditorButtons { ImGui.PopStyleColor(); } + } + + ImGui.PushID(2); + + //ImGui.SameLine(penX += size + 2 * padding); + ImGui.SameLine(); + + if (ImGui.ImageButton(_editorIcons.Pause, .(size, size), .Zero, .Ones, 0)) + _isPaused = !_isPaused; + + ImGui.PopID(); + + ImGui.AttachTooltip("If enabled the game or simulation will be started in paused state."); + + } + else + { + // Display the buttons for play/simulation state + + ImGui.SameLine(); + ImGui.SetCursorPosX(centerX - size - padding); + + SubTexture2D pauseButtonIcon = _isPaused ? _editorIcons.Play : _editorIcons.Pause; + + if (ImGui.ImageButton(pauseButtonIcon, .(size, size), .Zero, .Ones, 0)) + { + if (_isPaused) + OnSceneResume(); + else + OnScenePause(); + } + + ImGui.AttachTooltip(_isPaused ? "Resume" : "Pause"); + + ImGui.SameLine(); + ImGui.PushID(1); + + if (ImGui.ImageButton(_editorIcons.Stop, .(size, size), .Zero, .Ones, 0)) + OnSceneStop(); + + ImGui.PopID(); + + ImGui.AttachTooltip("Stop"); + } + + ImGui.End(); + + ImGui.PopStyleColor(3); + ImGui.PopStyleVar(2); + } + + private void OnScenePlay() + { + _editor.SceneViewportWindow.EditorMode = false; + _sceneState = .Play; + + using (Scene runtimeScene = new Scene()) + { + _editorScene.CopyTo(runtimeScene); + + runtimeScene.OnRuntimeStart(); + + SetReference!(_activeScene, runtimeScene); + } + + _editor.CurrentScene = _activeScene; + } + + private void OnSceneSimulate() + { + _editor.SceneViewportWindow.EditorMode = false; + _sceneState = .Simulate; + + using (Scene simulationScene = new Scene()) + { + _editorScene.CopyTo(simulationScene); + + simulationScene.OnSimulationStart(); + + SetReference!(_activeScene, simulationScene); + } + + _editor.CurrentScene = _activeScene; + } + + private void OnScenePause() + { + _isPaused = true; + } + + private void OnSceneResume() + { + _isPaused = false; + } + + private void OnSceneStop() + { + if (_sceneState == .Play) + _activeScene.OnRuntimeStop(); + else + _activeScene.OnSimulationStop(); + + SetReference!(_activeScene, _editorScene); + + _editor.SceneViewportWindow.EditorMode = true; + _sceneState = .Edit; + + _editor.CurrentScene = _activeScene; + + _isPaused = false; + + /* + * Update the viewport size because if the game windows size changed in + * "Game"-mode the updated aspect-rations will reset once we go back + * into "Editor"-mode (because Game-Mode works on a copy of the scene). + */ + GameViewportSizeChanged(null, _editor.GameViewportWindow.ViewportSize); + } + + /// Creates a new scene. + private void NewScene() + { + OnSceneStop(); + + SceneFilePath = null; + + Scene newScene = new Scene(); + + _camera.Position = .(-1.5f, 1.5f, -2.5f); + _camera.RotationEuler = .(MathHelper.ToRadians(25), MathHelper.ToRadians(35), 0); + + // Create a default camera + { + let cameraEntity = newScene.CreateEntity("Camera"); + let transform = cameraEntity.Transform; + transform.Position = Vector3(0, 2, -5); + transform.RotationEuler = Vector3(0, MathHelper.ToRadians(25), 0); + + let camera = cameraEntity.AddComponent(); + camera.Primary = true; + camera.Camera.ProjectionType = .InfinitePerspective; + camera.Camera.PerspectiveFovY = MathHelper.ToRadians(75); + camera.Camera.PerspectiveNearPlane = 0.1f; + } + + // Create a default light source + { + let lightEntity = newScene.CreateEntity("Light"); + let transform = lightEntity.Transform; + transform.Position = .(-3, 4, -1.5f); + transform.RotationEuler = .(MathHelper.ToRadians(20), MathHelper.ToRadians(75), MathHelper.ToRadians(20)); + + let light = lightEntity.AddComponent(); + light.SceneLight.Illuminance = 10.0f; + light.SceneLight.Color = .(1.0f, 0.95f, 0.8f); + } + + _editorScene.ReleaseRef(); + _editorScene = newScene; + _editor.CurrentScene = _editorScene; + var vpSize = _editor.SceneViewportWindow.ViewportSize; + _editorScene.OnViewportResize((.)vpSize.X, (.)vpSize.Y); + + SetReference!(_activeScene, _editorScene); + } + + /// Saves the scene in the file that is was loaded from or saved to last. If there is no such path (i.e. it is a new scene) the save file dialog will open. + private void SaveScene() + { + if (SceneFilePath.IsWhiteSpace) + { + SaveSceneAs(); + return; + } + + SceneSerializer serializer = scope .(_editorScene); + serializer.Serialize(SceneFilePath); + } + + /// Opens a save file dialog and saves the scene at the user specified location. + private void SaveSceneAs() + { + SaveFileDialog sfd = scope .(); + if (sfd.ShowDialog() case .Ok(let val)) + { + if (val == .OK) + { + SceneFilePath = sfd.FileNames[0]; + + SaveScene(); + } + } + } + + /// Opens a open file dialog and load the scene selected by the user specified. + private void OpenScene() + { + OpenFileDialog ofd = scope .(); + if (ofd.ShowDialog() case .Ok(let val)) + { + if (val == .OK) + { + LoadSceneFile(ofd.FileNames[0]); + } + } + } + + /// Loads the given scene file. + private void LoadSceneFile(StringView filename) + { + OnSceneStop(); + + SceneFilePath = scope String(filename); + + _editorScene.ReleaseRef(); + _editorScene = new Scene(); + _editor.CurrentScene = _editorScene; + var vpSize = _editor.SceneViewportWindow.ViewportSize; + _editorScene.OnViewportResize((.)vpSize.X, (.)vpSize.Y); + + SceneSerializer serializer = scope .(_editorScene); + serializer.Deserialize(SceneFilePath); + + SetReference!(_activeScene, _editorScene); + } + private void DrawMainMenuBar() { ImGui.BeginMainMenuBar(); if(ImGui.BeginMenu("File", true)) { + if (ImGui.MenuItem("New", "Ctrl+N")) + NewScene(); + + if (ImGui.MenuItem("Save", "Ctrl+S")) + SaveScene(); + + if (ImGui.MenuItem("Save as...", "Ctrl+Shift+N")) + SaveSceneAs(); + + if (ImGui.MenuItem("Open...", "Ctrl+O")) + OpenScene(); + + ImGui.Separator(); + if (ImGui.MenuItem("Settings")) _settingsWindow.Open = true; + + ImGui.Separator(); + + if (ImGui.MenuItem("Exit")) + Application.Get().Close(); ImGui.EndMenu(); } if(ImGui.BeginMenu("View", true)) { - if(ImGui.MenuItem(EntityHierarchyWindow.s_WindowTitle)) - { - _editor.EntityHierarchyWindow.Open = true; - } - if(ImGui.MenuItem(ComponentEditWindow.s_WindowTitle)) - { _editor.ComponentEditWindow.Open = true; - } - if(ImGui.MenuItem(SceneViewportWindow.s_WindowTitle)) - { + if(ImGui.MenuItem(ContentBrowserWindow.s_WindowTitle)) + _editor.ComponentEditWindow.Open = true; + + if(ImGui.MenuItem(EditorViewportWindow.s_WindowTitle)) _editor.SceneViewportWindow.Open = true; - } + + if(ImGui.MenuItem(EntityHierarchyWindow.s_WindowTitle)) + _editor.EntityHierarchyWindow.Open = true; + + if(ImGui.MenuItem(GameViewportWindow.s_WindowTitle)) + _editor.GameViewportWindow.Open = true; + + if(ImGui.MenuItem(PropertiesWindow.s_WindowTitle)) + _editor.PropertiesWindow.Open = true; ImGui.EndMenu(); } - ImGui.EndMainMenuBar(); } @@ -254,7 +685,7 @@ namespace GlitchyEditor return false; } - private void ViewportSizeChanged(Object sender, Vector2 viewportSize) + private void EditorViewportSizeChanged(Object sender, Vector2 viewportSize) { uint32 sizeX = (uint32)viewportSize.X; uint32 sizeY = (uint32)viewportSize.Y; @@ -262,9 +693,64 @@ namespace GlitchyEditor if(sizeX == 0 || sizeY == 0) return; - _viewportTarget.Resize(sizeX, sizeY); + _editorViewportTarget.Resize(sizeX, sizeY); + _cameraTarget.Resize(sizeX, sizeY); - _scene.OnViewportResize(sizeX, sizeY); + _camera.OnViewportResize(sizeX, sizeY); + + _editorSceneRenderer.OnViewportResize(sizeX, sizeY); + } + + private void GameViewportSizeChanged(Object sender, Vector2 viewportSize) + { + uint32 sizeX = (uint32)viewportSize.X; + uint32 sizeY = (uint32)viewportSize.Y; + + if(sizeX == 0 || sizeY == 0) + return; + + _gameViewportTarget.Resize(sizeX, sizeY); + + _activeScene.OnViewportResize(sizeX, sizeY); + + _gameSceneRenderer.OnViewportResize(sizeX, sizeY); + } + + private bool OnKeyPressed(KeyPressedEvent e) + { + bool control = Input.IsKeyPressed(Key.Control); + bool shift = Input.IsKeyPressed(Key.Shift); + + if (!_camera.[Friend]BindMouse && control) + { + switch (e.KeyCode) + { + case .N: + NewScene(); + return true; + case .O: + OpenScene(); + return true; + case .S: + if (shift) + SaveSceneAs(); + else + SaveScene(); + + return true; + default: + } + } + + return false; + } + + private bool OnMouseScrolled(MouseScrolledEvent e) + { + if (_camera.OnMouseScrolled(e)) + return true; + + return false; } } } diff --git a/GlitchyEditor/src/TextureViewer.bf b/GlitchyEditor/src/TextureViewer.bf new file mode 100644 index 0000000..a1adc3b --- /dev/null +++ b/GlitchyEditor/src/TextureViewer.bf @@ -0,0 +1,246 @@ +using GlitchyEngine.Renderer; +using GlitchyEngine; +using ImGui; +using GlitchyEngine.Math; +using System; + +namespace GlitchyEditor +{ + class TextureViewer + { + enum BackgroundMode : int32 + { + White, + Black, + Checkerboard + } + + enum SampleMode : int32 + { + Point, + Linear + } + + GraphicsContext _context ~ _.ReleaseRef(); + + Effect _effect ~ _.ReleaseRef(); + + float _zoom = 1.0f; + + BackgroundMode _backgroundMode = .Checkerboard; + + SampleMode _sampleMode = .Linear; + + RenderTarget2D _target ~ _?.ReleaseRef(); + // TODO: we don't need depth! + DepthStencilTarget _depth ~ _?.ReleaseRef(); + + SamplerState _samplerPoint ~ _.ReleaseRef(); + SamplerState _samplerLinear ~ _.ReleaseRef(); + + public this() + { + _context = Application.Get().Window.Context..AddRef(); + + InitEffect(); + InitState(); + // TODO: rasterizerstate and depthstencilstate + } + + private void InitEffect() + { + _effect = new Effect("content\\Shaders\\textureViewerShader.hlsl"); + } + + private void InitState() + { + SamplerStateDescription desc = .(); + desc.MagFilter = .Linear; + desc.MinFilter = .Linear; + _samplerLinear = SamplerStateManager.GetSampler(desc); + + desc.MagFilter = .Point; + desc.MinFilter = .Point; + _samplerPoint = SamplerStateManager.GetSampler(desc); + } + + Vector2 _position; + + bool _moving; + + float _colorOffset = 0; + float _colorScale = 1; + float _alphaOffset = 0; + float _alphaScale = 1; + + public void ViewTexture(Texture viewedTexture) + { + ImGui.Begin("Texture Viewer"); + + ImGui.SliderFloat("Zoom", &_zoom, 0.01f, 100.0f); + + char8*[] items = scope .("White", "Black", "Checkerboard"); + + ImGui.Combo("Background", (.)&_backgroundMode, items.Ptr, (.)items.Count); + + items = scope .("Point", "Linear"); + + ImGui.Combo("Sampler", (.)&_sampleMode, items.Ptr, (.)items.Count); + + ImGui.SliderFloat2("Color offset and scale", *(float[2]*)&_colorOffset, -1.0f, 1.0f); + ImGui.SliderFloat2("Alpha offset and scale", *(float[2]*)&_alphaOffset, -1.0f, 1.0f); + + ImGui.SliderFloat2("Position", *(float[2]*)&_position, 2 * -Math.Max(viewedTexture.Width, viewedTexture.Height) * _zoom, 2 * Math.Max(viewedTexture.Width, viewedTexture.Height) * _zoom); + + ImGui.BeginChild("imageChild"); + + UpdateInput(); + + var viewportSize = ImGui.GetContentRegionAvail(); + + 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) + { + _target?.ReleaseRef(); + _target = new RenderTarget2D(.(.R8G8B8A8_UNorm, (.)viewportSize.x, (.)viewportSize.y)); + _depth?.ReleaseRef(); + _depth = new DepthStencilTarget((.)viewportSize.x, (.)viewportSize.y, .D16_UNorm); + } + + RenderTexture(viewedTexture); + + ImGui.Image(_target, viewportSize); + + ImGui.EndChild(); + + ImGui.End(); + } + + float lastWheel; + + private void UpdateInput() + { + var windowPos = ImGui.GetWindowPos(); + var mousePos = ImGui.GetIO().MousePos; + Vector2 mouseInWindow = .(mousePos.x - windowPos.x, mousePos.y - windowPos.y); + + bool windowHovered = ImGui.IsWindowHovered(); + + if(windowHovered && Input.IsMouseButtonPressing(.MiddleButton)) + { + _moving = true; + } + else if(Input.IsMouseButtonReleased(.MiddleButton)) + { + _moving = false; + } + + if(windowHovered || _moving) + { + float mouseWheel = ImGui.GetIO().MouseWheel; + + float delta = mouseWheel - lastWheel; + + if(delta != 0) + { + _position -= mouseInWindow; + _position /= _zoom; + + _zoom *= Math.Pow(1.1f, delta); + + _position *= _zoom; + _position += mouseInWindow; + } + + } + + if(_moving) + { + Int2 movement = Input.GetMouseMovement(); + + _position.X += movement.X; + _position.Y += movement.Y; + } + } + + OrthographicCamera _camera = new OrthographicCamera() ~ delete _; + + private void RenderTexture(Texture viewedTexture) + { + Viewport vp = .(0, 0, _target.Width, _target.Height); + RenderCommand.SetViewport(vp); + + // TODO: don't clear pink! + RenderCommand.Clear(_target, .Pink); + RenderCommand.Clear(_depth, .Depth, 1.0f, 0); + + //_target.Bind(); + _context.SetRenderTarget(_target); + _context.SetDepthStencilTarget(_depth); + _context.BindRenderTargets(); + + Vector2 textureSize = Vector2(viewedTexture.Width, viewedTexture.Height); + Vector2 zoomedTextureSize = textureSize * _zoom; + + Vector2 targetSize = Vector2(_target.Width, _target.Height); + + _effect.Variables["ColorOffset"].SetData(_colorOffset); + _effect.Variables["ColorScale"].SetData(_colorScale); + _effect.Variables["AlphaOffset"].SetData(_alphaOffset); + _effect.Variables["AlphaScale"].SetData(_alphaScale); + + _camera.Left = 0; + _camera.Top = 0; + _camera.Right = targetSize.X; + _camera.Bottom = -targetSize.Y; + _camera.NearPlane = -5; + _camera.FarPlane = 5; + _camera.Update(); + + Renderer2D.BeginScene(_camera); + + switch(_backgroundMode) + { + case .Black: + Renderer2D.DrawQuadPivotCorner(Vector3(0, 0, 1), targetSize, 0, .Black); + case .White: + Renderer2D.DrawQuadPivotCorner(Vector3(0, 0, 1), targetSize, 0, .White); + case .Checkerboard: + float quadSize = 50.0f; + + Vector2 numQuads = (targetSize / 500f) * 10f; + + for(float x = 0; x < numQuads.X; x++) + { + for(float y = 0; y < numQuads.Y; y++) + { + Renderer2D.DrawQuadPivotCorner(Vector3(x * quadSize, -y * quadSize, 1), quadSize.XX, 0, ((x + y) % 2 == 0) ? .White : .Gray); + } + } + break; + } + + Renderer2D.EndScene(); + + var sampler = viewedTexture.SamplerState; + + switch(_sampleMode) + { + case .Point: + viewedTexture.SamplerState = _samplerPoint; + case .Linear: + viewedTexture.SamplerState = _samplerLinear; + } + + Renderer2D.BeginScene(_camera, .SortByTexture, _effect); + + Renderer2D.DrawQuad(Vector3(_position * .(1, -1), 0), zoomedTextureSize, 0, viewedTexture); + + Renderer2D.EndScene(); + + viewedTexture.SamplerState = sampler; + } + } +} diff --git a/GlitchyEngine/BeefProj.toml b/GlitchyEngine/BeefProj.toml index ee76dff..2a9211d 100644 --- a/GlitchyEngine/BeefProj.toml +++ b/GlitchyEngine/BeefProj.toml @@ -1,5 +1,5 @@ FileVersion = 1 -Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", FreeType = "*", cgltf-beef = "*", msdfgen-beef = "*", ImGui = "*", ImGuiImplDX11 = "*", ImGuiImplWin32 = "*", ImGuizmo = "*", Beefy2D = "*", LodePng = "*", GlitchyEngineHelper = "*", bon = "*"} +Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", FreeType = "*", cgltf-beef = "*", msdfgen-beef = "*", ImGui = "*", ImGuiImplDX11 = "*", ImGuiImplWin32 = "*", ImGuizmo = "*", Beefy2D = "*", LodePng = "*", GlitchyEngineHelper = "*", bon = "*", box2d-beef = "*", "Beef.Linq" = "*"} [Project] Name = "GlitchyEngine" diff --git a/GlitchyEngine/src/Application.bf b/GlitchyEngine/src/Application.bf index 577ddbb..0aad8ba 100644 --- a/GlitchyEngine/src/Application.bf +++ b/GlitchyEngine/src/Application.bf @@ -7,13 +7,12 @@ using GlitchyEngine.Content; namespace GlitchyEngine { - public class Application + public abstract class Application { static Application s_Instance = null; private Window _window; private RendererAPI _rendererApi; - private EffectLibrary _effectLibrary; private bool _running = true; private bool _isMinimized = false; @@ -31,12 +30,12 @@ namespace GlitchyEngine public bool IsRunning => _running; public Window Window => _window; - public EffectLibrary EffectLibrary => _effectLibrary; - public IContentManager ContentManager => _contentManager; public bool IsMinimized => _isMinimized; + public GameTime GameTime => _gameTime; + [Inline] public static Application Get() => s_Instance; @@ -55,18 +54,20 @@ namespace GlitchyEngine _window = new Window(.Default); _window.EventCallback = new => OnEvent; + Input.Init(); + + _contentManager = InitContentManager(); + + // TODO: RenderAPI in RenderCommand initialisieren? _rendererApi = new RendererAPI(); _rendererApi.Context = _window.Context; - _contentManager = new ContentManager("./content"); - SamplerStateManager.Init(); + // TODO: Rendercommmand in Renderer initialisieren? RenderCommand.RendererAPI = _rendererApi; - _effectLibrary = new EffectLibrary(); - - Renderer.Init(_window.Context, _effectLibrary); + Renderer.Init(); #if IMGUI _imGuiLayer = new ImGuiLayer(); @@ -77,6 +78,13 @@ namespace GlitchyEngine Settings.Apply(); } + /// Initializes the content manager. + protected abstract IContentManager InitContentManager(); + //{ + // TODO: init default content manager? + //_contentManager = new ContentManager("./content"); + //} + public ~this() { Profiler.ProfileFunction!(); @@ -84,8 +92,6 @@ namespace GlitchyEngine SamplerStateManager.Uninit(); Renderer.Deinit(); - delete _effectLibrary; - delete _contentManager; delete _rendererApi; @@ -167,6 +173,12 @@ namespace GlitchyEngine } } + /// Closes the applcation. + public void Close() + { + _running = false; + } + public void PushLayer(Layer ownLayer) { Profiler.ProfileFunction!(); diff --git a/GlitchyEngine/src/Collections/TreeNode.bf b/GlitchyEngine/src/Collections/TreeNode.bf index ab91281..d4667ff 100644 --- a/GlitchyEngine/src/Collections/TreeNode.bf +++ b/GlitchyEngine/src/Collections/TreeNode.bf @@ -2,10 +2,16 @@ using System.Collections; namespace GlitchyEngine.Collections { + // TODO: TreeNode is very bare minimum + // Destructor? + // RemoveChild? + // Remove in enumerator? + public class TreeNode { public T Value; + public Self Parent; public List Children = new .() ~ DeleteContainerAndItems!(_); public this() {} @@ -24,6 +30,7 @@ namespace GlitchyEngine.Collections } Self newChild = new .(value); + newChild.Parent = this; Children.Add(newChild); @@ -44,5 +51,32 @@ namespace GlitchyEngine.Collections return null; } + + public static ref T operator ->(TreeNode node) + { + return ref node.Value; + } + } + + static + { + public static mixin DeleteTreeAndChildren(TreeNode tree) where T : class, delete + { + InternalDeleteTreeAndChildren(tree); + } + + private static void InternalDeleteTreeAndChildren(TreeNode tree) where T : class, delete + { + for (var child in tree.Children) + { + InternalDeleteTreeAndChildren(child); + } + + delete tree.Value; + + tree.Children.Clear(); + + delete tree; + } } } diff --git a/GlitchyEngine/src/Content/Asset.bf b/GlitchyEngine/src/Content/Asset.bf new file mode 100644 index 0000000..067d6b0 --- /dev/null +++ b/GlitchyEngine/src/Content/Asset.bf @@ -0,0 +1,92 @@ +using GlitchyEngine.Core; +using System; +using Bon; +using Bon.Integrated; +using System.Reflection; +using System.IO; + +namespace GlitchyEngine.Content; + +[BonTarget] +abstract class Asset : RefCounter +{ + internal AssetHandle _handle = .Invalid; + + private append String _identifier; + + internal IContentManager _contentManager; + + /// Gets the identifier of this asset. + /// @remarks The identifier is the name with which the asset was registered in the content manager. + /// This identifier can be used to request the Asset from the content manager. + public StringView Identifier + { + get => _identifier; + internal set => _identifier.Set(value); + } + + /// If true the asset is completely loaded. If false it is only partially loaded (if at all). + public bool Complete { get; internal set; } + + // TODO: do we need unmanaged assets? Probably not... + /// Gets the content manager that manages this asset; or null if this asset isn't managed. + public IContentManager ContentManager => _contentManager; + + public AssetHandle Handle => _handle; + + static this + { + gBonEnv.typeHandlers.Add(typeof(Asset), + ((.)new => AssetSerialize, new => AssetDeserialize)); + } + + protected ~this() + { + // TODO: crash when _contentManager is deleted first... + // TODO: unregister from content manager + //_contentManager?.UnmanageAsset(this); + } + + static void AssetSerialize(BonWriter writer, ValueView value, BonEnvironment environment, SerializeValueState state) + { + Log.EngineLogger.Assert(value.type == typeof(Asset)); + + let identifier = value.Get().Identifier; + writer.String(identifier); + } + + static Result AssetDeserialize(BonReader reader, ValueView value, BonEnvironment environment, DeserializeValueState state) + { + Log.EngineLogger.Assert(value.type == typeof(Asset)); + + String identifier = scope .(); + + Deserialize.String!(reader, ref identifier, environment); + + AssetHandle handle = Content.LoadAsset(identifier); + + if (handle == .Invalid) + { + value.Assign(null); + return .Ok; + } + + Asset asset = Content.GetAsset(handle); + + if (asset != null) + { + Asset oldAsset = value.Get(); + oldAsset.ReleaseRef(); + + value.Assign(asset); + return .Ok; + } + else + { + Deserialize.Error!("Invalid resource path", reader, value.type); + } + } + + //gBonEnv.typeHandlers.Add(typeof(Resource<>), + // ((.)new => ResourceSerialize, (.)new => ResourceDeserialize)); +} \ No newline at end of file diff --git a/GlitchyEngine/src/Content/AssetHandle.bf b/GlitchyEngine/src/Content/AssetHandle.bf new file mode 100644 index 0000000..10f1c84 --- /dev/null +++ b/GlitchyEngine/src/Content/AssetHandle.bf @@ -0,0 +1,336 @@ +using Bon; +using Bon.Integrated; +using System; +using xxHash; +using System.Collections; +using System.Reflection; +using System.Diagnostics; +using GlitchyEngine.Core; + +namespace GlitchyEngine.Content; + +struct AssetHandle : IHashable +{ + private UUID _uuid; + + /// Defines an asset that is invalid. + public const AssetHandle Invalid = .(UUID(0)); + + public bool IsValid => this != .Invalid; + public bool IsInvalid => this == .Invalid; + + /// Create a new random AssetHandle + public this() + { + _uuid = UUID(); + } + + private this(UUID uuid) + { + _uuid = uuid; + } + + static this + { + gBonEnv.typeHandlers.Add(typeof(AssetHandle), + ((.)new => AssetSerialize, new => AssetDeserialize)); + } + + [Inline] + public T Get(IContentManager contentManager = null) where T : Asset + { + return Content.GetAsset(this, contentManager); + } + + public int GetHashCode() => _uuid.GetHashCode(); + + static void AssetSerialize(BonWriter writer, ValueView value, BonEnvironment environment, SerializeValueState state) + { + Log.EngineLogger.Assert(value.type == typeof(AssetHandle)); + + AssetHandle handle = value.Get(); + + if (handle.IsInvalid) + writer.String(""); + else + { + let identifier = handle.Get().Identifier; + writer.String(identifier); + } + } + + static Result AssetDeserialize(BonReader reader, ValueView value, BonEnvironment environment, DeserializeValueState state) + { + Log.EngineLogger.Assert(value.type == typeof(AssetHandle)); + + String identifier = scope .(); + + Deserialize.String!(reader, ref identifier, environment); + + AssetHandle handle = Content.LoadAsset(identifier); + + value.Assign(handle); + + return .Ok; + } +} + +struct AssetHandle where T : Asset +{ + private AssetHandle _handle; + private IContentManager _contentManager; + /* + * We don't increment/decrement the reference counter since we guarantee that we query for the asset every frame. + */ + private T _asset; + //private uint8 _currentFrame; + //private uint64 _actualCurrentFrame = 0; + + public const Self Invalid = .(); + + public bool IsValid => this != .Invalid; + public bool IsInvalid => this == .Invalid; + + public this(AssetHandle handle, IContentManager contentManager = null) + { + _handle = handle; + + _asset = handle.Get(contentManager); + _contentManager = _asset?.ContentManager; + if (_contentManager == null) + _contentManager = contentManager; + //_currentFrame = (uint8)Application.Get().GameTime.FrameCount; + } + + // Creates a new invalid asset handle + private this() + { + _handle = .Invalid; + //_currentFrame = 0; + _contentManager = null; + _asset = null; + } + + static this + { + gBonEnv.typeHandlers.Add(typeof(AssetHandle), + ((.)new => AssetSerialize, new => AssetDeserialize)); + } + + public static implicit operator Self(AssetHandle handle) + { + return Self(handle); + } + + public static implicit operator AssetHandle(Self handle) + { + return handle._handle; + } + + public static implicit operator T(ref Self handle) + { + return handle.Get(); + } + + public T Get(IContentManager contentManager = null) + { + return Content.GetAsset(_handle, contentManager == null ? _contentManager : contentManager); + } + + [Comptime, OnCompile(.TypeInit)] + static void Init() + { + for (var field in typeof(T).GetFields(BindingFlags.FlattenHierarchy)) + { + if (field.IsStatic || !field.IsPublic) + continue; + + String modifier = field.IsPublic ? "public" : "private"; + + String code = scope $""" + {modifier} {field.FieldType} {field.Name} + {{ + get mut + {{ + return Get().{field.Name}; + }} + set mut + {{ + Get().{field.Name} = value; + }} + }} + + + """; + + Compiler.EmitTypeBody(typeof(Self), code); + } + + Dictionary properties = scope .(); + + for (var method in typeof(T).GetMethods(BindingFlags.FlattenHierarchy | BindingFlags.Public)) + { + if (method.IsStatic || !method.IsPublic || method.IsConstructor || method.IsDestructor) + continue; + + // Filter out destructors + if (method.Name == "~this") + continue; + + if (method.Name.StartsWith("get__")) + { + StringView name = method.Name; + name.RemoveFromStart(5); + + if (!properties.TryGetValue(name, var propertyInfo)) + { + propertyInfo = default; + } + + propertyInfo.Getter = method; + + properties[name] = propertyInfo; + + continue; + } + if (method.Name.StartsWith("set__")) + { + StringView name = method.Name; + name.RemoveFromStart(5); + + if (!properties.TryGetValue(name, var propertyInfo)) + { + propertyInfo = default; + } + + propertyInfo.Setter = method; + + properties[name] = propertyInfo; + + continue; + } + + String modifier = method.IsPublic ? "public" : "private"; + + String parameters = scope String(); + String arguments = scope String(); + + for (int param < method.ParamCount) + { + Type paramType = method.GetParamType(param); + StringView paramName = method.GetParamName(param); + /*String buffer = scope .(); + method.GetParamsDecl(buffer);*/ + + if (param != 0) + { + parameters.Append(", "); + arguments.Append(", "); + } + + // TODO: Default value + parameters.AppendF($"{paramType} {paramName}"); + + /*if (!buffer.IsEmpty) + { + parameters.AppendF($"/*{buffer}*/"); + }*/ + + arguments.AppendF($" {paramName}"); + } + + String code = scope $""" + {modifier} {method.ReturnType} {method.Name}({parameters}) mut + {{ + return Get().{method.Name}({arguments}); + }} + + + """; + + Compiler.EmitTypeBody(typeof(Self), code); + } + + for (var (propertyName, property) in properties) + { + Type propertyType = property.Getter?.ReturnType ?? property.Setter?.GetParamType(0); + + String getter = scope .(); + String setter = scope .(); + + if (property.Getter != null) + { + getter.AppendF($""" + get mut + {{ + return Get().{propertyName}; + }} + """); + } + if (property.Setter != null) + { + getter.AppendF($""" + + set mut + {{ + Get().{propertyName} = value; + }} + """); + } + + String code = scope $""" + public {propertyType} {propertyName} + {{ + {getter}{setter} + }} + + + """; + + Compiler.EmitTypeBody(typeof(Self), code); + } + } + + public AssetHandle Cast() + where NewT : Asset + where T : NewT + { + return AssetHandle(this._handle, this._contentManager); + } + + // TODO: Cast up? + public AssetHandle Cast() where NewT : T + { + return AssetHandle(this._handle, this._contentManager); + } + + static void AssetSerialize(BonWriter writer, ValueView value, BonEnvironment environment, SerializeValueState state) + { + Log.EngineLogger.Assert(value.type == typeof(AssetHandle)); + + AssetHandle handle = value.Get>(); + + if (handle.IsInvalid) + writer.String(""); + else + { + let identifier = handle.Get().Identifier; + writer.String(identifier); + } + } + + static Result AssetDeserialize(BonReader reader, ValueView value, BonEnvironment environment, DeserializeValueState state) + { + Log.EngineLogger.Assert(value.type == typeof(AssetHandle)); + + String identifier = scope .(); + + Deserialize.String!(reader, ref identifier, environment); + + AssetHandle handle = Content.LoadAsset(identifier); + + value.Assign>(handle); + + return .Ok; + } +} diff --git a/GlitchyEngine/src/Content/ContentManager.bf b/GlitchyEngine/src/Content/ContentManager.bf index 624d4dd..16c16ad 100644 --- a/GlitchyEngine/src/Content/ContentManager.bf +++ b/GlitchyEngine/src/Content/ContentManager.bf @@ -1,6 +1,8 @@ using System; using System.IO; using xxHash; +using System.Collections; +using Bon; namespace GlitchyEngine.Content { @@ -22,17 +24,168 @@ namespace GlitchyEngine.Content _hash = xxHash.ComputeHash(id); } } + + [BonTarget, BonPolyRegister] + abstract class AssetLoaderConfig + { + [BonIgnore] + protected bool _changed; + + public bool Changed => _changed; + + protected bool SetIfChanged(ref T field, T value) + { + if (field == value) + return false; + + field = value; + _changed = true; + + return true; + } + } + + interface IAssetLoader + { + static List FileExtensions { get; } + + AssetLoaderConfig GetDefaultConfig(); + + /// Loads the asset from the given data stream with the specified config. + /// @param file The stream containing the asset. + /// @param config The configuration which specifies the settings used to load the asset. + /// @param contentManager The content manager used to load the asset. + /// @returns The loaded asset. + Asset LoadAsset(Stream file, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager); + + /// Returns the placeholder asset. + Asset GetPlaceholderAsset(Type assetType); + + /// Returns the error asset. + Asset GetErrorAsset(Type assetType); + } + + static class Content + { + /// Loads the specified asset with the given contentManager or the current applications content manager. + public static AssetHandle LoadAsset(StringView assetIdentifier, IContentManager contentManager = null, bool blocking = false) + { + var contentManager; + + if (contentManager == null) + contentManager = Application.Get().ContentManager; + + AssetHandle handle = contentManager.LoadAsset(assetIdentifier, blocking); + + return handle; + } + + /// Loads the specified asset with the given contentManager or the current applications content manager. + public static T GetAsset(AssetHandle handle, IContentManager contentManager = null) where T : Asset + { + var contentManager; + + if (contentManager == null) + contentManager = Application.Get().ContentManager; + + Asset asset = contentManager.GetAsset(typeof(T), handle); + + return (T)asset; + } + + public static AssetHandle ManageAsset(Asset asset, IContentManager contentManager = null) + { + var contentManager; + + if (contentManager == null) + contentManager = Application.Get().ContentManager; + + return contentManager.ManageAsset(asset); + } + + /*public static AssetHandle ManageAsset(T asset, IContentManager contentManager = null) where T : Asset + { + var contentManager; + + if (contentManager == null) + contentManager = Application.Get().ContentManager; + + contentManager.ManageAsset(asset); + }*/ + } interface IContentManager { - void GetFilePath(String outFilename, String filename); + /// Loads the Asset with the given handle and returns the handle. + AssetHandle LoadAsset(StringView assetIdentifier, bool blocking = false); - Stream GetFile(String filename); + /// Returns the asset for the given handle or null, if it isn't loaded. + Asset GetAsset(AssetHandle handle) + { + return GetAsset(null, handle); + } + + /// Returns the asset for the given handle or the default asset of the given type. + Asset GetAsset(Type assetType, AssetHandle handle); + + /// The content manager will manage the asset (e.g. provide it when LoadAsset is called with the assets identifier) + AssetHandle ManageAsset(Asset asset); + + /// The content manager will no longer manage the asset. + void UnmanageAsset(AssetHandle asset); + + /// Returns a data stream for the given asset. + Stream GetStream(StringView assetIdentifier); + + void RegisterAssetLoader() where T : new, class, IAssetLoader; + void SetAsDefaultAssetLoader(params Span fileExtensions) where T : IAssetLoader; + //void GetFilePath(String outFilename, String filename); + + //Stream GetFile(String filename); } - class ContentManager : IContentManager + class RuntimeContentManager : IContentManager { - private String _contentRoot; + public this() + { + Runtime.NotImplemented(); + } + + public AssetHandle LoadAsset(StringView assetIdentifier, bool blocking = false) + { + Runtime.NotImplemented(); + } + + public Asset GetAsset(Type assetType, AssetHandle handle) + { + Runtime.NotImplemented(); + } + + public AssetHandle ManageAsset(Asset asset) + { + Runtime.NotImplemented(); + } + + public void UnmanageAsset(AssetHandle asset) + { + Runtime.NotImplemented(); + } + + public Stream GetStream(StringView assetIdentifier) + { + Runtime.NotImplemented(); + } + + public void RegisterAssetLoader() where T : IAssetLoader where T : class where T : new + { + Runtime.NotImplemented(); + } + + public void SetAsDefaultAssetLoader(params Span fileExtensions) where T : IAssetLoader + { + Runtime.NotImplemented(); + } + /*private String _contentRoot; [AllowAppend] public this(String contentRoot) @@ -47,12 +200,14 @@ namespace GlitchyEngine.Content { Path.InternalCombine(outFilename, _contentRoot, filename); } - + public Stream GetFile(String filename) { String fullpath = scope .(_contentRoot.Length + 1 + filename.Length); GetFilePath(fullpath, filename); + Log.EngineLogger.AssertDebug(File.Exists(fullpath), "File doesn't exist!"); + FileStream stream = new FileStream(); var result = stream.Open(fullpath, .Read, .Read); @@ -63,6 +218,6 @@ namespace GlitchyEngine.Content } return stream; - } + }*/ } } \ No newline at end of file diff --git a/GlitchyEngine/src/Content/ModelLoader.bf b/GlitchyEngine/src/Content/ModelLoader.bf index 8cfe94f..11bce1c 100644 --- a/GlitchyEngine/src/Content/ModelLoader.bf +++ b/GlitchyEngine/src/Content/ModelLoader.bf @@ -5,6 +5,7 @@ using GlitchyEngine.Math; using GlitchyEngine.Renderer; using GlitchyEngine.Renderer.Animation; using GlitchyEngine.World; +using System.IO; namespace GlitchyEngine.Content { @@ -12,148 +13,223 @@ namespace GlitchyEngine.Content { static readonly Matrix RightToLeftHand = .Scaling(1, 1, -1); - public static void LoadModel(String filename, Effect validationEffect, Material material, EcsWorld world, - List outClips) + public static Result GetMeshNames(String filename, List meshNames) { CGLTF.Options options = .(); CGLTF.Data* data; CGLTF.Result result = CGLTF.ParseFile(options, filename, out data); - - Log.EngineLogger.Assert(result == .Success, "Failed to load model."); - result = CGLTF.LoadBuffers(options, data, filename); + if (!(result case .Success)) + return .Err; - Log.EngineLogger.Assert(result == .Success, "Failed to load buffers"); - - for(var node in data.Scenes[0].Nodes) + for (var mesh in data.Meshes) { - NodesToEntities(data, node, null, world, validationEffect, material, outClips); + meshNames.Add(new String(mesh.Name)); } - + CGLTF.Free(data); + + return .Ok; } - private static void NodesToEntities(CGLTF.Data* data, CGLTF.Node* node, EcsEntity? parentEntity, EcsWorld world, Effect validationEffect, Material material, List clips) + public static GeometryBinding LoadMesh(StringView fileName, StringView meshName, int primitiveIndex) { - EcsEntity entity = world.NewEntity(); + // TODO: add a context to remember which buffers were loaded before so that we don't load the same data multiple times. -#if DEBUG - var nameComponent = world.AssignComponent(entity); + char8* scopedFileName = fileName.ToScopeCStr!(); - if (node.Name != null) + CGLTF.Options options = .(); + CGLTF.Data* data; + CGLTF.Result result = CGLTF.ParseFile(options, scopedFileName, out data); + + if (!(result case .Success)) + return null; + + result = CGLTF.LoadBuffers(options, data, scopedFileName); + + GeometryBinding geoBinding = null; + + for (var mesh in data.Meshes) { - nameComponent.SetName(StringView(node.Name)); - } - else - { - nameComponent.SetName("Unnamed Node"); - } -#endif + var name = StringView(mesh.Name); - if(parentEntity.HasValue) - { - var childParent = world.AssignComponent(entity); - childParent.Entity = parentEntity.Value; - } - - var childTransform = world.AssignComponent(entity); - - if(node.HasMatrix) - { - childTransform.LocalTransform = *(Matrix*)&node.Matrix; - } - else - { - if(node.HasTranslation) - childTransform.Position = *(Vector3*)&node.Translation; - else - childTransform.Position = .Zero; - - if(node.HasRotation) - childTransform.Rotation = *(Quaternion*)&node.Rotation; - else - childTransform.Rotation = .Identity; - - if(node.HasScale) - childTransform.Scale = *(Vector3*)&node.Scale; - else - childTransform.Scale = .(1, 1, 1); - } - - // Invert the Z-Axis of the root Node to convert the coordinate system from right-handed to left-handed - if(parentEntity == null) - childTransform.Scale *= .(1, 1, -1); - - Skeleton skeleton = null; - - if(node.Skin != null) - { - skeleton = ExtractSkeleton(node.Skin); - - LoadAnimationClips(data, node.Skin, skeleton, clips); - } - - if(node.Mesh != null) - { - // If we have only one primitive, add it directly to the entity - if(node.Mesh.Primitives.Length == 1) + if (name == meshName) { - var mesh = world.AssignComponent(entity); + Log.EngineLogger.AssertDebug(primitiveIndex >= 0 && primitiveIndex < mesh.Primitives.Length); - using (var geo = PrimitiveToGeoBinding(node.Mesh.Primitives[0], validationEffect)) - { - mesh.Mesh = geo; - } + geoBinding = PrimitiveToGeoBinding(mesh.Primitives[primitiveIndex]); + break; + } + } - if(skeleton == null) + CGLTF.Free(data); + + return geoBinding; + } + + public static GeometryBinding LoadMesh(Stream data, StringView meshName, int primitiveIndex) + { + // TODO: add a context to remember which buffers were loaded before so that we don't load the same data multiple times. + + uint8[] rawData = new:ScopedAlloc! uint8[data.Length]; + + var dataReadResult = data.TryRead(rawData); + + if (dataReadResult case .Err(let err)) + { + Log.EngineLogger.Error($"Failed to read data from stream. Error: {err}"); + } + + CGLTF.Options options = .(); + CGLTF.Data* modelData; + CGLTF.Result result = CGLTF.Parse(options, (Span)rawData, out modelData); + + if (!(result case .Success)) + return null; + + // TODO: one buffer can be used by multiple primitives, the content manager could manage the buffers + + // TODO: load with content manager + + result = CGLTF.LoadBuffers(options, modelData, (char8*)null); + //result = LoadBuffersWithContentManager(options, modelData, meshName, Application.Get().ContentManager); + + GeometryBinding geoBinding = null; + + for (var mesh in modelData.Meshes) + { + var name = StringView(mesh.Name); + + //if (name == meshName) + { + Log.EngineLogger.AssertDebug(primitiveIndex >= 0 && primitiveIndex < mesh.Primitives.Length); + + geoBinding = PrimitiveToGeoBinding(mesh.Primitives[primitiveIndex]); + break; + } + } + + CGLTF.Free(modelData); + + return geoBinding; + } + + private static CGLTF.Result LoadBuffersWithContentManager(CGLTF.Options options, CGLTF.Data* data, StringView fileName, IContentManager contentManager) + { + if (data.Buffers.Length > 0 && data.Buffers[0].Data == null && data.Buffers[0].Uri == null && !data.Bin.IsEmpty) + { + if ((uint)data.Bin.Length < data.Buffers[0].Size) + return .DataTooShort; + + data.Buffers[0].Data = data.Bin.Ptr; + data.Buffers[0].DataFreeMethod = .None; + } + + for (ref CGLTF.Buffer buffer in ref data.Buffers) + { + if (buffer.Data != null) + continue; + + if (buffer.Uri == null) + continue; + + StringView uri = StringView(buffer.Uri); + + if (uri.StartsWith("data:")) + { + int commaIndex = uri.IndexOf(','); + + //char* comma = strchr(uri, ','); + + if (commaIndex == -1 || commaIndex >= 7 || uri.StartsWith(";base64")) + return .UnknownFormat; + + StringView dataView = uri.Substring(commaIndex + 1); + +#unwarn + CGLTF.Result loadBufferResult = CGLTF.LoadBuffersBase64(&options, buffer.Size, dataView.Ptr, &buffer.Data); + buffer.DataFreeMethod = .MemoryFree; + + return loadBufferResult; + } + else + { + Runtime.NotImplemented(); + + // TODO: Request Buffer from Content Manager + + //int index = uri.IndexOf("://"); + + //if (index == -1) + // return .UnknownFormat; + + // TODO: load buffer file... + //CGLTF.Result res = //cgltf_load_buffer_file(options, data->buffers[i].size, uri, gltf_path, &data->buffers[i].data); + //buffer.DataFreeMethod = cgltf_data_free_method_file_release; + + /*if (res != cgltf_result_success) { - var meshRenderer = world.AssignComponent(entity); - meshRenderer.Material = material; + return res; + }*/ + } + } + + /* + + for (cgltf_size i = 0; i < data->buffers_count; ++i) + { + if (data->buffers[i].data) + { + continue; + } + + const char* uri = data->buffers[i].uri; + + if (uri == NULL) + { + continue; + } + + if (strncmp(uri, "data:", 5) == 0) + { + const char* comma = strchr(uri, ','); + + if (comma && comma - uri >= 7 && strncmp(comma - 7, ";base64", 7) == 0) + { + cgltf_result res = cgltf_load_buffer_base64(options, data->buffers[i].size, comma + 1, &data->buffers[i].data); + data->buffers[i].data_free_method = cgltf_data_free_method_memory_free; + + if (res != cgltf_result_success) + { + return res; + } } else { - var meshRenderer = world.AssignComponent(entity); - meshRenderer.Material = material; - meshRenderer.Skeleton = skeleton; + return cgltf_result_unknown_format; + } + } + else if (strstr(uri, "://") == NULL && gltf_path) + { + cgltf_result res = cgltf_load_buffer_file(options, data->buffers[i].size, uri, gltf_path, &data->buffers[i].data); + data->buffers[i].data_free_method = cgltf_data_free_method_file_release; + + if (res != cgltf_result_success) + { + return res; } } - // otherwise one child-entity per primitive else { - for(var primitive in node.Mesh.Primitives) - { - EcsEntity meshEntity = world.NewEntity(); - - var meshParent = world.AssignComponent(meshEntity); - meshParent.Entity = entity; - - var mesh = world.AssignComponent(meshEntity); - mesh.Mesh = PrimitiveToGeoBinding(primitive, validationEffect); - - if(skeleton == null) - { - var meshRenderer = world.AssignComponent(meshEntity); - meshRenderer.Material = material; - } - else - { - var meshRenderer = world.AssignComponent(meshEntity); - meshRenderer.Material = material; - meshRenderer.Skeleton = skeleton; - } - } + return cgltf_result_unknown_format; } } + */ - skeleton?.ReleaseRef(); - - for(var child in node.Children) - { - NodesToEntities(data, child, entity, world, validationEffect, material, clips); - } + return .Success; } - public static GeometryBinding PrimitiveToGeoBinding(CGLTF.Primitive primitive, Effect validationEffect) + public static GeometryBinding PrimitiveToGeoBinding(CGLTF.Primitive primitive) { GeometryBinding binding = new GeometryBinding(); @@ -265,7 +341,7 @@ namespace GlitchyEngine.Content StringView strView = .(attribute.Name); // Remove number from end of name - while((*(strView.EndPtr - 1)).IsDigit) + while((*(strView.EndPtr - 1)).IsDigit || (*(strView.EndPtr - 1)) == '_') { strView.Length--; } @@ -318,7 +394,7 @@ namespace GlitchyEngine.Content vertexElements[i] = elements[i]; } - VertexLayout layout = new VertexLayout(vertexElements, true, validationEffect.VertexShader); + VertexLayout layout = new VertexLayout(vertexElements, true); binding.SetVertexLayout(layout..ReleaseRefNoDelete()); } diff --git a/GlitchyEngine/src/Core/FilePath.bf b/GlitchyEngine/src/Core/FilePath.bf new file mode 100644 index 0000000..580f239 --- /dev/null +++ b/GlitchyEngine/src/Core/FilePath.bf @@ -0,0 +1,112 @@ +using System; +using System.IO; + +namespace GlitchyEngine.Core; + +// TODO: make usable +class FilePath : IHashable +{ + append String _path = .(); + + public bool IsRooted => Path.IsPathRooted(_path); + + public this() + { + + } + + public this(StringView path) + { + Set(path); + } + + public static implicit operator StringView(FilePath filePath) => filePath._path; + + /// @param fixDirectorySeperators If true all alternative directory seperators will be replaced by the primary seperator. + /// @param resolveRelativeDirectories If true relative directories ('.' and '..') will be removed from the path. + public enum CanonicalizationFlags + { + FixDirectorySeperators = 1, + ResolveRelativeDirectories = _ << 1, + MakeFullPath = _ << 1 + } + + public void Set(StringView path, CanonicalizationFlags canonicalizationFlags = .FixDirectorySeperators | .ResolveRelativeDirectories) + { + _path.Append(path); + Canonicalize(canonicalizationFlags); + } + + /// Converts the path to a canonicalized path. + public void Canonicalize(CanonicalizationFlags canonicalizationFlags = .FixDirectorySeperators | .ResolveRelativeDirectories) + { + if (canonicalizationFlags.HasFlag(.FixDirectorySeperators)) + { + FixDirectorySeperators(); + } + + if (canonicalizationFlags.HasFlag(.ResolveRelativeDirectories)) + { + ResolveRelativeDirectories(); + } + + if (canonicalizationFlags.HasFlag(.MakeFullPath)) + { + MakeFullPath(); + } + } + + public void MakeFullPath() + { + if (IsRooted) + return; + + String buffer = scope String(Path.[Friend]MaxPath); + + Path.GetFullPath(_path, buffer); + + _path..Clear().Append(buffer); + } + + public void FixDirectorySeperators() + { + _path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + } + + public void ResolveRelativeDirectories() + { + // find . + // find entire entry name + // remove entry name (if its only .) + + /*for (char32 c in _path.DecodedChars) + { + if (c == '.') + { + } + } + + for (StringView component in _path.Split(Path.DirectorySeparatorChar)) + { + if (component == ".") + { + // . can be removed without replacement + } + else if (component == "..") + { + // .. can only be removed when not at the start of after another .. + + // e.g. "../foo" and "../../foo" can't be changed + // but "foo/.." can become "foo" + // "foo/../.." can become ".." + } + }*/ + } + + public void Append(StringView newPath) + { + + } + + public int GetHashCode() => _path.GetHashCode(); +} \ No newline at end of file diff --git a/GlitchyEngine/src/Core/RefCounter.bf b/GlitchyEngine/src/Core/RefCounter.bf index 0d24eef..e2f2d8e 100644 --- a/GlitchyEngine/src/Core/RefCounter.bf +++ b/GlitchyEngine/src/Core/RefCounter.bf @@ -7,8 +7,12 @@ namespace GlitchyEngine.Core * Implements the IDisposable interface so that it can be used with a using-Block so that the counter * will be decremented automatically after leaving the block. */ - public class RefCounter : System.RefCounted, IDisposable + public class RefCounter : RefCounted, IDisposable { + protected ~this() + { + } + public void Dispose() { ReleaseRef(); diff --git a/GlitchyEngine/src/Core/UUID.bf b/GlitchyEngine/src/Core/UUID.bf new file mode 100644 index 0000000..d7bc046 --- /dev/null +++ b/GlitchyEngine/src/Core/UUID.bf @@ -0,0 +1,53 @@ +using Bon; +using System; +using Bon.Integrated; +using System.Collections; + +namespace GlitchyEngine.Core +{ + [BonTarget] + struct UUID : IHashable + { + [BonInclude] + private uint64 _uuid; + + private static Random s_Random = new .() ~ delete _; + + static this() + { + gBonEnv.typeHandlers.Add(typeof(UUID), + ((.)new => Serialize, (.)new => Deserialize)); + } + + /// Creates a new random UUID. + public this() + { + _uuid = s_Random.NextU64(); + } + + /// Creates a new UUID with the given value. + public this(uint64 uuid) + { + _uuid = uuid; + } + + public int GetHashCode() + { + return (int)_uuid; + } + + static void Serialize(BonWriter writer, ValueView val, BonEnvironment env, SerializeValueState state) + { + UUID uuid = *(UUID*)val.dataPtr; + + Bon.Integrated.Serialize.[Friend]Integer(typeof(uint64), writer, ValueView(typeof(uint64), &uuid._uuid)); + } + + public static Result Deserialize(BonReader reader, ValueView val, BonEnvironment env, DeserializeValueState state) + { + Bon.Integrated.Deserialize.[Friend]Integer!(typeof(uint64), reader, val); + + return .Ok; + } + } +} \ No newline at end of file diff --git a/GlitchyEngine/src/Events/MouseEvent.bf b/GlitchyEngine/src/Events/MouseEvent.bf index 493eaa1..c785594 100644 --- a/GlitchyEngine/src/Events/MouseEvent.bf +++ b/GlitchyEngine/src/Events/MouseEvent.bf @@ -28,6 +28,33 @@ namespace GlitchyEngine.Events strBuffer.AppendF("MouseMovedEvent: Position: ({}, {})", _mouseX, _mouseY); } } + + public class RawMouseMovedEvent : Event, IEvent + { + private int32 _mouseX, _mouseY; + + public override EventType EventType => .MouseMoved; + + public override StringView Name => "RawMouseMoved"; + + public override EventCategory Category => .Input | .Mouse; + + public static EventType StaticType => .MouseMoved; + + public int32 PositionX => _mouseX; + public int32 PositionY => _mouseY; + + public this(int32 x, int32 y) + { + _mouseX = x; + _mouseY = y; + } + + public override void ToString(String strBuffer) + { + strBuffer.AppendF("RawMouseMovedEvent: Position: ({}, {})", _mouseX, _mouseY); + } + } public class MouseScrolledEvent : Event, IEvent { diff --git a/GlitchyEngine/src/Extension/Bon/Serialize.bf b/GlitchyEngine/src/Extension/Bon/Serialize.bf new file mode 100644 index 0000000..be89cf6 --- /dev/null +++ b/GlitchyEngine/src/Extension/Bon/Serialize.bf @@ -0,0 +1,37 @@ +using System; + +namespace Bon.Integrated +{ + extension Serialize + { + public static void Value(BonWriter writer, StringView identifier, in T value, BonEnvironment env = gBonEnv) + { + writer.Identifier(identifier); + Serialize.Value(writer, ValueView(typeof(T), &value), env); + } + + public static void Value(BonWriter writer, in T value, BonEnvironment env = gBonEnv) + { + Serialize.Value(writer, ValueView(typeof(T), &value), env); + } + } + + extension Deserialize + { + public static Result Value(BonReader reader, StringView identifier, out T value, BonEnvironment env = gBonEnv) + { + value = ?; + + if (Try!(reader.Identifier()) != identifier) + return .Err; + + return Deserialize.Value(reader, ValueView(typeof(T), &value), env); + } + + public static Result Value(BonReader reader, out T value, BonEnvironment env = gBonEnv) + { + value = ?; + return Deserialize.Value(reader, ValueView(typeof(T), &value), env); + } + } +} diff --git a/GlitchyEngine/src/Extension/System/IO/Path.bf b/GlitchyEngine/src/Extension/System/IO/Path.bf new file mode 100644 index 0000000..58f6cf8 --- /dev/null +++ b/GlitchyEngine/src/Extension/System/IO/Path.bf @@ -0,0 +1,47 @@ +using System.Diagnostics; + +namespace System.IO; + +extension Path +{ + public static mixin GetScopedFullPath(String path) + { + String fullPath = scope:: String(Path.MaxPath); + Path.GetFullPath(path, fullPath); + + fullPath + } + + /// Opens the file browser and selects the specified file. + /// @param path The path of the file to select. + public static extern Result OpenFolderAndSelectItem(String path); + + /// Opens the file browser in the given directory. + /// @param directory The directory to show in the file browser. + public static extern Result OpenFolder(String directory); + + /// Shows a dialog in which the user can select which program to open the given file with. + /// @param The Path of the file to open. + public static extern Result OpenWithDialog(String filePath); + + public static void Fixup(String path) + { + path.Replace(AltDirectorySeparatorChar, DirectorySeparatorChar); + path.Replace(scope $".{DirectorySeparatorChar}", ""); + path.Replace(scope $"{DirectorySeparatorChar}.", ""); + + if (path.StartsWith(DirectorySeparatorChar)) + path.Remove(0, 1); + } + + public static void Combine(String target, params StringView[] components) + { + for (var component in components) + { + if ((target.Length > 0) && (!target.EndsWith("\\")) && (!target.EndsWith("/")) && + (!component.StartsWith("\\")) && (!component.StartsWith("/"))) + target.Append(Path.DirectorySeparatorChar); + target.Append(component); + } + } +} \ No newline at end of file diff --git a/GlitchyEngine/src/Extension/System/String.bf b/GlitchyEngine/src/Extension/System/String.bf index e21442d..307cc56 100644 --- a/GlitchyEngine/src/Extension/System/String.bf +++ b/GlitchyEngine/src/Extension/System/String.bf @@ -17,5 +17,11 @@ namespace System target[copiedChars] = '\0'; } + + /// Converts camel case and delimiter-separated words to normal words. + public void ToHumanReadable() + { + // TODO! + } } } \ No newline at end of file diff --git a/GlitchyEngine/src/Generators/NewStructGenerator.bf b/GlitchyEngine/src/Generators/NewStructGenerator.bf index d7fc1bd..dfe5815 100644 --- a/GlitchyEngine/src/Generators/NewStructGenerator.bf +++ b/GlitchyEngine/src/Generators/NewStructGenerator.bf @@ -20,11 +20,10 @@ namespace GlitchyEngine.Generators outFileName.Append(name); outText.AppendF( $""" - namespace {Namespace} + namespace {Namespace}; + + struct {name} {{ - struct {name} - {{ - }} }} """); } diff --git a/GlitchyEngine/src/ImGui/ImGuiExtension.bf b/GlitchyEngine/src/ImGui/ImGuiExtension.bf index cafe8aa..16b85e4 100644 --- a/GlitchyEngine/src/ImGui/ImGuiExtension.bf +++ b/GlitchyEngine/src/ImGui/ImGuiExtension.bf @@ -1,9 +1,21 @@ using GlitchyEngine.Math; using GlitchyEngine.Renderer; using System; +using GlitchyEngine; +using System.Collections; + +namespace GlitchyEngine.Math +{ + extension ColorRGBA + { + internal uint32 ImGuiU32 => ImGui.ImGui.ColorConvertFloat4ToU32((.)(Vector4)this); + } +} namespace ImGui { + using internal GlitchyEngine.Math; + extension ImGui { extension Vec2 @@ -11,116 +23,301 @@ namespace ImGui public static explicit operator Vector2(Vec2 v) => .(v.x, v.y); public static explicit operator Vec2(Vector2 v) => .(v.X, v.Y); } - + extension Vec4 { public static explicit operator Vector4(Vec4 v) => .(v.x, v.y, v.z, v.w); public static explicit operator Vec4(Vector4 v) => .(v.X, v.Y, v.Z, v.W); } + /*public static bool IsItemJustDeactivated() + { + return IsItemDeactivatedAfterEdit(); + } + + public static bool IsItemActiveLastFrame() + { + Context* g = GetCurrentContext(); + if (g.ActiveIdPreviousFrame != 0) + return g.ActiveIdPreviousFrame == g.CurrentWindow.DC.LastItemId; + + return false; + } + + public static bool IsItemJustActivated() + { + return IsItemActive() && !IsItemActiveLastFrame(); + } + + public static bool IsItemEditing() + { + return IsItemActive(); + }*/ + // TODO: Color-functions public static bool ColorEdit3(char* label, ref ColorRGB col, ColorEditFlags flags = (ColorEditFlags) 0) => ColorEdit3Impl(label, *(float[3]*)&col, flags); public static bool ColorEdit3(char* label, ref ColorRGBA col, ColorEditFlags flags = (ColorEditFlags) 0) => ColorEdit3Impl(label, *(float[3]*)&col, flags); - + public static bool ColorEdit4(char* label, ref ColorRGBA col, ColorEditFlags flags = (ColorEditFlags) 0) => ColorEdit4Impl(label, *(float[4]*)&col, flags); - - public static bool ColorPicker3(char* label, ref ColorRGB col, ColorEditFlags flags = (ColorEditFlags) 0) => ColorPicker3Impl(label, *(float[3]*)&col, flags); - public static bool ColorPicker3(char* label, ref ColorRGBA col, ColorEditFlags flags = (ColorEditFlags) 0) => ColorPicker3Impl(label, *(float[3]*)&col, flags); + + public static bool ColorPicker3(char* label, ref ColorRGB col, ColorEditFlags flags = (ColorEditFlags) 0) => ColorPicker3Impl(label, *(float[3]*)&col, flags); + public static bool ColorPicker3(char* label, ref ColorRGBA col, ColorEditFlags flags = (ColorEditFlags) 0) => ColorPicker3Impl(label, *(float[3]*)&col, flags); public static bool ColorPicker4(char* label, ref ColorRGBA col, ColorEditFlags flags = (ColorEditFlags) 0, float* ref_col = null) => ColorPicker4Impl(label, *(float[4]*)&col, flags, ref_col); - - public static extern void Image(Texture2D texture, Vec2 size, Vec2 uv0 = Vec2.Zero, Vec2 uv1 = Vec2.Ones, Vec4 tint_col = Vec4.Ones, Vec4 border_col = Vec4.Zero); - // Wouldn't be necesseary if RenderTarget2D was Texture2D - public static extern void Image(RenderTarget2D texture, Vec2 size, Vec2 uv0 = Vec2.Zero, Vec2 uv1 = Vec2.Ones, Vec4 tint_col = Vec4.Ones, Vec4 border_col = Vec4.Zero); - + + public static void Image(Texture2D texture, Vec2 size, Vec2 uv0 = Vec2.Zero, Vec2 uv1 = Vec2.Ones, Vec4 tint_col = Vec4.Ones, Vec4 border_col = Vec4.Zero) + { + Image(texture.GetViewBinding(), size, uv0, uv1, tint_col, border_col); + } + + public static void Image(SubTexture2D subTexture, Vec2 size, Vec2 uv0 = Vec2.Zero, Vec2 uv1 = Vec2.Ones, Vec4 tint_col = Vec4.Ones, Vec4 border_col = Vec4.Zero) + { + if (uv0 != .Zero || uv1 != .Ones) + Runtime.NotImplemented(); + + Vector2 v = (.)subTexture.TexCoords.XY + subTexture.TexCoords.ZW; + + Image(subTexture.Texture.GetViewBinding(), size, (.)subTexture.TexCoords.XY, (.)v, tint_col, border_col); + } + + public static void Image(RenderTarget2D texture, Vec2 size, Vec2 uv0 = Vec2.Zero, Vec2 uv1 = Vec2.Ones, Vec4 tint_col = Vec4.Ones, Vec4 border_col = Vec4.Zero) + { + Image(texture.GetViewBinding(), size, uv0, uv1, tint_col, border_col); + } + + public static extern void Image(TextureViewBinding textureViewBinding, Vec2 size, Vec2 uv0 = Vec2.Zero, Vec2 uv1 = Vec2.Ones, Vec4 tint_col = Vec4.Ones, Vec4 border_col = Vec4.Zero); + + public static bool ImageButton(SubTexture2D subTexture, Vec2 size, Vec2 uv0 = Vec2.Zero, Vec2 uv1 = Vec2.Ones, int32 frame_padding = -1, Vec4 bg_col = Vec4.Zero, Vec4 tint_col = Vec4.Ones) + { + if (uv0 != .Zero || uv1 != .Ones) + Runtime.NotImplemented(); + + Vector2 v = (.)subTexture.TexCoords.XY + subTexture.TexCoords.ZW; + + return ImageButton(subTexture.Texture.GetViewBinding(), size, (.)subTexture.TexCoords.XY, (.)v, frame_padding, bg_col, tint_col); + } + + public static extern bool ImageButton(TextureViewBinding textureViewBinding, Vec2 size, Vec2 uv0 = Vec2.Zero, Vec2 uv1 = Vec2.Ones, int32 frame_padding = -1, Vec4 bg_col = Vec4.Zero, Vec4 tint_col = Vec4.Ones); + public static void TextUnformatted(StringView text) => TextUnformattedImpl(text.Ptr, text.Ptr + text.Length); public static void PushID(StringView id) => PushID(id.Ptr, id.Ptr + id.Length); - + /// Releases references that accumulated calls like ImGui::Image protected internal static extern void CleanupFrame(); - - /// Control to edit a vector 3 with drag functionality and reset buttons - public static bool EditVector3(StringView label, ref Vector3 value, Vector3 resetValues = .Zero, float dragSpeed = 0.1f, float columnWidth = 100f) + + /// Control to edit a vector 2 with drag functionality and reset buttons + public static bool EditVector2(StringView label, ref Vector2 value, Vector2 resetValues = .Zero, float dragSpeed = 0.1f, float columnWidth = 100f, Vector2 minValue = .Zero, Vector2 maxValue = .Zero) { - bool changed = false; + return EditVector<2>(label, ref *(float[2]*)&value, (float[2])resetValues, dragSpeed, columnWidth, (float[2])minValue, (float[2])maxValue); + } + + /// Control to edit a vector 3 with drag functionality and reset buttons + public static bool EditVector3(StringView label, ref Vector3 value, Vector3 resetValues = .Zero, float dragSpeed = 0.1f, float columnWidth = 100f, Vector3 minValue = .Zero, Vector3 maxValue = .Zero) + { + return EditVector<3>(label, ref *(float[3]*)&value, (float[3])resetValues, dragSpeed, columnWidth, (float[3])minValue, (float[3])maxValue); + } + + /// Control to edit a vector 4 with drag functionality and reset buttons + public static bool EditVector4(StringView label, ref Vector4 value, Vector4 resetValues = .Zero, float dragSpeed = 0.1f, float columnWidth = 100f, Vector4 minValue = .Zero, Vector4 maxValue = .Zero) + { + return EditVector<4>(label, ref *(float[4]*)&value, (float[4])resetValues, dragSpeed, columnWidth, (float[4])minValue, (float[4])maxValue); + } + + static (ColorRGBA Default, ColorRGBA Hovered, ColorRGBA Active)[?] VectorButtonColors = .( + (.(230, 25, 45), .(150, 25, 45), .(230, 90, 90)), + (.(50, 190, 15), .(50, 120, 15), .(116, 190, 99)), + (.(55, 55, 230), .(55, 55, 150), .(90, 90, 230)), + (.(230, 25, 45), .(230, 25, 45), .(230, 25, 45))); + + public static bool EditVector(StringView label, ref float[NumComponents] value, float[NumComponents] resetValues = .(), float dragSpeed = 0.1f, float columnWidth = 100f, float[NumComponents] minValue = .(), float[NumComponents] maxValue = .()) where NumComponents : const int32 + { + const String[?] componentNames = .("X", "Y", "Z", "W"); + const String[?] componentIds = .("##X", "##Y", "##Z", "##W"); + static int mouseLockId = 0; + + bool changed = false; + bool deactivated = false; + PushID(label); defer PopID(); + int currentId = ImGui.GetID(""); + Columns(2); defer Columns(1); SetColumnWidth(0, columnWidth); - + TextUnformatted(label); - + NextColumn(); - - PushMultiItemsWidths(3, CalcItemWidth()); - PushStyleVar(.ItemSpacing, Vec2.Zero); - defer PopStyleVar(); - + + PushMultiItemsWidths(NumComponents, CalcItemWidth()); + float lineHeight = GetFont().FontSize + GetStyle().FramePadding.y * 2.0f; ImGui.Vec2 buttonSize = .(lineHeight + 3.0f, lineHeight); - - PushStyleColor(.Button, Color(230, 25, 45).Value); - PushStyleColor(.ButtonHovered, Color(150, 25, 45).Value); - PushStyleColor(.ButtonActive, Color(230, 120, 130).Value); - - if (Button("X", buttonSize)) + + PushStyleVar(.ItemSpacing, Vec2.Zero); + + for (int i < NumComponents) { - value.X = resetValues.X; - changed = true; + if (i > 0) + { + SameLine(); + } + + PushStyleColor(.Button, VectorButtonColors[i].Default.ImGuiU32); + PushStyleColor(.ButtonHovered, VectorButtonColors[i].Hovered.ImGuiU32); + PushStyleColor(.ButtonActive, VectorButtonColors[i].Active.ImGuiU32); + + if (Button(componentNames[i], buttonSize)) + { + value[i] = resetValues[i]; + changed = true; + } + + SameLine(); + + if (DragFloat(componentIds[i], &value[i], dragSpeed, minValue[i], maxValue[i])) + { + changed = true; + + /*if (mouseLockId != currentId) + { + mouseLockId = currentId; + + Mouse.LockCurrentPosition(mouseLockId); + }*/ + } + + /*if (IsItemDeactivatedAfterEdit()) + { + deactivated = true; + }*/ + + PopItemWidth(); + PopStyleColor(3); } + + PopStyleVar(); - SameLine(); - - if (DragFloat("##X", &value.X, dragSpeed)) - changed = true; - - PopItemWidth(); - SameLine(); - - PopStyleColor(3); - PushStyleColor(.Button, Color(50, 190, 15).Value); - PushStyleColor(.ButtonHovered, Color(50, 120, 15).Value); - PushStyleColor(.ButtonActive, Color(116, 190, 99).Value); - - if (Button("Y", buttonSize)) + /*if (mouseLockId == currentId && deactivated) { - value.Y = resetValues.Y; - changed = true; - } - - SameLine(); + Mouse.UnlockPosition(mouseLockId); + mouseLockId = 0; + }*/ + + return changed; + } + + /// Draws a rectangle with the given color. + public static void DrawRect(Vec2 min, Vec2 max, Color color) + { + ImGui.GetForegroundDrawList().AddRect(min, max, ImGui.GetColorU32(color.Value)); + } - if (DragFloat("##Y", &value.Y, dragSpeed)) - changed = true; - - PopItemWidth(); - SameLine(); - - PopStyleColor(3); - PushStyleColor(.Button, Color(55, 55, 230).Value); - PushStyleColor(.ButtonHovered, Color(55, 55, 150).Value); - PushStyleColor(.ButtonActive, Color(90, 90, 230).Value); + /// Provides a combo Box to select an enum value. + public static bool EnumCombo(StringView label, ref T selectedValue) where T : enum + { + String selectedValueString = scope .(); + selectedValue.ToString(selectedValueString); + // TODO: make selectedValue human readable - if (Button("Z", buttonSize)) + bool changed = false; + + if (ImGui.BeginCombo(label.ToScopeCStr!(), selectedValueString)) { - value.Z = resetValues.Z; - changed = true; + for (let (name, value) in Enum.GetEnumerator()) + { + ImGui.PushID(name); + + if (ImGui.Selectable(name.ToScopeCStr!(), selectedValue == value)) + { + selectedValue = value; + changed = true; + } + + ImGui.PopID(); + } + + ImGui.EndCombo(); } - - SameLine(); - - if (DragFloat("##Z", &value.Z, dragSpeed)) - changed = true; - - PopItemWidth(); - - PopStyleColor(3); return changed; } + + /// Provides a tooltip that will be show when the previously defined Widget is hovered. + public static void AttachTooltip(StringView tooltip) + { + if (!ImGui.IsItemHovered()) + return; + + ImGui.BeginTooltip(); + + ImGui.TextUnformatted(tooltip); + + ImGui.EndTooltip(); + } + + [Comptime] + private static DataType GetDataType() + { + DataType dataType = .COUNT; + + switch (typeof(T)) + { + case typeof(int8): + dataType = .S8; + case typeof(int16): + dataType = .S16; + case typeof(int32): + dataType = .S32; + case typeof(int64): + dataType = .S64; + case typeof(int): + if (sizeof(int) == 8) + dataType = .S64; + else if (sizeof(int) == 4) + dataType = .S32; + + case typeof(uint8): + dataType = .U8; + case typeof(uint16): + dataType = .U16; + case typeof(uint32): + dataType = .U32; + case typeof(uint64): + dataType = .U64; + case typeof(uint): + if (sizeof(uint) == 8) + dataType = .U64; + else if (sizeof(uint) == 4) + dataType = .U32; + //default: + // Runtime.Assert(dataType != .COUNT); + //Log.EngineLogger.Assert(dataType != .COUNT, "Unknown data type."); + } + + return dataType; + } + + // TODO: Add support for floats + public static bool DragScalar(char8* label, ref T value, float dragSpeed = (float) 1.0f, T minValue = typeof(T).MinValue, T maxValue = typeof(T).MaxValue, char8* format = null, SliderFlags sliderFlags = .None) where T : IInteger + { + DataType dataType = GetDataType(); + +#unwarn + return DragScalar(label, dataType, &value, dragSpeed, &minValue, &maxValue, format, sliderFlags); + } + + // TODO: Add support for floats + public static bool SliderScalar(char8* label, ref T value, T minValue = typeof(T).MinValue, T maxValue = typeof(T).MaxValue, char8* format = null, SliderFlags sliderFlags = .None) where T : IInteger + { + DataType dataType = GetDataType(); + +#unwarn + return SliderScalar(label, dataType, &value, &minValue, &maxValue, format, sliderFlags); + } } -} +} \ No newline at end of file diff --git a/GlitchyEngine/src/ImGui/ImGuiLayer.bf b/GlitchyEngine/src/ImGui/ImGuiLayer.bf index 0cb853c..6b491e6 100644 --- a/GlitchyEngine/src/ImGui/ImGuiLayer.bf +++ b/GlitchyEngine/src/ImGui/ImGuiLayer.bf @@ -96,16 +96,19 @@ namespace GlitchyEngine.ImGui ImGui.GetIO().Fonts.Clear(); - String fullpath = scope String(); - Application.Get().ContentManager.GetFilePath(fullpath, settings.FontName); + // TODO: Fix fonts + //String fullpath = scope String(); + //Application.Get().ContentManager.GetFilePath(fullpath, settings.FontName); + /*Application.Get().ContentManager.GetStream(settings.FontName); if (File.Exists(fullpath)) { + ImGui.GetIO().Fonts.AddFontFromMemoryTTF(); ImGui.GetIO().Fonts.AddFontFromFileTTF(fullpath, settings.FontSize); } else - { + {*/ ImGui.GetIO().Fonts.AddFontDefault(); - } + //} #if GE_GRAPHICS_DX11 ImGuiImplDX11.CreateDeviceObjects(); @@ -116,6 +119,8 @@ namespace GlitchyEngine.ImGui Begin(); + ImGui.ShowDemoWindow(); + { Debug.Profiler.ProfileScope!("ImGuiRenderEvent"); diff --git a/GlitchyEngine/src/Input.bf b/GlitchyEngine/src/Input.bf index 17d4dbd..4043878 100644 --- a/GlitchyEngine/src/Input.bf +++ b/GlitchyEngine/src/Input.bf @@ -1,6 +1,8 @@ using System; using GlitchyEngine.Events; using GlitchyEngine.Math; +using System.Collections; +using ImGui; namespace GlitchyEngine { @@ -24,23 +26,152 @@ namespace GlitchyEngine * @remarks IsKeyReleasing(kc) == !IsKeyPressed(kc) && IsKeyReleased(kc) */ public static extern bool IsKeyReleasing(Key keycode); + + // TODO: Should Input be able to set mousepos? + public static extern void SetMousePosition(Int2 pos); public static extern bool IsMouseButtonPressed(MouseButton button); public static extern bool IsMouseButtonReleased(MouseButton button); - public static extern Point GetMousePosition(); + public static extern bool IsMouseButtonPressing(MouseButton button); + public static extern bool IsMouseButtonReleasing(MouseButton button); + public static extern Int2 GetMousePosition(); + public static extern Int2 GetMouseMovement(); + public static extern Int2 GetRawMouseMovement(); public static extern int32 GetMouseX(); public static extern int32 GetMouseY(); public static extern bool WasMouseButtonPressed(MouseButton button); + // public static extern bool WasMouseButtonPressing(MouseButton button); public static extern bool WasMouseButtonReleased(MouseButton button); - public static extern Point GetLastMousePosition(); + //public static extern bool WasMouseButtonReleasing(MouseButton button); + public static extern Int2 GetLastMousePosition(); + public static extern Int2 GetLastMouseMovement(); + public static extern Int2 GetLastRawMouseMovement(); public static extern int32 GetLastMouseX(); public static extern int32 GetLastMouseY(); - - public static extern bool IsMouseButtonPressing(MouseButton button); - public static extern bool IsMouseButtonReleasing(MouseButton button); - public static extern Point GetMouseMovement(); - public static extern void NewFrame(); + public static extern void Init(); + + public static void NewFrame() + { + [Inline] + Impl_NewFrame(); + + Mouse.NewFrame(); + + [Inline] + Impl_EndFrame(); + } + + public static extern void Impl_NewFrame(); + public static extern void Impl_EndFrame(); + + public static void ImGuiDebugDraw() + { + if (ImGui.Begin("Input")) + { + ImGui.TextUnformatted("Last Mouse state"); + + ImGui.Columns(2); + + ImGui.TextUnformatted("Position"); + ImGui.NextColumn(); + ImGui.Text($"{GetLastMousePosition()}"); + ImGui.NextColumn(); + + ImGui.TextUnformatted("Movement"); + ImGui.NextColumn(); + ImGui.Text($"{GetLastMouseMovement()}"); + ImGui.NextColumn(); + + ImGui.TextUnformatted("RawMovement"); + ImGui.NextColumn(); + ImGui.Text($"{GetLastRawMouseMovement()}"); + ImGui.NextColumn(); + + ImGui.Columns(1); + + ImGui.Separator(); + + ImGui.TextUnformatted("Current Mouse state"); + + ImGui.Columns(2); + + ImGui.TextUnformatted("Position"); + ImGui.NextColumn(); + ImGui.Text($"{GetMousePosition()}"); + ImGui.NextColumn(); + + ImGui.TextUnformatted("Movement"); + ImGui.NextColumn(); + ImGui.Text($"{GetMouseMovement()}"); + ImGui.NextColumn(); + + ImGui.TextUnformatted("RawMovement"); + ImGui.NextColumn(); + ImGui.Text($"{GetRawMouseMovement()}"); + ImGui.NextColumn(); + + ImGui.Columns(1); + } + + ImGui.End(); + } + } + + public static class Mouse + { + private static append List<(Int2 Position, int Hash)> _lockPositions = .(); + + public static void LockCurrentPosition(int hash) + { + LockPosition(Input.GetMousePosition(), hash); + } + + public static void LockPosition(Int2 pos, int hash) + { + for (var entry in _lockPositions) + { + if (entry.Hash == hash) + { + entry.Position = pos; + break; + } + } + + _lockPositions.Add((pos, hash)); + } + + public static void UnlockPosition(int hash) + { + for (var entry in _lockPositions) + { + if (entry.Hash == hash) + { + @entry.Remove(); + break; + } + } + + //Log.EngineLogger.AssertDebug(false, "No mouse lock position with hash found."); + } + + public static Int2? LockedPosition; + + public static void NewFrame() + { + if (_lockPositions.Count > 0) + { + var position = _lockPositions.Back.Position; + + LockedPosition = position; + + Input.SetMousePosition(position); + } + else + { + LockedPosition = null; + } + } } } diff --git a/GlitchyEngine/src/Math/Color.bf b/GlitchyEngine/src/Math/Color.bf new file mode 100644 index 0000000..3bb38bf --- /dev/null +++ b/GlitchyEngine/src/Math/Color.bf @@ -0,0 +1,236 @@ +using Bon; +using System; + +namespace GlitchyEngine.Math +{ + /// Represents a four component color with 8 bit per channel + [CRepr] + [BonTarget] + public struct Color + { + // from DirectXColors.h + // Todo: We probably loose some precision here... Could init them with the correct value + public const Color AliceBlue = .( 0.941176534f, 0.972549081f, 1.000000000f, 1.000000000f ); + public const Color AntiqueWhite = .( 0.980392218f, 0.921568692f, 0.843137324f, 1.000000000f ); + public const Color Aqua = .( 0.000000000f, 1.000000000f, 1.000000000f, 1.000000000f ); + public const Color Aquamarine = .( 0.498039246f, 1.000000000f, 0.831372619f, 1.000000000f ); + public const Color Azure = .( 0.941176534f, 1.000000000f, 1.000000000f, 1.000000000f ); + public const Color Beige = .( 0.960784376f, 0.960784376f, 0.862745166f, 1.000000000f ); + public const Color Bisque = .( 1.000000000f, 0.894117713f, 0.768627524f, 1.000000000f ); + public const Color Black = .( 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f ); + public const Color BlanchedAlmond = .( 1.000000000f, 0.921568692f, 0.803921640f, 1.000000000f ); + public const Color Blue = .( 0.000000000f, 0.000000000f, 1.000000000f, 1.000000000f ); + public const Color BlueViolet = .( 0.541176498f, 0.168627456f, 0.886274576f, 1.000000000f ); + public const Color Brown = .( 0.647058845f, 0.164705887f, 0.164705887f, 1.000000000f ); + public const Color BurlyWood = .( 0.870588303f, 0.721568644f, 0.529411793f, 1.000000000f ); + public const Color CadetBlue = .( 0.372549027f, 0.619607866f, 0.627451003f, 1.000000000f ); + public const Color Chartreuse = .( 0.498039246f, 1.000000000f, 0.000000000f, 1.000000000f ); + public const Color Chocolate = .( 0.823529482f, 0.411764741f, 0.117647067f, 1.000000000f ); + public const Color Coral = .( 1.000000000f, 0.498039246f, 0.313725501f, 1.000000000f ); + public const Color CornflowerBlue = .( 0.392156899f, 0.584313750f, 0.929411829f, 1.000000000f ); + public const Color Cornsilk = .( 1.000000000f, 0.972549081f, 0.862745166f, 1.000000000f ); + public const Color Crimson = .( 0.862745166f, 0.078431375f, 0.235294133f, 1.000000000f ); + public const Color Cyan = .( 0.000000000f, 1.000000000f, 1.000000000f, 1.000000000f ); + public const Color DarkBlue = .( 0.000000000f, 0.000000000f, 0.545098066f, 1.000000000f ); + public const Color DarkCyan = .( 0.000000000f, 0.545098066f, 0.545098066f, 1.000000000f ); + public const Color DarkGoldenrod = .( 0.721568644f, 0.525490224f, 0.043137256f, 1.000000000f ); + public const Color DarkGray = .( 0.662745118f, 0.662745118f, 0.662745118f, 1.000000000f ); + public const Color DarkGreen = .( 0.000000000f, 0.392156899f, 0.000000000f, 1.000000000f ); + public const Color DarkKhaki = .( 0.741176486f, 0.717647076f, 0.419607878f, 1.000000000f ); + public const Color DarkMagenta = .( 0.545098066f, 0.000000000f, 0.545098066f, 1.000000000f ); + public const Color DarkOliveGreen = .( 0.333333343f, 0.419607878f, 0.184313729f, 1.000000000f ); + public const Color DarkOrange = .( 1.000000000f, 0.549019635f, 0.000000000f, 1.000000000f ); + public const Color DarkOrchid = .( 0.600000024f, 0.196078449f, 0.800000072f, 1.000000000f ); + public const Color DarkRed = .( 0.545098066f, 0.000000000f, 0.000000000f, 1.000000000f ); + public const Color DarkSalmon = .( 0.913725555f, 0.588235319f, 0.478431404f, 1.000000000f ); + public const Color DarkSeaGreen = .( 0.560784340f, 0.737254918f, 0.545098066f, 1.000000000f ); + public const Color DarkSlateBlue = .( 0.282352954f, 0.239215702f, 0.545098066f, 1.000000000f ); + public const Color DarkSlateGray = .( 0.184313729f, 0.309803933f, 0.309803933f, 1.000000000f ); + public const Color DarkTurquoise = .( 0.000000000f, 0.807843208f, 0.819607913f, 1.000000000f ); + public const Color DarkViolet = .( 0.580392182f, 0.000000000f, 0.827451050f, 1.000000000f ); + public const Color DeepPink = .( 1.000000000f, 0.078431375f, 0.576470613f, 1.000000000f ); + public const Color DeepSkyBlue = .( 0.000000000f, 0.749019623f, 1.000000000f, 1.000000000f ); + public const Color DimGray = .( 0.411764741f, 0.411764741f, 0.411764741f, 1.000000000f ); + public const Color DodgerBlue = .( 0.117647067f, 0.564705908f, 1.000000000f, 1.000000000f ); + public const Color Firebrick = .( 0.698039234f, 0.133333340f, 0.133333340f, 1.000000000f ); + public const Color FloralWhite = .( 1.000000000f, 0.980392218f, 0.941176534f, 1.000000000f ); + public const Color ForestGreen = .( 0.133333340f, 0.545098066f, 0.133333340f, 1.000000000f ); + public const Color Fuchsia = .( 1.000000000f, 0.000000000f, 1.000000000f, 1.000000000f ); + public const Color Gainsboro = .( 0.862745166f, 0.862745166f, 0.862745166f, 1.000000000f ); + public const Color GhostWhite = .( 0.972549081f, 0.972549081f, 1.000000000f, 1.000000000f ); + public const Color Gold = .( 1.000000000f, 0.843137324f, 0.000000000f, 1.000000000f ); + public const Color Goldenrod = .( 0.854902029f, 0.647058845f, 0.125490203f, 1.000000000f ); + public const Color Gray = .( 0.501960814f, 0.501960814f, 0.501960814f, 1.000000000f ); + public const Color Green = .( 0.000000000f, 0.501960814f, 0.000000000f, 1.000000000f ); + public const Color GreenYellow = .( 0.678431392f, 1.000000000f, 0.184313729f, 1.000000000f ); + public const Color Honeydew = .( 0.941176534f, 1.000000000f, 0.941176534f, 1.000000000f ); + public const Color HotPink = .( 1.000000000f, 0.411764741f, 0.705882370f, 1.000000000f ); + public const Color IndianRed = .( 0.803921640f, 0.360784322f, 0.360784322f, 1.000000000f ); + public const Color Indigo = .( 0.294117659f, 0.000000000f, 0.509803951f, 1.000000000f ); + public const Color Ivory = .( 1.000000000f, 1.000000000f, 0.941176534f, 1.000000000f ); + public const Color Khaki = .( 0.941176534f, 0.901960850f, 0.549019635f, 1.000000000f ); + public const Color Lavender = .( 0.901960850f, 0.901960850f, 0.980392218f, 1.000000000f ); + public const Color LavenderBlush = .( 1.000000000f, 0.941176534f, 0.960784376f, 1.000000000f ); + public const Color LawnGreen = .( 0.486274540f, 0.988235354f, 0.000000000f, 1.000000000f ); + public const Color LemonChiffon = .( 1.000000000f, 0.980392218f, 0.803921640f, 1.000000000f ); + public const Color LightBlue = .( 0.678431392f, 0.847058892f, 0.901960850f, 1.000000000f ); + public const Color LightCoral = .( 0.941176534f, 0.501960814f, 0.501960814f, 1.000000000f ); + public const Color LightCyan = .( 0.878431439f, 1.000000000f, 1.000000000f, 1.000000000f ); + public const Color LightGoldenrodYellow = .( 0.980392218f, 0.980392218f, 0.823529482f, 1.000000000f ); + public const Color LightGreen = .( 0.564705908f, 0.933333397f, 0.564705908f, 1.000000000f ); + public const Color LightGray = .( 0.827451050f, 0.827451050f, 0.827451050f, 1.000000000f ); + public const Color LightPink = .( 1.000000000f, 0.713725507f, 0.756862819f, 1.000000000f ); + public const Color LightSalmon = .( 1.000000000f, 0.627451003f, 0.478431404f, 1.000000000f ); + public const Color LightSeaGreen = .( 0.125490203f, 0.698039234f, 0.666666687f, 1.000000000f ); + public const Color LightSkyBlue = .( 0.529411793f, 0.807843208f, 0.980392218f, 1.000000000f ); + public const Color LightSlateGray = .( 0.466666698f, 0.533333361f, 0.600000024f, 1.000000000f ); + public const Color LightSteelBlue = .( 0.690196097f, 0.768627524f, 0.870588303f, 1.000000000f ); + public const Color LightYellow = .( 1.000000000f, 1.000000000f, 0.878431439f, 1.000000000f ); + public const Color Lime = .( 0.000000000f, 1.000000000f, 0.000000000f, 1.000000000f ); + public const Color LimeGreen = .( 0.196078449f, 0.803921640f, 0.196078449f, 1.000000000f ); + public const Color Linen = .( 0.980392218f, 0.941176534f, 0.901960850f, 1.000000000f ); + public const Color Magenta = .( 1.000000000f, 0.000000000f, 1.000000000f, 1.000000000f ); + public const Color Maroon = .( 0.501960814f, 0.000000000f, 0.000000000f, 1.000000000f ); + public const Color MediumAquamarine = .( 0.400000036f, 0.803921640f, 0.666666687f, 1.000000000f ); + public const Color MediumBlue = .( 0.000000000f, 0.000000000f, 0.803921640f, 1.000000000f ); + public const Color MediumOrchid = .( 0.729411781f, 0.333333343f, 0.827451050f, 1.000000000f ); + public const Color MediumPurple = .( 0.576470613f, 0.439215720f, 0.858823597f, 1.000000000f ); + public const Color MediumSeaGreen = .( 0.235294133f, 0.701960802f, 0.443137288f, 1.000000000f ); + public const Color MediumSlateBlue = .( 0.482352972f, 0.407843173f, 0.933333397f, 1.000000000f ); + public const Color MediumSpringGreen = .( 0.000000000f, 0.980392218f, 0.603921592f, 1.000000000f ); + public const Color MediumTurquoise = .( 0.282352954f, 0.819607913f, 0.800000072f, 1.000000000f ); + public const Color MediumVioletRed = .( 0.780392230f, 0.082352944f, 0.521568656f, 1.000000000f ); + public const Color MidnightBlue = .( 0.098039225f, 0.098039225f, 0.439215720f, 1.000000000f ); + public const Color MintCream = .( 0.960784376f, 1.000000000f, 0.980392218f, 1.000000000f ); + public const Color MistyRose = .( 1.000000000f, 0.894117713f, 0.882353008f, 1.000000000f ); + public const Color Moccasin = .( 1.000000000f, 0.894117713f, 0.709803939f, 1.000000000f ); + public const Color NavajoWhite = .( 1.000000000f, 0.870588303f, 0.678431392f, 1.000000000f ); + public const Color Navy = .( 0.000000000f, 0.000000000f, 0.501960814f, 1.000000000f ); + public const Color OldLace = .( 0.992156923f, 0.960784376f, 0.901960850f, 1.000000000f ); + public const Color Olive = .( 0.501960814f, 0.501960814f, 0.000000000f, 1.000000000f ); + public const Color OliveDrab = .( 0.419607878f, 0.556862772f, 0.137254909f, 1.000000000f ); + public const Color Orange = .( 1.000000000f, 0.647058845f, 0.000000000f, 1.000000000f ); + public const Color OrangeRed = .( 1.000000000f, 0.270588249f, 0.000000000f, 1.000000000f ); + public const Color Orchid = .( 0.854902029f, 0.439215720f, 0.839215755f, 1.000000000f ); + public const Color PaleGoldenrod = .( 0.933333397f, 0.909803987f, 0.666666687f, 1.000000000f ); + public const Color PaleGreen = .( 0.596078455f, 0.984313786f, 0.596078455f, 1.000000000f ); + public const Color PaleTurquoise = .( 0.686274529f, 0.933333397f, 0.933333397f, 1.000000000f ); + public const Color PaleVioletRed = .( 0.858823597f, 0.439215720f, 0.576470613f, 1.000000000f ); + public const Color PapayaWhip = .( 1.000000000f, 0.937254965f, 0.835294187f, 1.000000000f ); + public const Color PeachPuff = .( 1.000000000f, 0.854902029f, 0.725490212f, 1.000000000f ); + public const Color Peru = .( 0.803921640f, 0.521568656f, 0.247058839f, 1.000000000f ); + public const Color Pink = .( 1.000000000f, 0.752941251f, 0.796078503f, 1.000000000f ); + public const Color Plum = .( 0.866666734f, 0.627451003f, 0.866666734f, 1.000000000f ); + public const Color PowderBlue = .( 0.690196097f, 0.878431439f, 0.901960850f, 1.000000000f ); + public const Color Purple = .( 0.501960814f, 0.000000000f, 0.501960814f, 1.000000000f ); + public const Color Red = .( 1.000000000f, 0.000000000f, 0.000000000f, 1.000000000f ); + public const Color RosyBrown = .( 0.737254918f, 0.560784340f, 0.560784340f, 1.000000000f ); + public const Color RoyalBlue = .( 0.254901975f, 0.411764741f, 0.882353008f, 1.000000000f ); + public const Color SaddleBrown = .( 0.545098066f, 0.270588249f, 0.074509807f, 1.000000000f ); + public const Color Salmon = .( 0.980392218f, 0.501960814f, 0.447058856f, 1.000000000f ); + public const Color SandyBrown = .( 0.956862807f, 0.643137276f, 0.376470625f, 1.000000000f ); + public const Color SeaGreen = .( 0.180392161f, 0.545098066f, 0.341176480f, 1.000000000f ); + public const Color SeaShell = .( 1.000000000f, 0.960784376f, 0.933333397f, 1.000000000f ); + public const Color Sienna = .( 0.627451003f, 0.321568638f, 0.176470593f, 1.000000000f ); + public const Color Silver = .( 0.752941251f, 0.752941251f, 0.752941251f, 1.000000000f ); + public const Color SkyBlue = .( 0.529411793f, 0.807843208f, 0.921568692f, 1.000000000f ); + public const Color SlateBlue = .( 0.415686309f, 0.352941185f, 0.803921640f, 1.000000000f ); + public const Color SlateGray = .( 0.439215720f, 0.501960814f, 0.564705908f, 1.000000000f ); + public const Color Snow = .( 1.000000000f, 0.980392218f, 0.980392218f, 1.000000000f ); + public const Color SpringGreen = .( 0.000000000f, 1.000000000f, 0.498039246f, 1.000000000f ); + public const Color SteelBlue = .( 0.274509817f, 0.509803951f, 0.705882370f, 1.000000000f ); + public const Color Tan = .( 0.823529482f, 0.705882370f, 0.549019635f, 1.000000000f ); + public const Color Teal = .( 0.000000000f, 0.501960814f, 0.501960814f, 1.000000000f ); + public const Color Thistle = .( 0.847058892f, 0.749019623f, 0.847058892f, 1.000000000f ); + public const Color Tomato = .( 1.000000000f, 0.388235331f, 0.278431386f, 1.000000000f ); + public const Color Transparent = .( 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f ); + public const Color Turquoise = .( 0.250980407f, 0.878431439f, 0.815686345f, 1.000000000f ); + public const Color Violet = .( 0.933333397f, 0.509803951f, 0.933333397f, 1.000000000f ); + public const Color Wheat = .( 0.960784376f, 0.870588303f, 0.701960802f, 1.000000000f ); + public const Color White = .( 1.000000000f, 1.000000000f, 1.000000000f, 1.000000000f ); + public const Color WhiteSmoke = .( 0.960784376f, 0.960784376f, 0.960784376f, 1.000000000f ); + public const Color Yellow = .( 1.000000000f, 1.000000000f, 0.000000000f, 1.000000000f ); + public const Color YellowGreen = .( 0.603921592f, 0.803921640f, 0.196078449f, 1.000000000f ); + + /// The red-component of the color + public uint8 R; + /// The green-component of the color + public uint8 G; + /// The blue-component of the color + public uint8 B; + /// The alpha-component of the color + public uint8 A; + + /// Creates a new instance of Color with all components set to 0. + public this() + { + this = default; + } + + /// Creates a new instance of Color with the RGB-values set to the specified values and alpha set to 255. + public this(uint8 r, uint8 g, uint8 b) + { + R = r; + G = g; + B = b; + A = 255; + } + + /// Creates a new instance of Color with the RGB-values set to the specified values and alpha set to 255. + public this(float r, float g, float b) : this(r, g, b, 1.0f) { } + + /// Creates a new instance of Color with the specified values. + public this(uint8 r, uint8 g, uint8 b, uint8 a) + { + R = r; + G = g; + B = b; + A = a; + } + + const float f = Math.Clamp(12.0f, uint8.MinValue, uint8.MaxValue); + + /// Creates a new instance of Color with the specified values. + public this(float r, float g, float b, float a) + { + R = (uint8)(int)Math.Clamp(r * 255.0f, uint8.MinValue, uint8.MaxValue); + G = (uint8)(int)Math.Clamp(g * 255.0f, uint8.MinValue, uint8.MaxValue); + B = (uint8)(int)Math.Clamp(b * 255.0f, uint8.MinValue, uint8.MaxValue); + A = (uint8)(int)Math.Clamp(a * 255.0f, uint8.MinValue, uint8.MaxValue); + } + + public ref uint8 this[int index] + { + [Unchecked] + get mut + { + return ref (&R)[index]; + } + + [Checked] + get mut + { + Runtime.Assert(index < 0 || index > 3); + return ref (&R)[index]; + } + } + + [Inline] + public uint8* ToPtr() mut + { + return &R; + } + + // Todo: mathematical operations + + public static explicit operator Color(ColorRGBA col) + { + return .(col.R, col.G, col.B, col.A); + } + + public static explicit operator ColorRGBA(Color col) + { + return .(col.R / 255.0f, col.G / 255.0f, col.B / 255.0f, col.A / 255.0f); + } + } +} diff --git a/GlitchyEngine/src/Math/ColorRGB.bf b/GlitchyEngine/src/Math/ColorRGB.bf new file mode 100644 index 0000000..be07e7c --- /dev/null +++ b/GlitchyEngine/src/Math/ColorRGB.bf @@ -0,0 +1,62 @@ +using Bon; +using System; + +using internal GlitchyEngine.Math; + +namespace GlitchyEngine.Math +{ + /// Represents an RGB color. + [CRepr] + [BonTarget] + public struct ColorRGB + { + /** + * A value representing the color of the red component. + * The range of this value is between 0 and 1. + */ + public float R; + /** + * A value representing the color of the green component. + * The range of this value is between 0 and 1. + */ + public float G; + /** + * A value representing the color of the blue component. + * The range of this value is between 0 and 1. + */ + public float B; + + public this() + { + this = default; + } + + public this(float red, float green, float blue) + { + R = red; + G = green; + B = blue; + } + + public this(ColorRGBA color) + { + R = color.R; + G = color.G; + B = color.B; + } + + // Converts a Color from sRGB color space to Linear color space. + public static ColorRGB SRgbToLinear(ColorRGB sRGB) => ColorRGB(Math.Pow(sRGB.R, srgbToLin), Math.Pow(sRGB.G, srgbToLin), Math.Pow(sRGB.B, srgbToLin)); + + // Converts a Color from linear color space to sRGB color space. + public static ColorRGB LinearToSRGB(ColorRGB linear) => ColorRGB(Math.Pow(linear.R, linToSRGB), Math.Pow(linear.G, linToSRGB), Math.Pow(linear.B, linToSRGB)); + + [Inline] +#unwarn + public static explicit operator Vector3(ColorRGB color) => *(Vector3*)&color; + + [Inline] +#unwarn + public static explicit operator ColorRGB(Vector3 color) => *(ColorRGB*)&color; + } +} diff --git a/GlitchyEngine/src/Math/ColorRGBA.bf b/GlitchyEngine/src/Math/ColorRGBA.bf new file mode 100644 index 0000000..9704e42 --- /dev/null +++ b/GlitchyEngine/src/Math/ColorRGBA.bf @@ -0,0 +1,338 @@ +using System; +using Bon; + +using internal GlitchyEngine.Math; + +namespace GlitchyEngine.Math +{ + static + { + internal const float srgbToLin = 2.2f; + internal const float linToSRGB = (float)(1.0 / 2.2); + } + + /** + Represents a four component floating point color + */ + [BonTarget] + [CRepr] + struct ColorRGBA + { + // from DirectXColors.h + public const ColorRGBA AliceBlue = .( 0.941176534f, 0.972549081f, 1.000000000f, 1.000000000f ); + public const ColorRGBA AntiqueWhite = .( 0.980392218f, 0.921568692f, 0.843137324f, 1.000000000f ); + public const ColorRGBA Aqua = .( 0.000000000f, 1.000000000f, 1.000000000f, 1.000000000f ); + public const ColorRGBA Aquamarine = .( 0.498039246f, 1.000000000f, 0.831372619f, 1.000000000f ); + public const ColorRGBA Azure = .( 0.941176534f, 1.000000000f, 1.000000000f, 1.000000000f ); + public const ColorRGBA Beige = .( 0.960784376f, 0.960784376f, 0.862745166f, 1.000000000f ); + public const ColorRGBA Bisque = .( 1.000000000f, 0.894117713f, 0.768627524f, 1.000000000f ); + public const ColorRGBA Black = .( 0.000000000f, 0.000000000f, 0.000000000f, 1.000000000f ); + public const ColorRGBA BlanchedAlmond = .( 1.000000000f, 0.921568692f, 0.803921640f, 1.000000000f ); + public const ColorRGBA Blue = .( 0.000000000f, 0.000000000f, 1.000000000f, 1.000000000f ); + public const ColorRGBA BlueViolet = .( 0.541176498f, 0.168627456f, 0.886274576f, 1.000000000f ); + public const ColorRGBA Brown = .( 0.647058845f, 0.164705887f, 0.164705887f, 1.000000000f ); + public const ColorRGBA BurlyWood = .( 0.870588303f, 0.721568644f, 0.529411793f, 1.000000000f ); + public const ColorRGBA CadetBlue = .( 0.372549027f, 0.619607866f, 0.627451003f, 1.000000000f ); + public const ColorRGBA Chartreuse = .( 0.498039246f, 1.000000000f, 0.000000000f, 1.000000000f ); + public const ColorRGBA Chocolate = .( 0.823529482f, 0.411764741f, 0.117647067f, 1.000000000f ); + public const ColorRGBA Coral = .( 1.000000000f, 0.498039246f, 0.313725501f, 1.000000000f ); + public const ColorRGBA CornflowerBlue = .( 0.392156899f, 0.584313750f, 0.929411829f, 1.000000000f ); + public const ColorRGBA Cornsilk = .( 1.000000000f, 0.972549081f, 0.862745166f, 1.000000000f ); + public const ColorRGBA Crimson = .( 0.862745166f, 0.078431375f, 0.235294133f, 1.000000000f ); + public const ColorRGBA Cyan = .( 0.000000000f, 1.000000000f, 1.000000000f, 1.000000000f ); + public const ColorRGBA DarkBlue = .( 0.000000000f, 0.000000000f, 0.545098066f, 1.000000000f ); + public const ColorRGBA DarkCyan = .( 0.000000000f, 0.545098066f, 0.545098066f, 1.000000000f ); + public const ColorRGBA DarkGoldenrod = .( 0.721568644f, 0.525490224f, 0.043137256f, 1.000000000f ); + public const ColorRGBA DarkGray = .( 0.662745118f, 0.662745118f, 0.662745118f, 1.000000000f ); + public const ColorRGBA DarkGreen = .( 0.000000000f, 0.392156899f, 0.000000000f, 1.000000000f ); + public const ColorRGBA DarkKhaki = .( 0.741176486f, 0.717647076f, 0.419607878f, 1.000000000f ); + public const ColorRGBA DarkMagenta = .( 0.545098066f, 0.000000000f, 0.545098066f, 1.000000000f ); + public const ColorRGBA DarkOliveGreen = .( 0.333333343f, 0.419607878f, 0.184313729f, 1.000000000f ); + public const ColorRGBA DarkOrange = .( 1.000000000f, 0.549019635f, 0.000000000f, 1.000000000f ); + public const ColorRGBA DarkOrchid = .( 0.600000024f, 0.196078449f, 0.800000072f, 1.000000000f ); + public const ColorRGBA DarkRed = .( 0.545098066f, 0.000000000f, 0.000000000f, 1.000000000f ); + public const ColorRGBA DarkSalmon = .( 0.913725555f, 0.588235319f, 0.478431404f, 1.000000000f ); + public const ColorRGBA DarkSeaGreen = .( 0.560784340f, 0.737254918f, 0.545098066f, 1.000000000f ); + public const ColorRGBA DarkSlateBlue = .( 0.282352954f, 0.239215702f, 0.545098066f, 1.000000000f ); + public const ColorRGBA DarkSlateGray = .( 0.184313729f, 0.309803933f, 0.309803933f, 1.000000000f ); + public const ColorRGBA DarkTurquoise = .( 0.000000000f, 0.807843208f, 0.819607913f, 1.000000000f ); + public const ColorRGBA DarkViolet = .( 0.580392182f, 0.000000000f, 0.827451050f, 1.000000000f ); + public const ColorRGBA DeepPink = .( 1.000000000f, 0.078431375f, 0.576470613f, 1.000000000f ); + public const ColorRGBA DeepSkyBlue = .( 0.000000000f, 0.749019623f, 1.000000000f, 1.000000000f ); + public const ColorRGBA DimGray = .( 0.411764741f, 0.411764741f, 0.411764741f, 1.000000000f ); + public const ColorRGBA DodgerBlue = .( 0.117647067f, 0.564705908f, 1.000000000f, 1.000000000f ); + public const ColorRGBA Firebrick = .( 0.698039234f, 0.133333340f, 0.133333340f, 1.000000000f ); + public const ColorRGBA FloralWhite = .( 1.000000000f, 0.980392218f, 0.941176534f, 1.000000000f ); + public const ColorRGBA ForestGreen = .( 0.133333340f, 0.545098066f, 0.133333340f, 1.000000000f ); + public const ColorRGBA Fuchsia = .( 1.000000000f, 0.000000000f, 1.000000000f, 1.000000000f ); + public const ColorRGBA Gainsboro = .( 0.862745166f, 0.862745166f, 0.862745166f, 1.000000000f ); + public const ColorRGBA GhostWhite = .( 0.972549081f, 0.972549081f, 1.000000000f, 1.000000000f ); + public const ColorRGBA Gold = .( 1.000000000f, 0.843137324f, 0.000000000f, 1.000000000f ); + public const ColorRGBA Goldenrod = .( 0.854902029f, 0.647058845f, 0.125490203f, 1.000000000f ); + public const ColorRGBA Gray = .( 0.501960814f, 0.501960814f, 0.501960814f, 1.000000000f ); + public const ColorRGBA Green = .( 0.000000000f, 0.501960814f, 0.000000000f, 1.000000000f ); + public const ColorRGBA GreenYellow = .( 0.678431392f, 1.000000000f, 0.184313729f, 1.000000000f ); + public const ColorRGBA Honeydew = .( 0.941176534f, 1.000000000f, 0.941176534f, 1.000000000f ); + public const ColorRGBA HotPink = .( 1.000000000f, 0.411764741f, 0.705882370f, 1.000000000f ); + public const ColorRGBA IndianRed = .( 0.803921640f, 0.360784322f, 0.360784322f, 1.000000000f ); + public const ColorRGBA Indigo = .( 0.294117659f, 0.000000000f, 0.509803951f, 1.000000000f ); + public const ColorRGBA Ivory = .( 1.000000000f, 1.000000000f, 0.941176534f, 1.000000000f ); + public const ColorRGBA Khaki = .( 0.941176534f, 0.901960850f, 0.549019635f, 1.000000000f ); + public const ColorRGBA Lavender = .( 0.901960850f, 0.901960850f, 0.980392218f, 1.000000000f ); + public const ColorRGBA LavenderBlush = .( 1.000000000f, 0.941176534f, 0.960784376f, 1.000000000f ); + public const ColorRGBA LawnGreen = .( 0.486274540f, 0.988235354f, 0.000000000f, 1.000000000f ); + public const ColorRGBA LemonChiffon = .( 1.000000000f, 0.980392218f, 0.803921640f, 1.000000000f ); + public const ColorRGBA LightBlue = .( 0.678431392f, 0.847058892f, 0.901960850f, 1.000000000f ); + public const ColorRGBA LightCoral = .( 0.941176534f, 0.501960814f, 0.501960814f, 1.000000000f ); + public const ColorRGBA LightCyan = .( 0.878431439f, 1.000000000f, 1.000000000f, 1.000000000f ); + public const ColorRGBA LightGoldenrodYellow = .( 0.980392218f, 0.980392218f, 0.823529482f, 1.000000000f ); + public const ColorRGBA LightGreen = .( 0.564705908f, 0.933333397f, 0.564705908f, 1.000000000f ); + public const ColorRGBA LightGray = .( 0.827451050f, 0.827451050f, 0.827451050f, 1.000000000f ); + public const ColorRGBA LightPink = .( 1.000000000f, 0.713725507f, 0.756862819f, 1.000000000f ); + public const ColorRGBA LightSalmon = .( 1.000000000f, 0.627451003f, 0.478431404f, 1.000000000f ); + public const ColorRGBA LightSeaGreen = .( 0.125490203f, 0.698039234f, 0.666666687f, 1.000000000f ); + public const ColorRGBA LightSkyBlue = .( 0.529411793f, 0.807843208f, 0.980392218f, 1.000000000f ); + public const ColorRGBA LightSlateGray = .( 0.466666698f, 0.533333361f, 0.600000024f, 1.000000000f ); + public const ColorRGBA LightSteelBlue = .( 0.690196097f, 0.768627524f, 0.870588303f, 1.000000000f ); + public const ColorRGBA LightYellow = .( 1.000000000f, 1.000000000f, 0.878431439f, 1.000000000f ); + public const ColorRGBA Lime = .( 0.000000000f, 1.000000000f, 0.000000000f, 1.000000000f ); + public const ColorRGBA LimeGreen = .( 0.196078449f, 0.803921640f, 0.196078449f, 1.000000000f ); + public const ColorRGBA Linen = .( 0.980392218f, 0.941176534f, 0.901960850f, 1.000000000f ); + public const ColorRGBA Magenta = .( 1.000000000f, 0.000000000f, 1.000000000f, 1.000000000f ); + public const ColorRGBA Maroon = .( 0.501960814f, 0.000000000f, 0.000000000f, 1.000000000f ); + public const ColorRGBA MediumAquamarine = .( 0.400000036f, 0.803921640f, 0.666666687f, 1.000000000f ); + public const ColorRGBA MediumBlue = .( 0.000000000f, 0.000000000f, 0.803921640f, 1.000000000f ); + public const ColorRGBA MediumOrchid = .( 0.729411781f, 0.333333343f, 0.827451050f, 1.000000000f ); + public const ColorRGBA MediumPurple = .( 0.576470613f, 0.439215720f, 0.858823597f, 1.000000000f ); + public const ColorRGBA MediumSeaGreen = .( 0.235294133f, 0.701960802f, 0.443137288f, 1.000000000f ); + public const ColorRGBA MediumSlateBlue = .( 0.482352972f, 0.407843173f, 0.933333397f, 1.000000000f ); + public const ColorRGBA MediumSpringGreen = .( 0.000000000f, 0.980392218f, 0.603921592f, 1.000000000f ); + public const ColorRGBA MediumTurquoise = .( 0.282352954f, 0.819607913f, 0.800000072f, 1.000000000f ); + public const ColorRGBA MediumVioletRed = .( 0.780392230f, 0.082352944f, 0.521568656f, 1.000000000f ); + public const ColorRGBA MidnightBlue = .( 0.098039225f, 0.098039225f, 0.439215720f, 1.000000000f ); + public const ColorRGBA MintCream = .( 0.960784376f, 1.000000000f, 0.980392218f, 1.000000000f ); + public const ColorRGBA MistyRose = .( 1.000000000f, 0.894117713f, 0.882353008f, 1.000000000f ); + public const ColorRGBA Moccasin = .( 1.000000000f, 0.894117713f, 0.709803939f, 1.000000000f ); + public const ColorRGBA NavajoWhite = .( 1.000000000f, 0.870588303f, 0.678431392f, 1.000000000f ); + public const ColorRGBA Navy = .( 0.000000000f, 0.000000000f, 0.501960814f, 1.000000000f ); + public const ColorRGBA OldLace = .( 0.992156923f, 0.960784376f, 0.901960850f, 1.000000000f ); + public const ColorRGBA Olive = .( 0.501960814f, 0.501960814f, 0.000000000f, 1.000000000f ); + public const ColorRGBA OliveDrab = .( 0.419607878f, 0.556862772f, 0.137254909f, 1.000000000f ); + public const ColorRGBA Orange = .( 1.000000000f, 0.647058845f, 0.000000000f, 1.000000000f ); + public const ColorRGBA OrangeRed = .( 1.000000000f, 0.270588249f, 0.000000000f, 1.000000000f ); + public const ColorRGBA Orchid = .( 0.854902029f, 0.439215720f, 0.839215755f, 1.000000000f ); + public const ColorRGBA PaleGoldenrod = .( 0.933333397f, 0.909803987f, 0.666666687f, 1.000000000f ); + public const ColorRGBA PaleGreen = .( 0.596078455f, 0.984313786f, 0.596078455f, 1.000000000f ); + public const ColorRGBA PaleTurquoise = .( 0.686274529f, 0.933333397f, 0.933333397f, 1.000000000f ); + public const ColorRGBA PaleVioletRed = .( 0.858823597f, 0.439215720f, 0.576470613f, 1.000000000f ); + public const ColorRGBA PapayaWhip = .( 1.000000000f, 0.937254965f, 0.835294187f, 1.000000000f ); + public const ColorRGBA PeachPuff = .( 1.000000000f, 0.854902029f, 0.725490212f, 1.000000000f ); + public const ColorRGBA Peru = .( 0.803921640f, 0.521568656f, 0.247058839f, 1.000000000f ); + public const ColorRGBA Pink = .( 1.000000000f, 0.752941251f, 0.796078503f, 1.000000000f ); + public const ColorRGBA Plum = .( 0.866666734f, 0.627451003f, 0.866666734f, 1.000000000f ); + public const ColorRGBA PowderBlue = .( 0.690196097f, 0.878431439f, 0.901960850f, 1.000000000f ); + public const ColorRGBA Purple = .( 0.501960814f, 0.000000000f, 0.501960814f, 1.000000000f ); + public const ColorRGBA Red = .( 1.000000000f, 0.000000000f, 0.000000000f, 1.000000000f ); + public const ColorRGBA RosyBrown = .( 0.737254918f, 0.560784340f, 0.560784340f, 1.000000000f ); + public const ColorRGBA RoyalBlue = .( 0.254901975f, 0.411764741f, 0.882353008f, 1.000000000f ); + public const ColorRGBA SaddleBrown = .( 0.545098066f, 0.270588249f, 0.074509807f, 1.000000000f ); + public const ColorRGBA Salmon = .( 0.980392218f, 0.501960814f, 0.447058856f, 1.000000000f ); + public const ColorRGBA SandyBrown = .( 0.956862807f, 0.643137276f, 0.376470625f, 1.000000000f ); + public const ColorRGBA SeaGreen = .( 0.180392161f, 0.545098066f, 0.341176480f, 1.000000000f ); + public const ColorRGBA SeaShell = .( 1.000000000f, 0.960784376f, 0.933333397f, 1.000000000f ); + public const ColorRGBA Sienna = .( 0.627451003f, 0.321568638f, 0.176470593f, 1.000000000f ); + public const ColorRGBA Silver = .( 0.752941251f, 0.752941251f, 0.752941251f, 1.000000000f ); + public const ColorRGBA SkyBlue = .( 0.529411793f, 0.807843208f, 0.921568692f, 1.000000000f ); + public const ColorRGBA SlateBlue = .( 0.415686309f, 0.352941185f, 0.803921640f, 1.000000000f ); + public const ColorRGBA SlateGray = .( 0.439215720f, 0.501960814f, 0.564705908f, 1.000000000f ); + public const ColorRGBA Snow = .( 1.000000000f, 0.980392218f, 0.980392218f, 1.000000000f ); + public const ColorRGBA SpringGreen = .( 0.000000000f, 1.000000000f, 0.498039246f, 1.000000000f ); + public const ColorRGBA SteelBlue = .( 0.274509817f, 0.509803951f, 0.705882370f, 1.000000000f ); + public const ColorRGBA Tan = .( 0.823529482f, 0.705882370f, 0.549019635f, 1.000000000f ); + public const ColorRGBA Teal = .( 0.000000000f, 0.501960814f, 0.501960814f, 1.000000000f ); + public const ColorRGBA Thistle = .( 0.847058892f, 0.749019623f, 0.847058892f, 1.000000000f ); + public const ColorRGBA Tomato = .( 1.000000000f, 0.388235331f, 0.278431386f, 1.000000000f ); + public const ColorRGBA Transparent = .( 0.000000000f, 0.000000000f, 0.000000000f, 0.000000000f ); + public const ColorRGBA Turquoise = .( 0.250980407f, 0.878431439f, 0.815686345f, 1.000000000f ); + public const ColorRGBA Violet = .( 0.933333397f, 0.509803951f, 0.933333397f, 1.000000000f ); + public const ColorRGBA Wheat = .( 0.960784376f, 0.870588303f, 0.701960802f, 1.000000000f ); + public const ColorRGBA White = .( 1.000000000f, 1.000000000f, 1.000000000f, 1.000000000f ); + public const ColorRGBA WhiteSmoke = .( 0.960784376f, 0.960784376f, 0.960784376f, 1.000000000f ); + public const ColorRGBA Yellow = .( 1.000000000f, 1.000000000f, 0.000000000f, 1.000000000f ); + public const ColorRGBA YellowGreen = .( 0.603921592f, 0.803921640f, 0.196078449f, 1.000000000f ); + + /// The red-component of the color + public float R; + /// The green-component of the color + public float G; + /// The blue-component of the color + public float B; + /// The alpha-component of the color + public float A; + + /// Creates a new instance of ColorRGBA with all components set to 0. + public this() + { + this = default; + } + + /// Creates a new instance of ColorRGBA with the specified values. + public this(float r, float g, float b, float a = 1.0f) + { + R = r; + G = g; + B = b; + A = a; + } + + /// Creates a new instance of ColorRGBA with the specified values. + public this(uint8 r, uint8 g, uint8 b, uint8 a = 255) + { + R = r / 255f; + G = g / 255f; + B = b / 255f; + A = a / 255f; + } + + /// Creates a new instance of ColorRGBA with the specified values. + public this(ColorRGB color, float a = 1.0f) + { + R = color.R; + G = color.G; + B = color.B; + A = a; + } + + public ref float this[int index] + { + [Unchecked] + get mut + { + return ref (&R)[index]; + } + + [Checked] + get mut + { + Runtime.Assert(index < 0 || index > 3); + return ref (&R)[index]; + } + } + + [Inline] + public float* ToPtr() mut + { + return &R; + } + + /// + /// Addition + /// + + public void operator +=(ColorRGBA value) mut + { + R += value.R; + G += value.G; + B += value.B; + A += value.A; + } + + public static ColorRGBA operator +(ColorRGBA left, ColorRGBA right) + { + return .(left.R + right.R, left.G + right.G, left.B + right.B, left.A + right.A); + } + + /// + /// Subtraction + /// + + public void operator -=(ColorRGBA value) mut + { + R -= value.R; + G -= value.G; + B -= value.B; + A -= value.A; + } + + public static ColorRGBA operator -(ColorRGBA left, ColorRGBA right) + { + return .(left.R - right.R, left.G - right.G, left.B - right.B, left.A - right.A); + } + + /// + /// Multiplication + /// + + public void operator *=(ColorRGBA value) mut + { + R *= value.R; + G *= value.G; + B *= value.B; + A *= value.A; + } + + public void operator *=(float value) mut + { + R *= value; + G *= value; + B *= value; + A *= value; + } + + public static ColorRGBA operator *(ColorRGBA left, ColorRGBA right) + { + return .(left.R * right.R, left.G * right.G, left.B * right.B, left.A * right.A); + } + + public static ColorRGBA operator *(ColorRGBA left, float right) + { + return .(left.R * right, left.G * right, left.B * right, left.A * right); + } + + public static ColorRGBA operator *(float left, ColorRGBA right) + { + return .(left * right.R, left * right.G, left * right.B, left * right.A); + } + + /// + /// Division + /// + + public void operator /=(float value) mut + { + R /= value; + G /= value; + B /= value; + A /= value; + } + + public static ColorRGBA operator /(ColorRGBA left, float right) + { + return .(left.R / right, left.G / right, left.B / right, left.A / right); + } + + public static ColorRGBA operator /(float left, ColorRGBA right) + { + return .(left / right.R, left / right.G, left / right.B, left / right.A); + } + + public static implicit operator ColorRGB(ColorRGBA color) + { + return .(color.R, color.G, color.B); + } + + // Converts a Color from sRGB color space to Linear color space. + public static ColorRGBA SRgbToLinear(ColorRGBA sRGB) => ColorRGBA(Math.Pow(sRGB.R, srgbToLin), Math.Pow(sRGB.G, srgbToLin), Math.Pow(sRGB.B, srgbToLin), sRGB.A); + + // Converts a Color from linear color space to sRGB color space. + public static ColorRGBA LinearToSRGB(ColorRGBA linear) => ColorRGBA(Math.Pow(linear.R, linToSRGB), Math.Pow(linear.G, linToSRGB), Math.Pow(linear.B, linToSRGB), linear.A); + + [Inline] +#unwarn + public static explicit operator Vector4(ColorRGBA color) => *(Vector4*)&color; + + [Inline] +#unwarn + public static explicit operator ColorRGBA(Vector4 color) => *(ColorRGBA*)&color; + } +} diff --git a/GlitchyEngine/src/Math/MathDefs.bf b/GlitchyEngine/src/Math/MathDefs.bf index 40531a0..0c66dfa 100644 --- a/GlitchyEngine/src/Math/MathDefs.bf +++ b/GlitchyEngine/src/Math/MathDefs.bf @@ -2,11 +2,6 @@ using System; namespace GlitchyEngine.Math { - typealias Color = DirectX.Color; - typealias ColorRGB = DirectX.ColorRGB; - typealias ColorRGBA = DirectX.ColorRGBA; - typealias Matrix3x3 = DirectX.Math.Matrix3x3; typealias Matrix4x3 = DirectX.Math.Matrix4x3; - typealias Matrix = DirectX.Math.Matrix; } diff --git a/GlitchyEngine/src/Math/MathHelper.bf b/GlitchyEngine/src/Math/MathHelper.bf index 3a7ad5a..32b7aec 100644 --- a/GlitchyEngine/src/Math/MathHelper.bf +++ b/GlitchyEngine/src/Math/MathHelper.bf @@ -77,5 +77,26 @@ namespace GlitchyEngine.Math { return Math.Abs(value) < epsilon; } + + /// Returns the point that lies on the unit circle at the specified angle. + public static Vector2 CirclePoint(float angle, float radius = 1.0f) + { + return .(Math.Cos(angle), Math.Sin(angle)) * radius; + } + + public static Vector2 Pow(Vector2 v, float p) + { + return Vector2(Math.Pow(v.X, p), Math.Pow(v.Y, p)); + } + + public static Vector3 Pow(Vector3 v, float p) + { + return Vector3(Math.Pow(v.X, p), Math.Pow(v.Y, p), Math.Pow(v.Z, p)); + } + + public static Vector4 Pow(Vector4 v, float p) + { + return Vector4(Math.Pow(v.X, p), Math.Pow(v.Y, p), Math.Pow(v.Z, p), Math.Pow(v.W, p)); + } } } diff --git a/GlitchyEngine/src/Math/Matrix.bf b/GlitchyEngine/src/Math/Matrix.bf new file mode 100644 index 0000000..ff6ca60 --- /dev/null +++ b/GlitchyEngine/src/Math/Matrix.bf @@ -0,0 +1,877 @@ +using System; + +namespace GlitchyEngine.Math; + +/** + * Represents a 4 by 4 column-major matrix. + */ +[Union] +public struct Matrix +{ + public struct Values + { + public float _11, _21, _31, _41, + _12, _22, _32, _42, + _13, _23, _33, _43, + _14, _24, _34, _44; + } + + public const Matrix Zero = .(); + public const Matrix Identity = .(.UnitX, .UnitY, .UnitZ, .UnitW); + + public using Values V; + public float[4][4] Values; + public Vector4[4] Columns; + + /// Creates a new zero-matrix. + public this() => this = default; + + /** + * Initializes a new Matrix. + * @param value The value that will be assigned to all components. + */ + /// Creates a new matrix. + public this(float value) + { + this = ?; + _11 = _12 = _13 = _14 = + _21 = _22 = _23 = _24 = + _31 = _32 = _33 = _34 = + _41 = _42 = _43 = _44 = value; + } + + /// Creates a new matrix and initializes it with the given entries. + public this(float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33) + { + Values[0][0] = m00; Values[0][1] = m10; Values[0][2] = m20; Values[0][3] = m30; + Values[1][0] = m01; Values[1][1] = m11; Values[1][2] = m21; Values[1][3] = m31; + Values[2][0] = m02; Values[2][1] = m12; Values[2][2] = m22; Values[2][3] = m32; + Values[3][0] = m03; Values[3][1] = m13; Values[3][2] = m23; Values[3][3] = m33; + } + + /// Creates a new matrix and initializes it with the given column-vectors. + public this(Vector4 c0, Vector4 c1, Vector4 c2, Vector4 c3) + { + Columns[0] = c0; + Columns[1] = c1; + Columns[2] = c2; + Columns[3] = c3; + } + + public ref Vector3 Right + { + [Inline] + get + { +#unwarn + return ref *(Vector3*)&Columns[0]; + } + } + + public ref Vector3 Up + { + [Inline] + get + { +#unwarn + return ref *(Vector3*)&Columns[1]; + } + } + + public ref Vector3 Forward + { + [Inline] + get + { +#unwarn + return ref *(Vector3*)&Columns[2]; + } + } + + public ref Vector3 Translation + { + [Inline] + get + { +#unwarn + return ref *(Vector3*)&Columns[3]; + } + } + + public Vector3 Scale + { + [Inline] + get => .(_11, _22, _33); + + [Inline] + set mut + { + _11 = value.X; + _22 = value.Y; + _33 = value.Z; + } + } + + public ref float this[int row, int column] + { + get + { +#unwarn + return ref *(float*)&Values[column][row]; + } + + [Checked] + get + { + if(column < 0 || column > 3 || row < 0 || row > 3) + Internal.ThrowIndexOutOfRange(); + +#unwarn + return ref *(float*)&Values[column][row]; + } + } + + public ref Vector4 this[int column] + { + get + { +#unwarn + return ref *(Vector4*)&Columns[column]; + } + + + [Checked] + get + { + if(column < 0 || column > 3) + Internal.ThrowIndexOutOfRange(); + +#unwarn + return ref *(Vector4*)&Columns[column]; + } + } + + // + // Assignment Operators + // + + // Addition + + public void operator +=(Matrix value) mut + { + Columns[0] += value.Columns[0]; + Columns[1] += value.Columns[1]; + Columns[2] += value.Columns[2]; + Columns[3] += value.Columns[3]; + } + + // Matrix + Scalar : Matrix + Scalar * Identity + public void operator +=(float scalar) mut + { + _11 += scalar; + _22 += scalar; + _33 += scalar; + _44 += scalar; + } + + // Subtraction + + public void operator -=(Matrix value) mut + { + Columns[0] -= value.Columns[0]; + Columns[1] -= value.Columns[1]; + Columns[2] -= value.Columns[2]; + Columns[3] -= value.Columns[3]; + } + + // Matrix - Scalar : Matrix - Scalar * Identity + public void operator -=(float scalar) mut + { + _11 -= scalar; + _22 -= scalar; + _33 -= scalar; + _44 -= scalar; + } + + // Multiplication + + public void operator *=(float scalar) mut + { + Columns[0] *= scalar; + Columns[1] *= scalar; + Columns[2] *= scalar; + Columns[3] *= scalar; + } + + public void operator *=(Matrix value) mut + { + this = this * value; + } + + // Divide + + public void operator /=(float scalar) mut + { + float inv = 1.0f / scalar; + Columns[0] *= inv; + Columns[1] *= inv; + Columns[2] *= inv; + Columns[3] *= inv; + } + + // + // Operators + // + + // Addition + + public static Matrix operator +(Matrix left, Matrix right) + { + return .(left.Columns[0] + right.Columns[0], + left.Columns[1] + right.Columns[1], + left.Columns[2] + right.Columns[2], + left.Columns[3] + right.Columns[3]); + } + + public static Matrix operator +(Matrix left, float right) + { + Matrix result = left; + result._11 += right; + result._22 += right; + result._33 += right; + result._44 += right; + + return result; + } + + public static Matrix operator +(float left, Matrix right) + { + Matrix result = right; + result._11 += left; + result._22 += left; + result._33 += left; + result._44 += left; + + return result; + } + + // Subtraction + + public static Matrix operator -(Matrix left, Matrix right) + { + return .(left.Columns[0] - right.Columns[0], + left.Columns[1] - right.Columns[1], + left.Columns[2] - right.Columns[2], + left.Columns[3] - right.Columns[3]); + } + + public static Matrix operator -(Matrix value, float scalar) + { + Matrix result = value; + result._11 -= scalar; + result._22 -= scalar; + result._33 -= scalar; + result._44 -= scalar; + + return result; + } + + public static Matrix operator -(float scalar, Matrix value) + { + Matrix result = value; + result._11 -= scalar; + result._22 -= scalar; + result._33 -= scalar; + result._44 -= scalar; + + return result; + } + + public static Matrix operator -(Matrix value) + { + return .(-value.Columns[0], + -value.Columns[1], + -value.Columns[2], + -value.Columns[3]); + } + + // Multiplication + + public static Matrix operator *(Matrix left, Matrix right) + { +#unwarn + var l = &left.V; +#unwarn + var r = &right.V; + + Matrix result = ?; + + result._11 = (l._11 * r._11) + (l._12 * r._21) + (l._13 * r._31) + (l._14 * r._41); + result._12 = (l._11 * r._12) + (l._12 * r._22) + (l._13 * r._32) + (l._14 * r._42); + result._13 = (l._11 * r._13) + (l._12 * r._23) + (l._13 * r._33) + (l._14 * r._43); + result._14 = (l._11 * r._14) + (l._12 * r._24) + (l._13 * r._34) + (l._14 * r._44); + + result._21 = (l._21 * r._11) + (l._22 * r._21) + (l._23 * r._31) + (l._24 * r._41); + result._22 = (l._21 * r._12) + (l._22 * r._22) + (l._23 * r._32) + (l._24 * r._42); + result._23 = (l._21 * r._13) + (l._22 * r._23) + (l._23 * r._33) + (l._24 * r._43); + result._24 = (l._21 * r._14) + (l._22 * r._24) + (l._23 * r._34) + (l._24 * r._44); + + result._31 = (l._31 * r._11) + (l._32 * r._21) + (l._33 * r._31) + (l._34 * r._41); + result._32 = (l._31 * r._12) + (l._32 * r._22) + (l._33 * r._32) + (l._34 * r._42); + result._33 = (l._31 * r._13) + (l._32 * r._23) + (l._33 * r._33) + (l._34 * r._43); + result._34 = (l._31 * r._14) + (l._32 * r._24) + (l._33 * r._34) + (l._34 * r._44); + + result._41 = (l._41 * r._11) + (l._42 * r._21) + (l._43 * r._31) + (l._44 * r._41); + result._42 = (l._41 * r._12) + (l._42 * r._22) + (l._43 * r._32) + (l._44 * r._42); + result._43 = (l._41 * r._13) + (l._42 * r._23) + (l._43 * r._33) + (l._44 * r._43); + result._44 = (l._41 * r._14) + (l._42 * r._24) + (l._43 * r._34) + (l._44 * r._44); + + return result; + } + + public static Matrix operator *(Matrix value, float scalar) + { + return .(value.Columns[0] * scalar, + value.Columns[1] * scalar, + value.Columns[2] * scalar, + value.Columns[3] * scalar); + } + + public static Matrix operator *(float scalar, Matrix value) + { + return .(value.Columns[0] * scalar, + value.Columns[1] * scalar, + value.Columns[2] * scalar, + value.Columns[3] * scalar); + } + + /** + * Multiplies a matrix and a column-vector resulting in a column vector. + */ + public static Vector4 operator *(Matrix matrix, Vector4 columnVector) + { +#unwarn + var m = &matrix.V; + + Vector4 result = ?; + result.X = (m._11 * columnVector.X) + (m._12 * columnVector.Y) + (m._13 * columnVector.Z) + (m._14 * columnVector.W); + result.Y = (m._21 * columnVector.X) + (m._22 * columnVector.Y) + (m._23 * columnVector.Z) + (m._24 * columnVector.W); + result.Z = (m._31 * columnVector.X) + (m._32 * columnVector.Y) + (m._33 * columnVector.Z) + (m._34 * columnVector.W); + result.W = (m._41 * columnVector.X) + (m._42 * columnVector.Y) + (m._43 * columnVector.Z) + (m._44 * columnVector.W); + return result; + } + + /** + * Multiplies a row-vector and a matrix resulting in a row vector. + */ + public static Vector4 operator *(Vector4 rowVector, Matrix matrix) + { +#unwarn + var m = &matrix.V; + + Vector4 result = ?; + result.X = (rowVector.X * m._11) + (rowVector.Y * m._21) + (rowVector.Z * m._31) + (rowVector.W * m._41); + result.Y = (rowVector.X * m._12) + (rowVector.Y * m._22) + (rowVector.Z * m._32) + (rowVector.W * m._42); + result.Z = (rowVector.X * m._13) + (rowVector.Y * m._23) + (rowVector.Z * m._33) + (rowVector.W * m._43); + result.W = (rowVector.X * m._14) + (rowVector.Y * m._24) + (rowVector.Z * m._34) + (rowVector.W * m._44); + return result; + } + + // Divison + + public static Matrix operator /(Matrix m, float s) + { + float f = 1 / s; + Matrix M = m; + + return .(M.Columns[0] * f, M.Columns[1] * f, M.Columns[2] * f, M.Columns[3] * f); + } + + public static Matrix Scaling(float scale) + { + return .(scale, 0, 0, 0, + 0, scale, 0, 0, + 0, 0, scale, 0, + 0, 0, 0, 1); + } + + public static Matrix Scaling(float scaleX, float scaleY, float scaleZ) + { + return .(scaleX, 0, 0, 0, + 0, scaleY, 0, 0, + 0, 0, scaleZ, 0, + 0, 0, 0, 1); + } + + public static Matrix Scaling(Vector3 scale) + { + return .(scale.X, 0, 0, 0, + 0, scale.Y, 0, 0, + 0, 0, scale.Z, 0, + 0, 0, 0, 1); + } + + public static Matrix Translation(float x, float y, float z) + { + return .(1, 0, 0, x, + 0, 1, 0, y, + 0, 0, 1, z, + 0, 0, 0, 1); + } + + public static Matrix Translation(Vector3 translation) + { + return .(1, 0, 0, translation.X, + 0, 1, 0, translation.Y, + 0, 0, 1, translation.Z, + 0, 0, 0, 1); + } + + public static Matrix RotationX(float rot) + { + float sin = Math.Sin(rot); + float cos = Math.Cos(rot); + + return .(1, 0, 0, 0, + 0, cos, -sin, 0, + 0, sin, cos, 0, + 0, 0, 0, 1); + } + + public static Matrix RotationY(float rot) + { + float sin = Math.Sin(rot); + float cos = Math.Cos(rot); + + return .(cos, 0, sin, 0, + 0, 1, 0, 0, + -sin, 0, cos, 0, + 0, 0, 0, 1); + } + + public static Matrix RotationZ(float rot) + { + float sin = Math.Sin(rot); + float cos = Math.Cos(rot); + + return .(cos, -sin, 0, 0, + sin, cos, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + } + + /** + * Calculates a view Matrix that is located at specified postion and looks at the given target. + * @param position The cameras position. + * @param target The point the camera looks at. + * @param up A vector defining the up direction of the camera. + * @returns a view matrix. + */ + public static Matrix LookAt(Vector3 position, Vector3 target, Vector3 up) + { + Vector3 forward = target - position; + forward.Normalize(); + + Vector3 right = Vector3.Cross(up, forward); + right.Normalize(); + + Vector3 newUp = Vector3.Cross(forward, right); + newUp.Normalize(); + + Matrix result = .Identity; + + result.Forward = forward; + result.Up = up; + result.Right = right; + result.Translation.X = -Vector3.Dot(position, right); + result.Translation.Y = -Vector3.Dot(position, up); + result.Translation.Z = -Vector3.Dot(position, forward); + + return result; + } + + /// Returns the transpose of this matrix + [DisableChecks] + public Matrix Transpose() + { + return .(_11, _21, _31, _41, + _12, _22, _32, _42, + _13, _23, _33, _43, + _14, _24, _34, _44); + } + + /** + Calculates the inverse of the matrix. + */ + [DisableChecks] + public float Determinant() mut + { + // From: Lengyel, Eric. Foundations of Game Engine Development, Volume 1: Mathematics (S.61). Kindle-Version. + + Vector3 a = *(Vector3*)&Columns[0]; + Vector3 b = *(Vector3*)&Columns[1]; + Vector3 c = *(Vector3*)&Columns[2]; + Vector3 d = *(Vector3*)&Columns[3]; + + float x = this[3, 0]; + float y = this[3, 1]; + float z = this[3, 2]; + float w = this[3, 3]; + + Vector3 s = Vector3.Cross(a, b); + Vector3 t = Vector3.Cross(c, d); + Vector3 u = y * a - x * b; + Vector3 v = w * c - z * d; + + return Vector3.Dot(s, v) + Vector3.Dot(t, u); + } + + /** + Calculates the inverse of the matrix. + */ + public Matrix Invert() + { + // From: Lengyel, Eric. Foundations of Game Engine Development, Volume 1: Mathematics (S.61). Kindle-Version. + +#unwarn + Vector3 a = *(Vector3*)&Columns[0]; +#unwarn + Vector3 b = *(Vector3*)&Columns[1]; +#unwarn + Vector3 c = *(Vector3*)&Columns[2]; +#unwarn + Vector3 d = *(Vector3*)&Columns[3]; + + float x = this[3, 0]; + float y = this[3, 1]; + float z = this[3, 2]; + float w = this[3, 3]; + + Vector3 s = Vector3.Cross(a, b); + Vector3 t = Vector3.Cross(c, d); + Vector3 u = y * a - x * b; + Vector3 v = w * c - z * d; + + float invDet = 1.0f / (Vector3.Dot(s, v) + Vector3.Dot(t, u)); + + s *= invDet; + t *= invDet; + u *= invDet; + v *= invDet; + + Vector3 r0 = Vector3.Cross(b, v) + t * y; + Vector3 r1 = Vector3.Cross(v, a) - t * x; + Vector3 r2 = Vector3.Cross(d, u) + s * w; + Vector3 r3 = Vector3.Cross(u, c) - s * z; + return .(r0.X, r0.Y, r0.Z, -Vector3.Dot(b, t), + r1.X, r1.Y, r1.Z, Vector3.Dot(a, t), + r2.X, r2.Y, r2.Z, -Vector3.Dot(d, s), + r3.X, r3.Y, r3.Z, Vector3.Dot(c, s)); + } + + /// Calculates the inverse of the matrix. + public static Matrix Invert(in Matrix matrix) + { + // From: Lengyel, Eric. Foundations of Game Engine Development, Volume 1: Mathematics (S.61). Kindle-Version. + +#unwarn + Vector3 a = *(Vector3*)&matrix.Columns[0]; +#unwarn + Vector3 b = *(Vector3*)&matrix.Columns[1]; +#unwarn + Vector3 c = *(Vector3*)&matrix.Columns[2]; +#unwarn + Vector3 d = *(Vector3*)&matrix.Columns[3]; + + float x = matrix[3, 0]; + float y = matrix[3, 1]; + float z = matrix[3, 2]; + float w = matrix[3, 3]; + + Vector3 s = Vector3.Cross(a, b); + Vector3 t = Vector3.Cross(c, d); + Vector3 u = y * a - x * b; + Vector3 v = w * c - z * d; + + float invDet = 1.0f / (Vector3.Dot(s, v) + Vector3.Dot(t, u)); + + s *= invDet; + t *= invDet; + u *= invDet; + v *= invDet; + + Vector3 r0 = Vector3.Cross(b, v) + t * y; + Vector3 r1 = Vector3.Cross(v, a) - t * x; + Vector3 r2 = Vector3.Cross(d, u) + s * w; + Vector3 r3 = Vector3.Cross(u, c) - s * z; + return .(r0.X, r0.Y, r0.Z, -Vector3.Dot(b, t), + r1.X, r1.Y, r1.Z, Vector3.Dot(a, t), + r2.X, r2.Y, r2.Z, -Vector3.Dot(d, s), + r3.X, r3.Y, r3.Z, Vector3.Dot(c, s)); + } + + /** + * Creates a perspective projection matrix. + * @param fovY The vertical field of view. + * @param aspectRation The aspect ratio of the viewport. + * @param nearPlane The distance to the near plane. + * @param farPlane The distance to the far plane. + */ + public static Matrix PerspectiveProjection(float fovY, float aspectRatio, float nearPlane, float farPlane) + { + // Lengyel, Eric. Foundations of Game Engine Development, Volume 2: Rendering (Seite82). . Kindle-Version. + + float g = 1.0f / Math.Tan(fovY * 0.5f); + float k = farPlane / (farPlane - nearPlane); + + return .(g / aspectRatio, 0, 0, 0, + 0, g, 0, 0, + 0, 0, k, -nearPlane * k, + 0, 0, 1, 0); + } + + /** + * Creates a perspective projection matrix with reversed near- and far plane. + * (i.e. Points on near plane have z-value of 1 and points of far plane have z-value of 0) + * @param fovY The vertical field of view. + * @param aspectRation The aspect ratio of the viewport. + * @param nearPlane The distance to the near plane. + * @param farPlane The distance to the far plane. + */ + public static Matrix ReversedPerspectiveProjection(float fovY, float aspectRatio, float nearPlane, float farPlane) + { + // Lengyel, Eric. Foundations of Game Engine Development, Volume 2: Rendering (Seite86). . Kindle-Version. + + float g = 1.0f / Math.Tan(fovY * 0.5f); + float k = nearPlane / (nearPlane - farPlane); + + return .(g / aspectRatio, 0, 0, 0, + 0, g, 0, 0, + 0, 0, k, -farPlane * k, + 0, 0, 1, 0); + } + + /** + * Creates a perspective projection matrix with a far plane at infinity. + * @param fovY The vertical field of view. + * @param aspectRation The aspect ratio of the viewport. + * @param nearPlane The distance to the near plane. + * @param ε An offset to account for floating point round-off errors at infinity. + * Note: Use a tiny value significant compared to the floating-point value of one. + */ + public static Matrix InfinitePerspectiveProjection(float fovY, float aspectRatio, float nearPlane, float ε = 1e-6f) + { + // Lengyel, Eric. Foundations of Game Engine Development, Volume 2: Rendering (Seite83). . Kindle-Version. + + float g = 1.0f / Math.Tan(fovY * 0.5f); + + float f = 1 - ε; + + return .(g / aspectRatio, 0, 0, 0, + 0, g, 0, 0, + 0, 0, f, -nearPlane * f, + 0, 0, 1, 0); + } + + /** + * Creates a perspective projection matrix with a far plane at infinity with reversed near- and far plane. + * (i.e. Points on near plane have z-value of 1 and points of far plane have z-value of 0) + * @param fovY The vertical field of view. + * @param aspectRation The aspect ratio of the viewport. + * @param nearPlane The distance to the near plane. + * @param ε An offset to account for floating point round-off errors at infinity. + * Note: Use a tiny value significant compared to the floating-point value of one. + */ + public static Matrix ReversedInfinitePerspectiveProjection(float fovY, float aspectRatio, float nearPlane, float ε = 1e-6f) + { + // Lengyel, Eric. Foundations of Game Engine Development, Volume 2: Rendering (Seite88). . Kindle-Version. + + float g = 1.0f / Math.Tan(fovY * 0.5f); + + return .(g / aspectRatio, 0, 0, 0, + 0, g, 0, 0, + 0, 0, ε, nearPlane * (1 - ε), + 0, 0, 1, 0); + } + + /** + * Creates an orthographic projection matrix with the camera centered at the near-plane. + * @param width The width of the view volume. + * @param height The height of the view volume. + * @param depth The depth of the view volume. + */ + public static Matrix OrthographicProjection(float width, float height, float depth) + { + // Lengyel, Eric. Foundations of Game Engine Development, Volume 2: Rendering (Seite91). . Kindle-Version. + return .(2.0f / width, 0, 0, 0, + 0, 2.0f/height, 0, 0, + 0, 0, 1.0f / depth, 0, + 0, 0, 0, 1); + } + + /** + * Creates an orthographic projection matrix. + * @param left The left side of the view volume. + * @param right The right side of the view volume. + * @param top The top side of the view volume. + * @param bottom The bottom side of the view volume. + * @param near The near plane of the view volume. + * @param far The far plane of the view volume. + */ + public static Matrix OrthographicProjectionOffCenter(float left, float right, float top, float bottom, float near, float far) + { + // Lengyel, Eric. Foundations of Game Engine Development, Volume 2: Rendering (Seite91). . Kindle-Version. + + float w_inv = 1.0f / (right - left); + float h_inv = 1.0f / (top - bottom); + float d_inv = 1.0f / (far - near); + + return .(2.0f * w_inv, 0.0f, 0.0f, -(right + left) * w_inv, + 0.0f, 2.0f * h_inv, 0.0f, -(bottom + top) * h_inv, + 0.0f, 0.0f, d_inv, -near * d_inv, + 0.0f, 0.0f, 0.0f, 1.0f); + } + + public static bool operator ==(Matrix left, Matrix right) + { + return Matrix.Equals(left, right); + } + + public static bool operator !=(Matrix left, Matrix right) + { + return !Matrix.Equals(left, right); + } + + public static bool Equals(Matrix left, Matrix right) + { + return left.Values == right.Values; + } + + public static explicit operator Matrix3x3(Matrix value) + { + return .(value.Right, value.Up, value.Forward); + } + + public static void Exponent(ref Matrix matrix, int exponent, out Matrix result) + { + if(exponent == 0) + result = .Identity; + else if(exponent == 1) + result = matrix; + else if(exponent > 1) + { + result = .Identity; + Matrix b = matrix; + + var exponent; + + for(;exponent > 0;) + { + if(exponent & 1 > 0) + result *= b; + + exponent >>= 1; + + if(exponent > 0) + b *= b; + } + + } + else // Exponent < 0 + { + Matrix m = matrix.Invert(); + Exponent(ref m, -exponent, out result); + } + } + + /** + * Orthogonalizes the matrix. + */ + public void Orthogonalize() mut + { + Columns[1] -= .Project(Columns[1], Columns[0]); + Columns[2] -= .Project(Columns[2], Columns[0]) + .Project(Columns[2], Columns[1]); + Columns[3] -= .Project(Columns[3], Columns[0]) + .Project(Columns[3], Columns[1]) + .Project(Columns[3], Columns[2]); + } + + /** + * Orthonormalizes the matrix. + */ + public void Orthonormalize() mut + { + Columns[0].Normalize(); + Columns[1] = .Normalize(.Reject(Columns[1], Columns[0])); + Columns[2] = .Normalize(.Reject(.Reject(Columns[2], Columns[0]), Columns[1])); + Columns[3] = .Normalize(.Reject(.Reject(.Reject(Columns[2], Columns[0]), Columns[1]), Columns[2])); + } + public static Self RotationQuaternion(Quaternion rotation) + { + float xSq = 2 * rotation.X * rotation.X; + float ySq = 2 * rotation.Y * rotation.Y; + float zSq = 2 * rotation.Z * rotation.Z; + + float xy = 2 * rotation.X * rotation.Y; + float xz = 2 * rotation.X * rotation.Z; + float xw = 2 * rotation.X * rotation.W; + float yz = 2 * rotation.Y * rotation.Z; + float yw = 2 * rotation.Y * rotation.W; + float zw = 2 * rotation.Z * rotation.W; + + Self result = ?; + + result._11 = 1 - ySq - zSq; + result._21 = xy + zw; + result._31 = xz - yw; + result._41 = 0; + + result._12 = xy - zw; + result._22 = 1 - xSq - zSq; + result._32 = yz + xw; + result._42 = 0; + + result._13 = xz + yw; + result._23 = yz - xw; + result._33 = 1 - xSq - ySq; + result._43 = 0; + + result._14 = 0; + result._24 = 0; + result._34 = 0; + result._44 = 1; + + return result; + } + + public static void Decompose(Self matrix, out Vector3 position, out Quaternion rotation, out Vector3 scale) + { + var matrix; + + // Translation -> get last column + position = matrix.Translation; + // Zero translation for next step + matrix.Translation = .Zero; + + // TODO: this doesn't detect mirroring + + // Extract scaling from matrix + scale.X = (*(Vector3*)&matrix.Columns[0]).Magnitude(); + scale.Y = (*(Vector3*)&matrix.Columns[1]).Magnitude(); + scale.Z = (*(Vector3*)&matrix.Columns[2]).Magnitude(); + + if(MathHelper.IsZero(scale.X) || MathHelper.IsZero(scale.Y) || MathHelper.IsZero(scale.Z)) + { + rotation = .Identity; + return; + } + + // Remove scale from matrix (normalize the columns) + matrix.Columns[0] /= scale.X; + matrix.Columns[1] /= scale.Y; + matrix.Columns[2] /= scale.Z; + + rotation = Quaternion.FromMatrix(matrix); + } +} diff --git a/GlitchyEngine/src/Math/MatrixExtension.bf b/GlitchyEngine/src/Math/MatrixExtension.bf deleted file mode 100644 index b2ce8b1..0000000 --- a/GlitchyEngine/src/Math/MatrixExtension.bf +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using GlitchyEngine.Math; -using GlitchyEngine; - -namespace DirectX.Math -{ - extension Matrix - { - typealias Vec3 = GlitchyEngine.Math.Vector3; - - public static Self RotationQuaternion(Quaternion rotation) - { - float xSq = 2 * rotation.X * rotation.X; - float ySq = 2 * rotation.Y * rotation.Y; - float zSq = 2 * rotation.Z * rotation.Z; - - float xy = 2 * rotation.X * rotation.Y; - float xz = 2 * rotation.X * rotation.Z; - float xw = 2 * rotation.X * rotation.W; - float yz = 2 * rotation.Y * rotation.Z; - float yw = 2 * rotation.Y * rotation.W; - float zw = 2 * rotation.Z * rotation.W; - - Self result = ?; - - result.V._11 = 1 - ySq - zSq; - result.V._21 = xy + zw; - result.V._31 = xz - yw; - result.V._41 = 0; - - result.V._12 = xy - zw; - result.V._22 = 1 - xSq - zSq; - result.V._32 = yz + xw; - result.V._42 = 0; - - result.V._13 = xz + yw; - result.V._23 = yz - xw; - result.V._33 = 1 - xSq - ySq; - result.V._43 = 0; - - result.V._14 = 0; - result.V._24 = 0; - result.V._34 = 0; - result.V._44 = 1; - - return result; - } - - public static void Decompose(Self matrix, out Vec3 position, out Quaternion rotation, out Vec3 scale) - { - var matrix; - - // Translation -> get last column - position = matrix.Translation; - // Zero translation for next step - matrix.Translation = .Zero; - - // TODO: this doesn't detect mirroring - - // Extract scaling from matrix - scale.X = (*(Vec3*)&matrix.Columns[0]).Magnitude(); - scale.Y = (*(Vec3*)&matrix.Columns[1]).Magnitude(); - scale.Z = (*(Vec3*)&matrix.Columns[2]).Magnitude(); - - if(MathHelper.IsZero(scale.X) || MathHelper.IsZero(scale.Y) || MathHelper.IsZero(scale.Z)) - { - rotation = .Identity; - return; - } - - // Remove scale from matrix (normalize the columns) - matrix.Columns[0] /= scale.X; - matrix.Columns[1] /= scale.Y; - matrix.Columns[2] /= scale.Z; - - rotation = Quaternion.FromMatrix(matrix); - } - - /*[Test] - static void TestQuaternionToMatrix() - { - // Rotation around Z-Axis by 90° - Quaternion quat = .(0, 0, 0.707107f, 0.707107f); - quat.Normalize(); - - - }*/ - } -} diff --git a/GlitchyEngine/src/Math/Point.bf b/GlitchyEngine/src/Math/Point.bf deleted file mode 100644 index 91ae8fb..0000000 --- a/GlitchyEngine/src/Math/Point.bf +++ /dev/null @@ -1,131 +0,0 @@ -using System; - -namespace GlitchyEngine.Math -{ - - /** - * A 2D point represented by two 32bit integers. - */ - public struct Point - { - public int32 X, Y; - - /** - * Creates a new instance of a @Point with both components set to zero. - */ - public this() => this = default; - - /** - * Creates a new instance of a @Point with both components set to the specified value. - * @param value The value for both components. - */ - public this(int32 value) - { - X = value; - Y = value; - } - - /** - * Creates a new instance of a @Point. - * @param x The value for the x-component. - * @param y The value for the y-component. - */ - public this(int32 x, int32 y) - { - X = x; - Y = y; - } - - // - // Unary Operators - // - - public static Point operator +(Point value) => value; - - public static Point operator -(Point value) => .(-value.X, -value.Y); - - // - // Binary Operators - // - - public static Point operator +(Point left, Point right) => .(left.X + right.X, left.Y + right.Y); - public static Point operator +(int32 left, Point right) => .(left + right.X, left + right.Y); - public static Point operator +(Point left, int32 right) => .(left.X + right, left.Y + right); - - public static Point operator -(Point left, Point right) => .(left.X - right.X, left.Y - right.Y); - public static Point operator -(int32 left, Point right) => .(left - right.X, left - right.Y); - public static Point operator -(Point left, int32 right) => .(left.X - right, left.Y - right); - - public static Point operator *(Point left, Point right) => .(left.X * right.X, left.Y * right.Y); - public static Point operator *(int32 left, Point right) => .(left * right.X, left * right.Y); - public static Point operator *(Point left, int32 right) => .(left.X * right, left.Y * right); - - public static Point operator /(Point left, Point right) => .(left.X / right.X, left.Y / right.Y); - public static Point operator /(int32 left, Point right) => .(left / right.X, left / right.Y); - public static Point operator /(Point left, int32 right) => .(left.X / right, left.Y / right); - - // - // Assignment Operators - // - - public void operator +=(Point value) mut - { - X += value.X; - Y += value.Y; - } - - public void operator +=(int32 value) mut - { - X += value; - Y += value; - } - - public void operator -=(Point value) mut - { - X -= value.X; - Y -= value.Y; - } - - public void operator -=(int32 value) mut - { - X -= value; - Y -= value; - } - - public void operator *=(Point value) mut - { - X *= value.X; - Y *= value.Y; - } - - public void operator *=(int32 value) mut - { - X *= value; - Y *= value; - } - - public void operator /=(Point value) mut - { - X /= value.X; - Y /= value.Y; - } - - public void operator /=(int32 value) mut - { - X /= value; - Y /= value; - } - - // - // Equality - // - public static bool operator ==(Point left, Point right) => left.X == right.X && left.Y == right.Y; - public static bool operator !=(Point left, Point right) => left.X != right.X || left.Y != right.Y; - - // - // Misc - // - public override void ToString(String strBuffer) => strBuffer.AppendF("X={0} Y={1}", X, Y); - } - -} diff --git a/GlitchyEngine/src/Math/Quaternion.bf b/GlitchyEngine/src/Math/Quaternion.bf index 5fc3e27..9e55d76 100644 --- a/GlitchyEngine/src/Math/Quaternion.bf +++ b/GlitchyEngine/src/Math/Quaternion.bf @@ -1,7 +1,9 @@ +using Bon; using System; namespace GlitchyEngine.Math { + [BonTarget] public struct Quaternion { public const Quaternion Zero = .(); @@ -142,7 +144,7 @@ namespace GlitchyEngine.Math { // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ - var m = matrix.V; + var m = matrix; Quaternion result = ?; diff --git a/GlitchyEngine/src/Math/Vector2.bf b/GlitchyEngine/src/Math/Vector2.bf index f2eff59..21ed285 100644 --- a/GlitchyEngine/src/Math/Vector2.bf +++ b/GlitchyEngine/src/Math/Vector2.bf @@ -1,8 +1,10 @@ +using Bon; using System; namespace GlitchyEngine.Math { - [SwizzleVector(2, "Vector")] + [BonTarget] + [SwizzleVector(2, "GlitchyEngine.Math.Vector")] public struct Vector2 { public const Vector2 Zero = .(0f, 0f); @@ -282,12 +284,16 @@ namespace GlitchyEngine.Math public override void ToString(String strBuffer) => strBuffer.AppendF("X:{0} Y:{1}", X, Y); - [Inline] - public static explicit operator Self(float value) => Self(value); - public bool Equals(Vector2 v, float epsilon = Math.[Friend]sMachineEpsilonFloat) { return (Math.Abs(v.X - X) < epsilon) && (Math.Abs(v.Y - Y) < epsilon); } + + [Inline] + public static explicit operator Self(float value) => Self(value); + + [Inline] +#unwarn + public static explicit operator float[2](Vector2 value) => *(float[2]*)&value; } } diff --git a/GlitchyEngine/src/Math/Vector3.bf b/GlitchyEngine/src/Math/Vector3.bf index 013d1ac..2aaba43 100644 --- a/GlitchyEngine/src/Math/Vector3.bf +++ b/GlitchyEngine/src/Math/Vector3.bf @@ -1,8 +1,10 @@ +using Bon; using System; namespace GlitchyEngine.Math { - [SwizzleVector(3, "Vector")] + [BonTarget] + [SwizzleVector(3, "GlitchyEngine.Math.Vector")] public struct Vector3 { public const Vector3 Zero = .(0f, 0f, 0f); @@ -31,7 +33,7 @@ namespace GlitchyEngine.Math Z = value; } - public this(Vector2 value, float z) + public this(Vector2 value, float z = 0.0f) { X = value.X; Y = value.Y; @@ -44,6 +46,20 @@ namespace GlitchyEngine.Math Y = y; Z = z; } + + public this(Vector3 value) + { + X = value.X; + Y = value.Y; + Z = value.Z; + } + + public this(Vector4 value) + { + X = value.X; + Y = value.Y; + Z = value.Z; + } public ref float this[int index] { @@ -87,24 +103,17 @@ namespace GlitchyEngine.Math /** * Calculates the magnitude (length) of this vector. - * @remarks MagnitudeSquared might be used if only the relative length is relevant. + * @remarks If the exact magnitude isn't needed (e.g. for comparisons) consider using MagnitudeSquared which doesn't use the square root operation. */ - public float Magnitude() - { - return Math.Sqrt(X * X + Y * Y + Z * Z); - } + public float Magnitude() => Math.Sqrt(X * X + Y * Y + Z * Z); /** * Calculates the squared magnitude (length) of this vector. + * @remarks This function avoids the square root operation to calculate the magnitude and is thus more suitable for comparisons where the exact magnitude isn't needed. */ - public float MagnitudeSquared() - { - return X * X + Y * Y + Z * Z; - } - - /** - * Normalizes this vector. - */ + public float MagnitudeSquared() => X * X + Y * Y + Z * Z; + + /// Normalizes this vector. [Checked] public void Normalize() mut { @@ -114,17 +123,13 @@ namespace GlitchyEngine.Math this /= Magnitude(); } - /** - * Normalizes this vector. - */ + /// Normalizes this vector. public void Normalize() mut { this /= Magnitude(); } - /** - * Returns a copy of this Vector with a magnitude of 1. - */ + /// Returns a copy of this Vector with a magnitude of 1. [Checked] public Vector3 Normalized() { @@ -134,24 +139,28 @@ namespace GlitchyEngine.Math return this / Magnitude(); } - /** - * Returns a copy of this Vector with a magnitude of 1. - */ + /// Returns a copy of this Vector with a magnitude of 1. public Vector3 Normalized() { return this / Magnitude(); } - + + /// Returns a copy of the given Vector with a magnitude of 1. public static Vector3 Normalize(Vector3 v) { return v / v.Magnitude(); } - public static float Dot(Vector3 l, Vector3 r) - { - return l.X * r.X + l.Y * r.Y + l.Z * r.Z; - } + /// Calculates the dot product of two vectors. + public static float Dot(Vector3 l, Vector3 r) => l.X * r.X + l.Y * r.Y + l.Z * r.Z; + /// Calculates the distance between two vectors. + public static float Distance(Vector3 a, Vector3 b) => (a - b).[Inline]Magnitude(); + + /// Calculates the squared distance between two vectors. + public static float DistanceSquared(Vector3 a, Vector3 b) => (a - b).[Inline]MagnitudeSquared(); + + /// Calculates the cross product of two vectors. public static Vector3 Cross(Vector3 l, Vector3 r) { return .(l.Y * r.Z - l.Z * r.Y, @@ -159,27 +168,21 @@ namespace GlitchyEngine.Math l.X * r.Y - l.Y * r.X); } - /** - * Calculates the projection of a onto b - */ + /// Calculates the projection of a onto b. public static Vector3 Project(Vector3 a, Vector3 b) { return (b * (Dot(a, b) / Dot(b, b))); } - - /** - * Calculates the rejection of a from b - */ + /// Calculates the rejection of a from b. public static Vector3 Reject(Vector3 a, Vector3 b) { return (a - b * (Dot(a, b) / Dot(b, b))); } - public static Vector3 Floor(Vector3 value) - { - return .(Math.Floor(value.X), Math.Floor(value.Y), Math.Floor(value.Z)); - } + public static Vector3 Floor(Vector3 value) => .(Math.Floor(value.X), Math.Floor(value.Y), Math.Floor(value.Z)); + + public static Vector3 Ceiling(Vector3 value) => .(Math.Ceiling(value.X), Math.Ceiling(value.Y), Math.Ceiling(value.Z)); /** * Interpolates linearly between two given vectors. @@ -194,14 +197,17 @@ namespace GlitchyEngine.Math return a + interpolationValue * (b - a); } - public static Vector3 Min(Vector3 a, Vector3 b) - { - return .(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y), Math.Min(a.Z, b.Z)); - } + public static Vector3 Min(Vector3 a, Vector3 b) => .(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y), Math.Min(a.Z, b.Z)); - public static Vector3 Max(Vector3 a, Vector3 b) + public static Vector3 Max(Vector3 a, Vector3 b) => .(Math.Max(a.X, b.X), Math.Max(a.Y, b.Y), Math.Min(a.Z, b.Z)); + + public static Vector3 Abs(Vector3 v) => .(Math.Abs(v.X), Math.Abs(v.Y), Math.Abs(v.Z)); + + public static Vector3 Clamp(Vector3 v, Vector3 min, Vector3 max) { - return .(Math.Max(a.X, b.X), Math.Max(a.Y, b.Y), Math.Min(a.Z, b.Z)); + return .(Math.Clamp(v.X, min.X, max.X), + Math.Clamp(v.Y, min.Y, max.Y), + Math.Clamp(v.X, min.Z, max.Z)); } // @@ -226,7 +232,6 @@ namespace GlitchyEngine.Math // Subtraction - public void operator -=(Vector3 value) mut { X -= value.X; @@ -336,5 +341,9 @@ namespace GlitchyEngine.Math [Inline] public static explicit operator Self(float value) => Self(value); + + [Inline] +#unwarn + public static explicit operator float[3](Vector3 value) => *(float[3]*)&value; } } diff --git a/GlitchyEngine/src/Math/Vector4.bf b/GlitchyEngine/src/Math/Vector4.bf index 0774578..334098d 100644 --- a/GlitchyEngine/src/Math/Vector4.bf +++ b/GlitchyEngine/src/Math/Vector4.bf @@ -1,8 +1,10 @@ +using Bon; using System; namespace GlitchyEngine.Math { - [SwizzleVector(4, "Vector")] + [BonTarget] + [SwizzleVector(4, "GlitchyEngine.Math.Vector")] public struct Vector4 { public const Vector4 Zero = .(0f, 0f, 0f, 0f); @@ -320,5 +322,9 @@ namespace GlitchyEngine.Math [Inline] public static explicit operator Self(float value) => Self(value); + + [Inline] +#unwarn + public static explicit operator float[4](Vector4 value) => *(float[4]*)&value; } } diff --git a/GlitchyEngine/src/Physics/PhysicsMaterial2D.bf b/GlitchyEngine/src/Physics/PhysicsMaterial2D.bf new file mode 100644 index 0000000..f35f518 --- /dev/null +++ b/GlitchyEngine/src/Physics/PhysicsMaterial2D.bf @@ -0,0 +1,9 @@ +namespace GlitchyEngine.Physics; + +struct PhysicsMaterial2D +{ + public float Density = 1.0f; + public float Friction = 0.5f; + public float Restitution = 0.0f; + public float RestitutionThreshold = 0.5f; +} \ No newline at end of file diff --git a/GlitchyEngine/src/Platform/DX11/ImGui/ImGui.bf b/GlitchyEngine/src/Platform/DX11/ImGui/ImGui.bf index ae4c04d..b886d84 100644 --- a/GlitchyEngine/src/Platform/DX11/ImGui/ImGui.bf +++ b/GlitchyEngine/src/Platform/DX11/ImGui/ImGui.bf @@ -12,20 +12,26 @@ namespace ImGui { static List _resourceViews = new .() ~ delete _; - public static override void Image(Texture2D texture, Vec2 size, Vec2 uv0 = Vec2.Zero, Vec2 uv1 = Vec2.Ones, Vec4 tint_col = Vec4.Ones, Vec4 border_col = Vec4.Zero) + public static override void Image(TextureViewBinding textureViewBinding, Vec2 size, Vec2 uv0 = Vec2.Zero, Vec2 uv1 = Vec2.Ones, Vec4 tint_col = Vec4.Ones, Vec4 border_col = Vec4.Zero) { - var view = texture.nativeResourceView..AddRef(); + var view = textureViewBinding._nativeShaderResourceView..AddRef(); _resourceViews.Add(view); ImGui.Image(view, size, uv0, uv1, tint_col, border_col); + + textureViewBinding.Release(); } - public static override void Image(RenderTarget2D texture, Vec2 size, Vec2 uv0 = Vec2.Zero, Vec2 uv1 = Vec2.Ones, Vec4 tint_col = Vec4.Ones, Vec4 border_col = Vec4.Zero) + public static override bool ImageButton(TextureViewBinding textureViewBinding, Vec2 size, Vec2 uv0 = Vec2.Zero, Vec2 uv1 = Vec2.Ones, int32 frame_padding = -1, Vec4 bg_col = Vec4.Zero, Vec4 tint_col = Vec4.Ones) { - var view = texture._nativeResourceView..AddRef(); + var view = textureViewBinding._nativeShaderResourceView..AddRef(); _resourceViews.Add(view); - ImGui.Image(view, size, uv0, uv1, tint_col, border_col); + bool pressed = ImGui.ImageButton(view, size, uv0, uv1, frame_padding, bg_col, tint_col); + + textureViewBinding.Release(); + + return pressed; } protected internal static override void CleanupFrame() diff --git a/GlitchyEngine/src/Platform/DX11/Math/Dx11Color.bf b/GlitchyEngine/src/Platform/DX11/Math/Dx11Color.bf new file mode 100644 index 0000000..953b82f --- /dev/null +++ b/GlitchyEngine/src/Platform/DX11/Math/Dx11Color.bf @@ -0,0 +1,29 @@ +#if GE_GRAPHICS_DX11 + +using System; + +namespace GlitchyEngine.Math +{ + extension ColorRGBA + { + [Inline] +#unwarn + public static implicit operator DirectX.ColorRGBA(ColorRGBA self) => *(DirectX.ColorRGBA*)&self; + } + + extension ColorRGB + { + [Inline] +#unwarn + public static implicit operator DirectX.ColorRGB(ColorRGB self) => *(DirectX.ColorRGB*)&self; + } + + extension Color + { + [Inline] +#unwarn + public static implicit operator DirectX.Color(Color self) => *(DirectX.Color*)&self; + } +} + +#endif diff --git a/GlitchyEngine/src/Platform/DX11/Math/Vector.bf b/GlitchyEngine/src/Platform/DX11/Math/Vector.bf index 18cd634..e9ffcd4 100644 --- a/GlitchyEngine/src/Platform/DX11/Math/Vector.bf +++ b/GlitchyEngine/src/Platform/DX11/Math/Vector.bf @@ -4,6 +4,7 @@ #pragma warning disable 4204 using System; +using Bon; namespace GlitchyEngine.Math { @@ -35,4 +36,16 @@ namespace GlitchyEngine.Math } } +namespace DirectX +{ + [BonTarget] + extension Color; + + [BonTarget] + extension ColorRGB; + + [BonTarget] + extension ColorRGBA; +} + #endif diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Buffer.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Buffer.bf index 8a9ad71..26f4914 100644 --- a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Buffer.bf +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Buffer.bf @@ -84,7 +84,7 @@ namespace GlitchyEngine.Renderer return .Ok; } - protected override Result PlatformSetData(void* data, uint32 byteLength, uint32 dstByteOffset, GlitchyEngine.Renderer.MapType mapType) + protected override Result PlatformSetData(void* data, uint32 byteLength, uint32 dstByteOffset, Renderer.MapType mapType) { Debug.Profiler.ProfileResourceFunction!(); @@ -102,7 +102,7 @@ namespace GlitchyEngine.Renderer Box dataBox = .(dstByteOffset, 0, 0, dstByteOffset + byteLength, 1, 1); NativeContext.UpdateSubresource(nativeBuffer, 0, &dataBox, data, byteLength, byteLength); case .Dynamic: - Debug.Assert(mapType.CanWrite, "The map type has to have write access."); + Log.EngineLogger.Assert(mapType == .WriteDiscard || mapType == .WriteNoOverwrite, scope $"When writing to dynamic resources the map type must be {nameof(Renderer.MapType.WriteDiscard)} or {nameof(Renderer.MapType.WriteNoOverwrite)}."); // Todo: DoNotWaitFlag MappedSubresource map = ?; NativeContext.Map(nativeBuffer, 0, (.)mapType, .None, &map); diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11BufferCollection.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11BufferCollection.bf index 9d20794..9bc478e 100644 --- a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11BufferCollection.bf +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11BufferCollection.bf @@ -14,6 +14,9 @@ namespace GlitchyEngine.Renderer { Debug.Profiler.ProfileRendererFunction!(); + // Clear + nativeBuffers = .(); + for(let buffer in _buffers) { nativeBuffers[buffer.Index] = buffer.Buffer.nativeBuffer; diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11ConstantBuffer.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11ConstantBuffer.bf index 9b2c17d..07b9fab 100644 --- a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11ConstantBuffer.bf +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11ConstantBuffer.bf @@ -37,6 +37,8 @@ namespace GlitchyEngine.Renderer _type = .Float; case .Int: _type = .Int; + case .UInt: + _type = .UInt; default: Log.EngineLogger.Assert(false, scope $"Unhandled shader variable type: {shaderTypeDescription.Type}"); } diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Effect.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Effect.bf deleted file mode 100644 index 0daa906..0000000 --- a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Effect.bf +++ /dev/null @@ -1,31 +0,0 @@ -#if GE_GRAPHICS_DX11 - -using System; -using DirectX; -using DirectX.D3D11; - -using internal GlitchyEngine.Renderer; - -namespace GlitchyEngine.Renderer -{ - extension Effect - { - protected override void Compile(String vsPath, String vsEntry, String psPath, String psEntry) - { - Debug.Profiler.ProfileResourceFunction!(); - - // Todo: macros - VertexShader = new VertexShader(vsPath, vsEntry); - PixelShader = new PixelShader(psPath, psEntry); - - Reflect(); - } - - private void Reflect() - { - - } - } -} - -#endif diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11GeometryBinding.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11GeometryBinding.bf index ceae99e..ffd373a 100644 --- a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11GeometryBinding.bf +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11GeometryBinding.bf @@ -15,7 +15,6 @@ namespace GlitchyEngine.Renderer internal uint32[DirectX.D3D11.D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT] bufferStrides; internal uint32[DirectX.D3D11.D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT] bufferOffsets; - internal ID3D11InputLayout* nativeVertexLayout; internal ID3D11Buffer* nativeIndexBuffer; public ~this() @@ -25,7 +24,6 @@ namespace GlitchyEngine.Renderer buffer?.Release(); } - nativeVertexLayout?.Release(); nativeIndexBuffer?.Release(); } @@ -61,10 +59,6 @@ namespace GlitchyEngine.Renderer protected override void PlatformSetVertexLayout(VertexLayout vertexLayout) { - Debug.Profiler.ProfileResourceFunction!(); - - nativeVertexLayout?.Release(); - nativeVertexLayout = vertexLayout?.nativeLayout..AddRef(); } protected override void PlatformSetIndexBuffer(IndexBuffer indexBuffer) @@ -88,7 +82,7 @@ namespace GlitchyEngine.Renderer Debug.Profiler.ProfileRendererFunction!(); NativeContext.InputAssembler.SetVertexBuffers(0, nativeBuffers.Count, &nativeBuffers, &bufferStrides, &bufferOffsets); - NativeContext.InputAssembler.SetInputLayout(_vertexLayout.nativeLayout); + GraphicsContext.Get().SetVertexLayout(_vertexLayout); NativeContext.InputAssembler.SetPrimitiveTopology((.)_primitiveTopology); if(_indexBuffer != null) diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11GraphicsContext.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11GraphicsContext.bf index 71ee6fc..6985b6c 100644 --- a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11GraphicsContext.bf +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11GraphicsContext.bf @@ -34,6 +34,11 @@ namespace GlitchyEngine.Renderer private const uint32 MaxRTVCount = DirectX.D3D11.D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT; + // current vertex layout and vertex shader needed for validation. + ID3D11InputLayout* _currentInputLayout ~ _?.Release(); + VertexLayout _currentVertexLayout ~ _?.ReleaseRef(); + VertexShader _currentVertexShader ~ _?.ReleaseRef(); + //public static override uint32 MaxRenderTargetCount() => MaxRTVCount; public this(Windows.HWnd windowHandle) @@ -120,6 +125,11 @@ namespace GlitchyEngine.Renderer _depthStencilTarget = target?.nativeView; } + internal void SetNativeDepthStencilTarget(ID3D11DepthStencilView* depthStencilTarget) + { + _depthStencilTarget = depthStencilTarget; + } + public override void SetRenderTarget(RenderTarget2D renderTarget, int slot, bool setDepthTarget) { _renderTargets[slot] = (renderTarget ?? _swapChain.BackBuffer)._nativeRenderTargetView; @@ -130,6 +140,22 @@ namespace GlitchyEngine.Renderer } } + internal void SetNativeRenderTargets(Span renderTargets, int startSlot) + { + for (int i < renderTargets.Length) + { + _renderTargets[i + startSlot] = renderTargets[i]; + } + } + + public override void UnbindRenderTargets() + { + for (var rt in ref _renderTargets) + { + rt = null; + } + } + public override void BindRenderTargets() { NativeContext.OutputMerger.SetRenderTargets(MaxRTVCount, &_renderTargets, _depthStencilTarget); @@ -147,16 +173,52 @@ namespace GlitchyEngine.Renderer NativeContext.InputAssembler.SetVertexBuffers(slot, 1, &buffer.nativeBuffer, &stride, &offset); } + [Inline] + private void BindInputLayout() + { + if (_currentInputLayout == null) + { + _currentInputLayout = _currentVertexLayout.GetNativeVertexLayout(_currentVertexShader.nativeCode); + _currentInputLayout.AddRef(); + + NativeContext.InputAssembler.SetInputLayout(_currentInputLayout); + } + } + + private void BindState() + { + Debug.Profiler.ProfileRendererFunction!(); + + BindInputLayout(); + + if (_hasVs) + NativeContext.VertexShader.SetConstantBuffers(0, _vsBuffers.Count, &_vsBuffers); + + if (_hasPs) + NativeContext.PixelShader.SetConstantBuffers(0, _psBuffers.Count, &_psBuffers); + } + public override void Draw(uint32 vertexCount, uint32 startVertexIndex = 0) { + BindState(); + NativeContext.Draw(vertexCount, startVertexIndex); } public override void DrawIndexed(uint32 indexCount, uint32 startIndexLocation = 0, int32 vertexOffset = 0) { + BindState(); + NativeContext.DrawIndexed(indexCount, startIndexLocation, vertexOffset); } + public override void DrawIndexedInstanced(uint32 indexCountPerInstance, uint32 instanceCount, uint32 startIndexLocation, int32 baseVertexLocation, uint32 startInstanceLocation) + { + BindState(); + + NativeContext.DrawIndexedInstanced(indexCountPerInstance, instanceCount, startIndexLocation, baseVertexLocation, startInstanceLocation); + } + public override void SetIndexBuffer(Buffer buffer, IndexFormat indexFormat = .Index16Bit, uint32 byteOffset = 0) { NativeContext.InputAssembler.SetIndexBuffer(buffer.nativeBuffer, indexFormat == .Index32Bit ? .R32_UInt : .R16_UInt, byteOffset); @@ -169,7 +231,12 @@ namespace GlitchyEngine.Renderer public override void SetVertexLayout(VertexLayout vertexLayout) { - NativeContext.InputAssembler.SetInputLayout(vertexLayout.nativeLayout); + if (_currentVertexLayout != vertexLayout) + { + SetReference!(_currentVertexLayout, vertexLayout); + _currentInputLayout?.Release(); + _currentInputLayout = null; + } } public override void SetPrimitiveTopology(GlitchyEngine.Renderer.PrimitiveTopology primitiveTopology) @@ -177,6 +244,17 @@ namespace GlitchyEngine.Renderer NativeContext.InputAssembler.SetPrimitiveTopology((DirectX.Common.PrimitiveTopology)primitiveTopology); } + private uint32 _ps_FirstTexture; + private uint32 _ps_BoundTextures; + private uint32 _vs_FirstTexture; + private uint32 _vs_BoundTextures; + + private bool _hasPs; + private bool _hasVs; + + private ID3D11Buffer*[DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT] _vsBuffers; + private ID3D11Buffer*[DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT] _psBuffers; + /** * Binds the given shader to the corresponding shader stage. * @param shader The shader that will be bound to the graphics context. @@ -191,10 +269,10 @@ namespace GlitchyEngine.Renderer ID3D11ShaderResourceView*[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] _textures = .(); ID3D11SamplerState*[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] _samplers = .(); - for(let entry in shader.Textures) + for (let entry in shader?.Textures) { - _textures[entry.Index] = entry.Texture?.nativeResourceView; - _samplers[entry.Index] = entry.Texture?.SamplerState?.nativeSamplerState; + _textures[entry.Index] = entry.BoundTexture._nativeShaderResourceView; + _samplers[entry.Index] = entry.BoundTexture._nativeSamplerState; if(entry.Index >= _textureCount) _textureCount = entry.Index + 1; @@ -202,48 +280,98 @@ namespace GlitchyEngine.Renderer _firstTexture = entry.Index; } - if(_textureCount > 0) - { - switch(typeof(TShader)) - { - // TODO: Add remaining shader stages - case typeof(PixelShader): - // TODO: bind uavs - NativeContext.PixelShader.SetShaderResources(_firstTexture, _textureCount, &_textures[_firstTexture]); - NativeContext.PixelShader.SetSamplers(_firstTexture, _textureCount, &_samplers[_firstTexture]); - case typeof(VertexShader): - NativeContext.VertexShader.SetShaderResources(_firstTexture, _textureCount, &_textures[_firstTexture]); - NativeContext.VertexShader.SetSamplers(_firstTexture, _textureCount, &_samplers[_firstTexture]); - default: - Runtime.FatalError(scope $"Shader stage \"{typeof(TShader)}\" not implemented."); - } - } - shader.Buffers.PlatformFetchNativeBuffers(); - - switch(typeof(TShader)) + + switch (typeof(TShader)) { // TODO: Add remaining shader stages case typeof(PixelShader): - NativeContext.PixelShader.SetConstantBuffers(0, shader.Buffers.nativeBuffers.Count, &shader.Buffers.nativeBuffers); - [IgnoreErrors]{ NativeContext.PixelShader.SetShader(((PixelShader)shader).nativeShader); } + _hasPs = shader != null; + + // TODO: bind uavs + if (_textureCount > 0) + { + NativeContext.PixelShader.SetShaderResources(_firstTexture, _textureCount, &_textures[_firstTexture]); + NativeContext.PixelShader.SetSamplers(_firstTexture, _textureCount, &_samplers[_firstTexture]); + } + + _ps_FirstTexture = _firstTexture; + _ps_BoundTextures = _textureCount; + + for (var buffer in shader?.Buffers) + { + _psBuffers[buffer.Index] = buffer.Buffer.nativeBuffer; + } + + //NativeContext.PixelShader.SetConstantBuffers(0, shader.Buffers.nativeBuffers.Count, &shader.Buffers.nativeBuffers); + + NativeContext.PixelShader.SetShader((ID3D11PixelShader*)shader?.nativeShader); + case typeof(VertexShader): - NativeContext.VertexShader.SetConstantBuffers(0, shader.Buffers.nativeBuffers.Count, &shader.Buffers.nativeBuffers); - [IgnoreErrors]{ NativeContext.VertexShader.SetShader(((VertexShader)shader).nativeShader); } + _hasVs = shader != null; + + if (_textureCount > 0) + { + NativeContext.VertexShader.SetShaderResources(_firstTexture, _textureCount, &_textures[_firstTexture]); + NativeContext.VertexShader.SetSamplers(_firstTexture, _textureCount, &_samplers[_firstTexture]); + } + + for (var buffer in shader?.Buffers) + { + _vsBuffers[buffer.Index] = buffer.Buffer.nativeBuffer; + } + + //NativeContext.VertexShader.SetConstantBuffers(0, shader.Buffers.nativeBuffers.Count, &shader.Buffers.nativeBuffers); + + NativeContext.VertexShader.SetShader((ID3D11VertexShader*)shader?.nativeShader); + + _vs_FirstTexture = _firstTexture; + _vs_BoundTextures = _textureCount; + + //if (VertexShader vs = shader as VertexShader) + [ConstSkip] + { + SetReference!(_currentVertexShader, shader); + _currentInputLayout?.Release(); + _currentInputLayout = null; + } default: Runtime.FatalError(scope $"Shader stage \"{typeof(TShader)}\" not implemented."); } } - public override void SetVertexShader(VertexShader vertexShader) + public override void UnbindTextures() + { + void** voidArray = scope void*[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT]*; + + NativeContext.PixelShader.SetShaderResources(0, D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT, (.)voidArray); + NativeContext.VertexShader.SetShaderResources(0, D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT, (.)voidArray); + } + + public override void BindVertexShader(VertexShader vertexShader) { BindShaderToStage(vertexShader); } - public override void SetPixelShader(PixelShader pixelShader) + public override void BindPixelShader(PixelShader pixelShader) { BindShaderToStage(pixelShader); } + + public override void BindConstantBuffer(Buffer buffer, int slot, ShaderStage stage) + { + Debug.Assert((slot >= 0) && (slot < DirectX.D3D11.D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT)); + + if (stage.HasFlag(.Vertex)) + { + _vsBuffers[slot] = buffer.nativeBuffer; + } + + if (stage.HasFlag(.Pixel)) + { + _psBuffers[slot] = buffer.nativeBuffer; + } + } } } diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11PixelShader.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11PixelShader.bf index a47e270..e85d9bc 100644 --- a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11PixelShader.bf +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11PixelShader.bf @@ -4,6 +4,7 @@ using System; using DirectX.D3D11; using DirectX.D3DCompiler; using GlitchyEngine.Platform.DX11; +using GlitchyEngine.Content; using internal GlitchyEngine.Renderer; using internal GlitchyEngine.Platform.DX11; @@ -12,18 +13,16 @@ namespace GlitchyEngine.Renderer { extension PixelShader { - internal ID3D11PixelShader* nativeShader ~ _?.Release(); - - public override void CompileFromSource(String code, String entryPoint, ShaderDefine[] macros = null) + public override void CompileFromSource(StringView code, StringView? fileName, String entryPoint, IContentManager contentManager, ShaderDefine[] macros = null) { Debug.Profiler.ProfileRendererFunction!(); - Shader.PlattformCompileShaderFromSource(code, macros, entryPoint, "ps_5_0", DefaultCompileFlags, out nativeCode); + Shader.PlattformCompileShaderFromSource(code, fileName, macros, entryPoint, "ps_5_0", DefaultCompileFlags, contentManager, out nativeCode); { Debug.Profiler.ProfileResourceScope!("CreateNativePixelShader"); - var result = NativeDevice.CreatePixelShader(nativeCode.GetBufferPointer(), nativeCode.GetBufferSize(), null, &nativeShader); + var result = NativeDevice.CreatePixelShader(nativeCode.GetBufferPointer(), nativeCode.GetBufferSize(), null, (ID3D11PixelShader**)&nativeShader); if(result.Failed) { Log.EngineLogger.Error($"Failed to create pixel shader: Message ({(int)result}): {result}"); diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11RenderTarget.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11RenderTarget.bf index 755a8a7..ad44bba 100644 --- a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11RenderTarget.bf +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11RenderTarget.bf @@ -4,6 +4,8 @@ using DirectX.Common; using DirectX.D3D11; using GlitchyEngine.Platform.DX11; using DirectX.DXGI.DXGI1_2; +using System; +using GlitchyEngine.Math; using internal GlitchyEngine.Platform.DX11; @@ -14,7 +16,6 @@ namespace GlitchyEngine.Renderer extension RenderTarget2D { protected internal ID3D11Texture2D* _nativeTexture ~ _?.Release(); - protected internal ID3D11ShaderResourceView* _nativeResourceView ~ _?.Release(); protected internal ID3D11RenderTargetView* _nativeRenderTargetView ~ _?.Release(); private void ReleaseAndNullify() @@ -119,6 +120,363 @@ namespace GlitchyEngine.Renderer Log.EngineLogger.Assert(result.Succeeded, "Failed to create render target view"); } } + + protected override TextureViewBinding PlatformGetViewBinding() + { + return .(_nativeResourceView, _samplerState.nativeSamplerState); + } + + protected override void PlatformSneakySwappyTexture(RenderTarget2D otherTexture) + { + Swap!(_description, otherTexture._description); + + // Consider sneaky swapping _depthStencilTarget too... + Swap!(_depthStenilTarget, otherTexture._depthStenilTarget); + + Swap!(_nativeTexture, otherTexture._nativeTexture); + Swap!(_nativeRenderTargetView, otherTexture._nativeRenderTargetView); + } + } + + extension RenderTargetFormat + { + public DirectX.DXGI.Format GetTextureFormat() + { + switch(this) + { + case .R8_SInt: + return .R8_SInt; + case .R32_UInt: + return .R32_UInt; + + case .R8G8B8A8_UNorm: + return .R8G8B8A8_UNorm; + case .R8G8B8A8_SNorm: + return .R8G8B8A8_SNorm; + + case .R16G16B16A16_SNorm: + return .R16G16B16A16_SNorm; + case .R16G16B16A16_Float: + return .R16G16B16A16_Float; + + case .R32G32B32A32_Float: + return .R32G32B32A32_Float; + + case .D24_UNorm_S8_UInt: + return .R24G8_Typeless; + + default: + return .Unknown; + } + } + + public DirectX.DXGI.Format GetShaderViewFormat() + { + switch(this) + { + case .D24_UNorm_S8_UInt: + return .R24_UNorm_X8_Typeless; + + default: + return GetTextureFormat(); + } + } + + public DirectX.DXGI.Format GetTargetViewFormat() + { + switch(this) + { + case .D24_UNorm_S8_UInt: + return .D24_UNorm_S8_UInt; + + default: + return GetTextureFormat(); + } + } + } + + extension RenderTargetGroup + { + protected uint32 _mipLevels; + internal ID3D11Texture2D*[] _nativeTextures; + internal ID3D11RenderTargetView*[] _renderTargetViews; + internal ID3D11ShaderResourceView*[] _nativeResourceViews; + + internal ID3D11Texture2D* _nativeDepthTexture; + internal ID3D11DepthStencilView* _nativeDepthTargetView; + internal ID3D11ShaderResourceView* _nativeDepthResourceView; + + ~this() + { + ReleaseEveryThing(); + } + + internal ID3D11Texture2D* GetNativeTexture(int index) + { + Log.EngineLogger.AssertDebug(index >= -1 && index < ColorTargetCount); + + if (index == -1) + return _nativeDepthTexture; + + return _nativeTextures[index]; + } + + private ID3D11Texture2D* PlatformCreateTexture(TargetDescription target) + { + Debug.Profiler.ProfileResourceFunction!(); + + Texture2DDescription desc = .() + { + Width = _description.Width, + Height = _description.Height, + ArraySize = _description.ArraySize, + MipLevels = _description.MipLevels, + Format = target.Format.GetTextureFormat(), + // Always bindable as ShaderResource and RenderTarget + BindFlags = .ShaderResource | (target.Format.IsDepth ? .DepthStencil : .RenderTarget), + // TODO: CpuAccessFlags = (.)_description.CpuAccess, + //CpuAccessFlags = (.)_description.CpuAccess, + // 2D RenderTarget never has misc flags + MiscFlags = .None, + SampleDesc = .(_description.Samples, 0), // TODO: SampleQuality? + // RenderTarget always has Default usage + Usage = .Default + }; + + ID3D11Texture2D* texture = null; + var result = NativeDevice.CreateTexture2D(ref desc, null, &texture); + Log.EngineLogger.Assert(result.Succeeded, "Failed to create RenderTarget2D"); + + // TODO: calculate the max mip level + // Read back the description to get the actual mip level count. + texture.GetDescription(let actualDesc); + _mipLevels = actualDesc.MipLevels; + + return texture; + } + + private (ID3D11ShaderResourceView* ResourceView, ID3D11DeviceChild* TargetOrDepthView) CreateViews(TargetDescription target, ID3D11Texture2D* texture) + { + Debug.Profiler.ProfileResourceFunction!(); + + ShaderResourceViewDescription svDesc = .(); + RenderTargetViewDescription rtDesc = .(); + DepthStencilViewDescription dsDesc = .(); + + svDesc.Format = target.Format.GetShaderViewFormat(); + rtDesc.Format = target.Format.GetTargetViewFormat(); + dsDesc.Format = target.Format.GetTargetViewFormat(); + + if (_description.ArraySize > 1) + { + if (_description.Samples > 1) + { + svDesc.ViewDimension = .Texture2DMultisampledArray; + rtDesc.ViewDimension = .Texture2DArrayMultisample; + dsDesc.ViewDimension = .Texture2DMultisampledArray; + } + else + { + svDesc.ViewDimension = .Texture2DArray; + rtDesc.ViewDimension = .Texture2DArray; + dsDesc.ViewDimension = .Texture2DArray; + } + } + else + { + if (_description.Samples > 1) + { + svDesc.ViewDimension = .Texture2DMultisampled; + rtDesc.ViewDimension = .Texture2DMultisample; + dsDesc.ViewDimension = .Texture2DMultisampled; + } + else + { + svDesc.ViewDimension = .Texture2D; + rtDesc.ViewDimension = .Texture2D; + dsDesc.ViewDimension = .Texture2D; + } + } + + svDesc.Description = .(svDesc.ViewDimension); + rtDesc.Description = .(rtDesc.ViewDimension); + dsDesc.Description = .(dsDesc.ViewDimension); + + ID3D11ShaderResourceView* resourceView = null; + ID3D11DeviceChild* targetOrDepthView = null; + + var result = NativeDevice.CreateShaderResourceView(texture, &svDesc, &resourceView); + Log.EngineLogger.Assert(result.Succeeded, "Failed to create resource view"); + + if (target.Format.IsDepth) + result = NativeDevice.CreateDepthStencilView(texture, &dsDesc, (.)&targetOrDepthView); + else + result = NativeDevice.CreateRenderTargetView(texture, &rtDesc, (.)&targetOrDepthView); + + Log.EngineLogger.Assert(result.Succeeded, "Failed to create render target view"); + + // TODO: UAVs + + return (resourceView, targetOrDepthView); + } + + mixin DeleteContainerReleaseItemsAndNullify(var container) + { + if (container != null) + { + for (var tex in container) + { + tex.Release(); + } + DeleteAndNullify!(container); + } + } + + private void ReleaseEveryThing() + { + DeleteContainerReleaseItemsAndNullify!(_nativeTextures); + DeleteContainerReleaseItemsAndNullify!(_renderTargetViews); + DeleteContainerReleaseItemsAndNullify!(_nativeResourceViews); + + ReleaseAndNullify!(_nativeDepthTexture); + ReleaseAndNullify!(_nativeDepthTargetView); + ReleaseAndNullify!(_nativeDepthResourceView); + } + + public override void ApplyChanges() + { + Debug.Profiler.ProfileResourceFunction!(); + + ReleaseEveryThing(); + + if (_colorTargetDescriptions != null) + { + _nativeTextures = new .[_colorTargetDescriptions.Count]; + _renderTargetViews = new .[_colorTargetDescriptions.Count]; + _nativeResourceViews = new .[_colorTargetDescriptions.Count]; + + for (int i < _nativeTextures.Count) + { + TargetDescription target = _colorTargetDescriptions[i]; + + if (target.IsSwapchainTarget) + { + // TODO: if the engine supports multiple windows it has to support multiple swap chains. + + // kinda dirty... + var context = GraphicsContext.Get(); + + context.SwapChain.GetBackbuffer(out _nativeTextures[i]); + } + else + { + _nativeTextures[i] = PlatformCreateTexture(target); + } + + (_nativeResourceViews[i], _renderTargetViews[i]) = (.)CreateViews(target, _nativeTextures[i]); + } + } + + if (_depthTargetDescription.Format != .None) + { + _nativeDepthTexture = PlatformCreateTexture(_depthTargetDescription); + + (_nativeDepthResourceView, _nativeDepthTargetView) = (.)CreateViews(_depthTargetDescription, _nativeDepthTexture); + } + } + + public override void Resize(uint32 width, uint32 height) + { + Debug.Profiler.ProfileResourceFunction!(); + + _description.Width = width; + _description.Height = height; + + ApplyChanges(); + } + + protected override TextureViewBinding PlatformGetViewBinding(int index) + { + if (index == -1) + { + return .(_nativeDepthResourceView, _depthSamplerState.nativeSamplerState); + } + else + { + Log.EngineLogger.AssertDebug(index < _nativeResourceViews.Count); + + return .(_nativeResourceViews[index], _colorSamplerStates[index].nativeSamplerState); + } + } + + protected override Result PlatformGetData(void* destination, uint32 elementSize, uint32 x, uint32 y, uint32 width, uint32 height, int renderTarget, uint32 arraySlice, uint32 mipLevel) // mapType? + { + Debug.Profiler.ProfileResourceFunction!(); + + ID3D11Texture2D* texture = renderTarget == -1 ? _nativeDepthTexture : _nativeTextures[renderTarget]; + + if (texture == null) + return .Err; + + // TODO: Dynamic textures with CPU read access don't need a staging texture. + + ID3D11Texture2D* stagingTexture = null; + + Texture2DDescription stagingDesc = .(); + stagingDesc.Width = width; + stagingDesc.Height = height; + stagingDesc.MipLevels = 1; + stagingDesc.ArraySize = 1; + stagingDesc.Format = _colorTargetDescriptions[renderTarget].Format.GetTextureFormat(); + stagingDesc.SampleDesc = .(_description.Samples, 0); + stagingDesc.Usage = .Staging; + stagingDesc.BindFlags = .None; + stagingDesc.CpuAccessFlags = .Read; + stagingDesc.MiscFlags = .None; + + var result = NativeDevice.CreateTexture2D(ref stagingDesc, null, &stagingTexture); + if (result != 0) + return .Err; + + defer stagingTexture.Release(); + + uint32 srcSubResource = D3D11.CalcSubresource(mipLevel, arraySlice, _mipLevels); + + Box srcBox = .(x, y, arraySlice, x + width, y + height, arraySlice + 1); + + NativeContext.CopySubresourceRegion(stagingTexture, 0, 0, 0, 0, texture, srcSubResource, &srcBox); + + MappedSubresource subresource = ?; + result = NativeContext.Map(stagingTexture, 0, .Read, .None, &subresource); + defer NativeContext.Unmap(stagingTexture, 0); + + if (result != 0) + return .Err; + + for (int i < height) + { + uint32 destRowLength = elementSize * width; + + uint32 count = Math.Min(destRowLength, subresource.RowPitch); + + Internal.MemCpy((uint8*)destination + i * destRowLength, (uint8*)subresource.Data + i * subresource.RowPitch, + count); + } + + return .Ok; + } + + public override void CopyTo(RenderTargetGroup destination, int dstTarget, Int2 dstTopLeft, Int2 size, Int2 srcTopLeft, int srcTarget) + { + ID3D11Texture2D* dstTexture = destination.GetNativeTexture(dstTarget); + ID3D11Texture2D* srcTexture = GetNativeTexture(srcTarget); + + // TODO: Mips/Arrays + + Box srcBox = .((.)srcTopLeft.X, (.)srcTopLeft.Y, 0, (.)(srcTopLeft.X + size.X), (.)(srcTopLeft.Y + size.Y), 1); + + NativeContext.CopySubresourceRegion(dstTexture, 0, (.)dstTopLeft.X, (.)dstTopLeft.Y, 0, srcTexture, 0, &srcBox); + } } } diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11RendererAPI.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11RendererAPI.bf index 893d526..76f2c39 100644 --- a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11RendererAPI.bf +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11RendererAPI.bf @@ -2,6 +2,7 @@ using GlitchyEngine.Math; using GlitchyEngine.Platform.DX11; +using System; using internal GlitchyEngine.Renderer; using internal GlitchyEngine.Platform.DX11; @@ -14,6 +15,9 @@ namespace GlitchyEngine.Renderer { private GraphicsContext _context ~ _?.ReleaseRef(); + private Effect _clearUintFx ~ _?.ReleaseRef(); + private BlendState _nonblendingState ~ _?.ReleaseRef(); + public GraphicsContext Context { get => _context; @@ -25,6 +29,11 @@ namespace GlitchyEngine.Renderer public override void Init() { Debug.Profiler.ProfileFunction!(); + + _clearUintFx = new Effect("content/Shaders/ClearUInt.hlsl"); + BlendStateDescription desc =.Default; + desc.RenderTarget[0].BlendEnable = false; + _nonblendingState = new BlendState(desc); } private mixin RtOrBackbuffer(RenderTarget2D renderTarget) @@ -61,13 +70,107 @@ namespace GlitchyEngine.Renderer NativeContext.ClearDepthStencilView(target.nativeView, flags, depth, stencil); } + private void ClearRtv(DirectX.D3D11.ID3D11RenderTargetView* rtv, ClearColor clearColor) + { + switch(clearColor) + { + case .Color(let color): + NativeContext.ClearRenderTargetView(rtv, color); + case .UInt(let value): +#unwarn + NativeContext.OutputMerger.SetRenderTargets(1, &rtv, null); + + using (BlendState lastBlendState = _currentBlendState..AddRef()) + { + SetBlendState(_nonblendingState); + + _clearUintFx.Variables["ClearValue"].SetData(value); + _clearUintFx.ApplyChanges(); + _clearUintFx.Bind(); + + FullscreenQuad.Draw(); + + SetBlendState(lastBlendState); + } + + // Rebind the old render targets + BindRenderTargets(); + default: + Runtime.NotImplemented(); + } + } + + public override void Clear(RenderTargetGroup renderTarget, ClearOptions options, ClearColor? color = null, float? depth = null, uint8? stencil = null) + { + if (options.HasFlag(.Color) && renderTarget._renderTargetViews != null) + { + for (int i < renderTarget._renderTargetViews.Count) + { + ClearRtv(renderTarget._renderTargetViews[i], color ?? renderTarget._colorTargetDescriptions[i].ClearColor); + } + } + + if (renderTarget._nativeDepthTargetView != null) + { + DirectX.D3D11.ClearFlag flags = default; + + if(options.HasFlag(.Depth)) + { + flags |= .Depth; + } + + if(options.HasFlag(.Stencil)) + { + flags |= .Stencil; + } + + if (flags != default) + { + float clearDepth = 0.0f; + uint8 clearStencil = 0; + + if (renderTarget._depthTargetDescription.ClearColor case .DepthStencil(let d, let s)) + { + clearDepth = d; + clearStencil = s; + } + else + { + Log.EngineLogger.Error("Clear color of depth stencil target must be of type DepthStencil."); + Log.EngineLogger.AssertDebug(false); + } + + clearDepth = depth ?? clearDepth; + clearStencil = stencil ?? clearStencil; + + NativeContext.ClearDepthStencilView(renderTarget._nativeDepthTargetView, flags, clearDepth, clearStencil); + } + } + } + public override void SetRenderTarget(RenderTarget2D renderTarget, int slot, bool setDepthBuffer) { Debug.Profiler.ProfileRendererFunction!(); _context.SetRenderTarget(renderTarget, slot, setDepthBuffer); } - + + public override void SetRenderTargetGroup(RenderTargetGroup renderTarget, bool setDepthBuffer) + { + if (renderTarget._renderTargetViews != null) + { + for (int i < renderTarget._renderTargetViews.Count) + { + _context.SetNativeRenderTargets(renderTarget._renderTargetViews, 0); + } + } + + if (setDepthBuffer) + { + _context.SetNativeDepthStencilTarget(renderTarget._nativeDepthTargetView); + } + } + public override void SetDepthStencilTarget(DepthStencilTarget target) { Debug.Profiler.ProfileRendererFunction!(); @@ -75,6 +178,13 @@ namespace GlitchyEngine.Renderer _context.SetDepthStencilTarget(target); } + public override void UnbindRenderTargets() + { + Debug.Profiler.ProfileRendererFunction!(); + + _context.UnbindRenderTargets(); + } + public override void BindRenderTargets() { Debug.Profiler.ProfileRendererFunction!(); @@ -126,7 +236,7 @@ namespace GlitchyEngine.Renderer { Debug.Profiler.ProfileRendererFunction!(); - NativeContext.DrawIndexedInstanced(geometry.IndexCount, geometry.InstanceCount, geometry.IndexByteOffset, 0, 0); + _context.DrawIndexedInstanced(geometry.IndexCount, geometry.InstanceCount, geometry.IndexByteOffset, 0, 0); } public override void SetViewport(Viewport viewport) @@ -135,6 +245,26 @@ namespace GlitchyEngine.Renderer _context.SetViewport(viewport); } + + public override void UnbindTextures() + { + _context.UnbindTextures(); + } + + public override void BindConstantBuffer(Buffer buffer, int slot, ShaderStage stage) + { + _context.BindConstantBuffer(buffer, slot, stage); + } + + public override void BindVertexShader(VertexShader vertexShader) + { + _context.BindVertexShader(vertexShader); + } + + public override void BindPixelShader(PixelShader pixelShader) + { + _context.BindPixelShader(pixelShader); + } } } diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11SamplerState.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11SamplerState.bf index 7aa5918..f29bd17 100644 --- a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11SamplerState.bf +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11SamplerState.bf @@ -40,8 +40,8 @@ namespace DirectX.D3D11 { output = .Anisotropic; } - - // Min filter + + // Mag filter if(samplerDesc.MagFilter == .Linear) { output |= .Min_Point_Mag_Linear_Mip_Point; @@ -51,12 +51,12 @@ namespace DirectX.D3D11 output = .Anisotropic; } - // Mag filter + // Min filter if(samplerDesc.MinFilter == .Linear) { output |= .Min_Linear_Mag_Mip_Point; } - else if(samplerDesc.MagFilter == .Anisotropic) + else if(samplerDesc.MinFilter == .Anisotropic) { output = .Anisotropic; } diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Shader.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Shader.bf index 684479f..44d1f5e 100644 --- a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Shader.bf +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Shader.bf @@ -6,13 +6,110 @@ using DirectX.Common; using DirectX.D3D11; using DirectX.D3DCompiler; using DirectX.D3D11Shader; +using System.IO; +using GlitchyEngine.Content; +using System.Collections; using internal GlitchyEngine.Renderer; namespace GlitchyEngine.Renderer { + struct ContentManagerInclude : ID3DInclude, IDisposable + { + private VTable _vTable; + + private IContentManager _contentManager; + + private Dictionary _loadedFiles; + + private String _parentFileDirectory; + + public this(IContentManager contentManager, String parentFileDirectory) + { + _contentManager = contentManager; + _parentFileDirectory = parentFileDirectory; + _loadedFiles = new Dictionary(); + + _vTable.Open = => Open; + _vTable.Close = => Close; + + _vt = &_vTable; + } + + public void Dispose() + { + delete _loadedFiles; + } + + public static HResult Open(ID3DInclude* self, IncludeType includeType, char8* fileName, void* parentData, void** data, uint32* bytes) + { + ContentManagerInclude* includer = (.)self; + + if (includer._loadedFiles.TryGetValue(fileName, let value)) + { + *data = value.Data; + *bytes = value.Length; + + return .S_OK; + } + + String pathNextToParent = scope .(); + + Path.Combine(pathNextToParent, includer._parentFileDirectory, StringView(fileName)); + + Stream fileStream = Application.Get().ContentManager.GetStream(pathNextToParent); + + if (fileStream == null) + { + fileStream = Application.Get().ContentManager.GetStream(StringView(fileName)); + } + + if (fileStream == null) + { + Log.EngineLogger.Error($"Failed to include file \"{fileName}\""); + return .E_FILENOTFOUND; + } + + String fileContent = new String(); + + { + StreamReader reader = scope .(fileStream); + + reader.ReadToEnd(fileContent); + + includer._loadedFiles.Add(fileName, (fileContent, fileContent.Ptr, (uint32)fileContent.Length)); + } + + delete fileStream; + + *data = (void*)fileContent.Ptr; + *bytes = (uint32)fileContent.Length; + + return .S_OK; + } + + public static HResult Close(ID3DInclude* self, void** data) + { + ContentManagerInclude* includer = (.)self; + + for (var v in includer._loadedFiles) + { + if (v.value.Data == data) + { + delete v.value.FileContent; + + includer._loadedFiles.Remove(v.key); + } + } + + return .S_OK; + } + } + extension Shader { + internal ID3D11DeviceChild* nativeShader ~ _?.Release(); + /** * Internal compiled code of the shader. */ @@ -25,7 +122,7 @@ namespace GlitchyEngine.Renderer .OptimizationLevel3; #endif - internal static void PlattformCompileShaderFromSource(String code, ShaderDefine[] macros, String entryPoint, String target, ShaderCompileFlags compileFlags, out ID3DBlob* shaderBlob) + internal static void PlattformCompileShaderFromSource(StringView code, StringView? fileName, ShaderDefine[] macros, String entryPoint, String target, ShaderCompileFlags compileFlags, IContentManager contentManager, out ID3DBlob* shaderBlob) { Debug.Profiler.ProfileResourceFunction!(); @@ -42,13 +139,25 @@ namespace GlitchyEngine.Renderer ID3DBlob* errorBlob = null; - shaderBlob = null; - var result = D3DCompiler.D3DCompile(code.CStr(), (.)code.Length, null, nativeMacros, null, entryPoint, target, compileFlags, .None, &shaderBlob, &errorBlob); - if(result.Failed) + String directory = scope .(); + + Path.GetDirectoryPath(fileName.Value, directory); + + using (ContentManagerInclude includer = .(contentManager, directory)) { - StringView str = StringView((char8*)errorBlob.GetBufferPointer(), (int)errorBlob.GetBufferSize()); - Log.EngineLogger.Error($"Failed to compile Shader: Error Code({(int)result}): {result} | Error Message: {str}"); + //ID3DInclude.StandardInclude + + shaderBlob = null; + var result = D3DCompiler.D3DCompile(code.Ptr, (.)code.Length, fileName?.ToScopeCStr!(), nativeMacros, &includer, entryPoint, target, compileFlags, .None, &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}"); + } } + + Log.EngineLogger.Assert(shaderBlob != null, "Shader compilation failed."); } protected internal void Reflect(ID3DBlob* shaderCode) @@ -96,7 +205,7 @@ namespace GlitchyEngine.Renderer buffer.ReleaseRef(); } case .Texture: - _textures.Add(scope String(bindDesc.Name), bindDesc.BindPoint, null); + _textures.Add(scope String(bindDesc.Name), bindDesc.BindPoint, TextureViewBinding(null, null)); case .Sampler: // TODO: do we have to do something for samplers? default: diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Texture.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Texture.bf index fcab3fa..cfd3b61 100644 --- a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Texture.bf +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Texture.bf @@ -18,7 +18,7 @@ namespace GlitchyEngine.Renderer extension Texture { - protected internal ID3D11ShaderResourceView* nativeResourceView ~ _?.Release(); + protected internal ID3D11ShaderResourceView* _nativeResourceView ~ _?.Release(); /** \brief Loads the texture from the specified path. * @param path The path of the texture to load. @@ -39,17 +39,17 @@ namespace GlitchyEngine.Renderer } ((ID3D11Resource*)texture)?.Release(); - nativeResourceView?.Release(); + _nativeResourceView?.Release(); HResult loadResult = DDSTextureLoader.CreateDDSTextureFromMemory(NativeDevice, - ddsData.Ptr, (uint)ddsData.Count, (.)&texture, &nativeResourceView); + ddsData.Ptr, (uint)ddsData.Count, (.)&texture, &_nativeResourceView); if(loadResult.Failed) { Log.EngineLogger.Error($"Failed to load texture. Error({(int)loadResult}): {loadResult}"); ReleaseAndNullify!(texture); - ReleaseAndNullify!(nativeResourceView); + ReleaseAndNullify!(_nativeResourceView); return false; } @@ -101,7 +101,7 @@ namespace GlitchyEngine.Renderer LoadDdsResourcePlatform(stream, ref nativeTexture); let resType = nativeTexture.GetResourceType(); - Log.EngineLogger.Assert(resType == .Texture2D, scope $"The texture \"{_path}\" is not a 2D texture (it is {resType})."); + Log.EngineLogger.Assert(resType == .Texture2D, scope $"The texture is not a 2D texture (it is {resType})."); nativeTexture.GetDescription(out nativeDesc); } @@ -126,7 +126,7 @@ namespace GlitchyEngine.Renderer var result = NativeDevice.CreateTexture2D(ref nativeDesc, resDataPtr, &nativeTexture); Log.EngineLogger.Assert(result.Succeeded, scope $"Failed to create texture 2D. Error ({result.Underlying}): {result}"); - result = NativeDevice.CreateShaderResourceView(nativeTexture, null, &nativeResourceView); + result = NativeDevice.CreateShaderResourceView(nativeTexture, null, &_nativeResourceView); Log.EngineLogger.Assert(result.Succeeded, scope $"Failed to create texture view. Error ({result.Underlying}): {result}"); } @@ -136,8 +136,8 @@ namespace GlitchyEngine.Renderer nativeTexture?.Release(); nativeTexture = null; - nativeResourceView?.Release(); - nativeResourceView = null; + _nativeResourceView?.Release(); + _nativeResourceView = null; nativeDesc = (NativeTex2DDesc)desc; @@ -146,7 +146,7 @@ namespace GlitchyEngine.Renderer } // TODO: Update Texture Arrays! - protected override System.Result PlatformSetData(void* data, uint32 elementSize, uint32 destX, + protected override Result PlatformSetData(void* data, uint32 elementSize, uint32 destX, uint32 destY, uint32 destWidth, uint32 destHeight, uint32 arraySlice, uint32 mipLevel, GlitchyEngine.Renderer.MapType mapType) { Debug.Profiler.ProfileResourceFunction!(); @@ -256,6 +256,11 @@ namespace GlitchyEngine.Renderer nativeTexture, D3D11.CalcSubresource(mipSlice, arraySlice, MipLevels), (.)&sourceBox); } } + + protected override TextureViewBinding PlatformGetViewBinding() + { + return .(_nativeResourceView, _samplerState?.nativeSamplerState); + } } extension TextureCube @@ -282,6 +287,11 @@ namespace GlitchyEngine.Renderer Log.EngineLogger.Assert(nativeDesc.MiscFlags.HasFlag(.TextureCube), scope $"The texture \"{_path}\" is not a texture cube."); // TODO: load fallback texture } + + protected override TextureViewBinding PlatformGetViewBinding() + { + return .(_nativeResourceView, _samplerState.nativeSamplerState); + } } } diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11TextureViewBinding.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11TextureViewBinding.bf new file mode 100644 index 0000000..409783e --- /dev/null +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11TextureViewBinding.bf @@ -0,0 +1,33 @@ +using DirectX.D3D11; + +namespace GlitchyEngine.Renderer +{ + extension TextureViewBinding + { + internal ID3D11ShaderResourceView* _nativeShaderResourceView; + internal ID3D11SamplerState* _nativeSamplerState; + + public override bool IsEmpty => _nativeShaderResourceView != null; + + internal this(ID3D11ShaderResourceView* shaderResourceView, ID3D11SamplerState* samplerState) + { + _nativeShaderResourceView = shaderResourceView; + _nativeShaderResourceView?.AddRef(); + + _nativeSamplerState = samplerState; + _nativeSamplerState?.AddRef(); + } + + public override void AddRef() + { + _nativeShaderResourceView?.AddRef(); + _nativeSamplerState?.AddRef(); + } + + public override void Release() + { + _nativeShaderResourceView?.Release(); + _nativeSamplerState?.Release(); + } + } +} diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11VertexLayout.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11VertexLayout.bf index d439a91..3f717fc 100644 --- a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11VertexLayout.bf +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11VertexLayout.bf @@ -5,6 +5,7 @@ using System.Diagnostics; using DirectX.Common; using DirectX.D3D11; using GlitchyEngine.Platform.DX11; +using System.Collections; using internal GlitchyEngine.Renderer; using internal GlitchyEngine.Platform.DX11; @@ -13,19 +14,19 @@ namespace GlitchyEngine.Renderer { public extension VertexLayout { - internal ID3D11InputLayout* nativeLayout ~ _?.Release(); + private Dictionary _validatedShaders = new .() ~ + { + if (_ != null) + { + for (let entry in _) + { + entry.key.Release(); + entry.value.Release(); + } - public ID3DBlob* nativeShaderCode ~ _?.Release(); - - public this(VertexElement[] elements, bool ownsElements, VertexShader vertexShader) - { - nativeShaderCode = vertexShader.nativeCode..AddRef(); - - _elements = elements; - _ownsElements = ownsElements; - - CreateNativeLayout(); - } + delete _; + } + }; private void ToNativeLayout(VertexElement[] input, InputElementDescription[] output) { @@ -35,19 +36,30 @@ namespace GlitchyEngine.Renderer output[i] = .(input[i].SemanticName, input[i].SemanticIndex, input[i].Format, input[i].InputSlot, input[i].AlignedByteOffset, (.)input[i].InputSlotClass, input[i].InstanceDataStepRate); } - protected override void CreateNativeLayout() + /// Validates or gets the validated input layout for the given vertexshader. + internal ID3D11InputLayout* GetNativeVertexLayout(ID3DBlob* vertexShaderCode) { Debug.Profiler.ProfileResourceFunction!(); - var nativeElements = scope InputElementDescription[_elements.Count]; + ID3D11InputLayout* layout = null; - ToNativeLayout(_elements, nativeElements); - - var result = NativeDevice.CreateInputLayout(nativeElements.CArray(), (.)nativeElements.Count, nativeShaderCode.GetBufferPointer(), nativeShaderCode.GetBufferSize(), &nativeLayout); - if(result.Failed) + if (!_validatedShaders.TryGetValue(vertexShaderCode, out layout)) { - Log.EngineLogger.Error($"Failed to create D3D11 input layout: Message({(int)result}): {result}"); + var nativeElements = scope InputElementDescription[_elements.Count]; + + ToNativeLayout(_elements, nativeElements); + + var result = NativeDevice.CreateInputLayout(nativeElements.CArray(), (.)nativeElements.Count, vertexShaderCode.GetBufferPointer(), vertexShaderCode.GetBufferSize(), &layout); + if(result.Failed) + { + Log.EngineLogger.Error($"Failed to create D3D11 input layout: Message({(int)result}): {result}"); + Debug.FatalError(); + } + + _validatedShaders[vertexShaderCode..AddRef()] = layout; } + + return layout; } } } diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11VertexShader.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11VertexShader.bf index 88f9510..49e48e9 100644 --- a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11VertexShader.bf +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11VertexShader.bf @@ -5,6 +5,7 @@ using GlitchyEngine.Renderer; using DirectX.D3D11; using DirectX.D3DCompiler; using GlitchyEngine.Platform.DX11; +using GlitchyEngine.Content; using internal GlitchyEngine.Renderer; using internal GlitchyEngine.Platform.DX11; @@ -13,18 +14,16 @@ namespace GlitchyEngine.Renderer { extension VertexShader { - internal ID3D11VertexShader* nativeShader ~ _?.Release(); - - public override void CompileFromSource(String code, String entryPoint, ShaderDefine[] macros = null) + public override void CompileFromSource(StringView code, StringView? fileName, String entryPoint, IContentManager contentManager = null, ShaderDefine[] macros = null) { Debug.Profiler.ProfileResourceFunction!(); - Shader.PlattformCompileShaderFromSource(code, macros, entryPoint, "vs_5_0", DefaultCompileFlags, out nativeCode); + Shader.PlattformCompileShaderFromSource(code, fileName, macros, entryPoint, "vs_5_0", DefaultCompileFlags, contentManager, out nativeCode); { Debug.Profiler.ProfileResourceScope!("CreateNativeVertexShader"); - var result = NativeDevice.CreateVertexShader(nativeCode.GetBufferPointer(), nativeCode.GetBufferSize(), null, &nativeShader); + var result = NativeDevice.CreateVertexShader(nativeCode.GetBufferPointer(), nativeCode.GetBufferSize(), null, (ID3D11VertexShader**)&nativeShader); if(result.Failed) { Log.EngineLogger.Error($"Failed to create vertex shader: Message ({(int)result}): {result}"); diff --git a/GlitchyEngine/src/Platform/Windows/System/IO/Path.bf b/GlitchyEngine/src/Platform/Windows/System/IO/Path.bf new file mode 100644 index 0000000..22bbb98 --- /dev/null +++ b/GlitchyEngine/src/Platform/Windows/System/IO/Path.bf @@ -0,0 +1,90 @@ +#if BF_PLATFORM_WINDOWS + +using DirectX.Common; +using DirectX.Windows; +using System; +using System.Diagnostics; +using DirectX.Windows.Winuser; + +namespace DirectX.Windows.Winuser +{ + enum OpenAsInfoFlags : uint32 + { + /// Enable the "always use this program" checkbox. If not passed, it will be disabled. + AllowRegistration = 0x1, + /// Do the registration after the user hits the OK button. + RegisterExtension = 0x2, + /// Execute file after registering. + Exec = 0x4, + ///Force the Always use this program checkbox to be checked. + /// Typically, you won't use the OAIF_ALLOW_REGISTRATION flag when you pass this value. + ForceRegistration = 0x8, + /// Introduced in Windows Vista. Hide the Always use this program checkbox. If this flag is specified, the OAIF_ALLOW_REGISTRATION and OAIF_FORCE_REGISTRATION flags will be ignored. + HideRegistration = 0x20, + /// Introduced in Windows Vista. The value for the extension that is passed is actually a protocol, so the Open With dialog box should show applications that are registered as capable of handling that protocol. + UrlProtocol = 0x40, + /// Introduced in Windows 8. The location pointed to by the pcszFile parameter is given as a URI. + FileIsUri = 0x80 + } + + struct OpenAsInfo + { + public LPCWSTR File; + public LPCWSTR Class; + public OpenAsInfoFlags Flags; + } + + static + { + [Import("user32.lib"), CallingConvention(.Stdcall), CLink] + public extern static HResult SHOpenWithDialog(HWND hwndParent, OpenAsInfo* poainfo); + } +} + +namespace System.IO; + +extension Path +{ + /// Opens the file browser and selects the specified file. + /// @param path The path of the file to select. + public static override Result OpenFolderAndSelectItem(String path) + { + String fullPath = GetScopedFullPath!(path); + + ProcessStartInfo processInfo = scope .(); + processInfo.SetFileNameAndArguments(scope $"explorer /select,\"{fullPath}\""); + + return scope SpawnedProcess().Start(processInfo); + } + + /// Opens the file browser in the given directory. + /// @param directory The directory to show in the file browser. + public static override Result OpenFolder(String directory) + { + String fullPath = GetScopedFullPath!(directory); + + ProcessStartInfo processInfo = scope .(); + processInfo.SetFileNameAndArguments(scope $"explorer \"{fullPath}\""); + + return scope SpawnedProcess().Start(processInfo); + } + + public static override Result OpenWithDialog(String filePath) + { + String fullPath = GetScopedFullPath!(filePath); + + OpenAsInfo info = .(); + info.File = fullPath.ToScopedNativeWChar!(); + info.Class = null; + info.Flags = .Exec; + + HResult result = SHOpenWithDialog(0, &info); + + if (result.Succeeded) + return .Ok; + else + return .Err; + } +} + +#endif diff --git a/GlitchyEngine/src/Platform/Windows/WindowsInput.bf b/GlitchyEngine/src/Platform/Windows/WindowsInput.bf index 934b6d3..c93556d 100644 --- a/GlitchyEngine/src/Platform/Windows/WindowsInput.bf +++ b/GlitchyEngine/src/Platform/Windows/WindowsInput.bf @@ -1,10 +1,14 @@ #if BF_PLATFORM_WINDOWS -using System; -using GlitchyEngine.Events; -using DirectX.Windows.VirtualKeyCodes; using DirectX.Windows; +using DirectX.Windows.Winuser; +using DirectX.Windows.Winuser.RawInput; +using DirectX.Windows.VirtualKeyCodes; +using System; +using System.Interop; +using GlitchyEngine.Events; using GlitchyEngine.Math; + using static System.Windows; namespace GlitchyEngine @@ -12,12 +16,43 @@ namespace GlitchyEngine /// Windows (WinApi) specific implementation of the Input-class extension Input { + //[CLink, CallingConvention(.Stdcall)] + //static extern int16 GetKeyState(int32 keycode); + [CLink, CallingConvention(.Stdcall)] + static extern IntBool GetCursorPos(out Int2 p); + [CLink, CallingConvention(.Stdcall)] + static extern IntBool SetCursorPos(c_int x, c_int y); + [CLink, CallingConvention(.Stdcall)] + static extern IntBool ScreenToClient(HWnd hWnd, ref Int2 p); + [CLink, CallingConvention(.Stdcall)] + static extern IntBool ClientToScreen(HWnd hWnd, ref Int2 p); + + [Import("user32.lib"), CLink] + public static extern IntBool RegisterRawInputDevices(RAWINPUTDEVICE* pRawInputDevices, uint32 uiNumDevices, uint32 cbSize); + + public static void RegisterRIDs() + { + RAWINPUTDEVICE rid; + + rid.UsagePage = 0x01; + rid.Usage = 0x02; + rid.Flags = 0; + rid.Target = 0; + + if(RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)) == 0) + { + Log.EngineLogger.Error("Failed to register raw input devices: {0}", GetLastError()); + Log.EngineLogger.AssertDebug(false, "Failed to register raw input device."); + } + } + /// Represents the state of the input devices on the windows platform (WinApi that is). struct WindowsInputState { public int8[256] KeyStates; - public Point CursorPosition; - public Point CursorPositionDifference; + public Int2 CursorPosition; + public Int2 CursorPositionDifference; + public Int2 RawCursorMovement; } static WindowsInputState[2] IputStates; @@ -118,7 +153,15 @@ namespace GlitchyEngine return state >= 0; } - public override static Point GetMousePosition() => CurrentState.CursorPosition; + public override static bool IsMouseButtonPressing(MouseButton button) => IsMouseButtonPressed(button) && WasMouseButtonReleased(button); + + public override static bool IsMouseButtonReleasing(MouseButton button) => IsMouseButtonReleased(button) && WasMouseButtonPressed(button); + + public override static Int2 GetMousePosition() => CurrentState.CursorPosition; + + public override static Int2 GetMouseMovement() => CurrentState.CursorPositionDifference; + + public override static Int2 GetRawMouseMovement() => CurrentState.RawCursorMovement; public override static int32 GetMouseX() => CurrentState.CursorPosition.X; @@ -139,30 +182,39 @@ namespace GlitchyEngine return state >= 0; } - public override static Point GetLastMousePosition() => LastState.CursorPosition; + public override static Int2 GetLastMousePosition() => LastState.CursorPosition; + + public override static Int2 GetLastMouseMovement() => LastState.CursorPositionDifference; + + public override static Int2 GetLastRawMouseMovement() => LastState.RawCursorMovement; public override static int32 GetLastMouseX() => LastState.CursorPosition.X; public override static int32 GetLastMouseY() => LastState.CursorPosition.Y; - // - // Mouse state transition - // - public override static bool IsMouseButtonPressing(MouseButton button) => IsMouseButtonPressed(button) && WasMouseButtonReleased(button); - - public override static bool IsMouseButtonReleasing(MouseButton button) => IsMouseButtonReleased(button) && WasMouseButtonPressed(button); - - public override static Point GetMouseMovement() => CurrentState.CursorPositionDifference; - - //[CLink, CallingConvention(.Stdcall)] - //static extern int16 GetKeyState(int32 keycode); - [CLink, CallingConvention(.Stdcall)] - static extern IntBool GetCursorPos(out Point p); - [CLink, CallingConvention(.Stdcall)] - static extern IntBool ScreenToClient(HWnd hWnd, ref Point p); - - public override static void NewFrame() + public override static void SetMousePosition(Int2 pos) { + HWnd windowHandle = (HWnd)(int)Application.Get().Window.NativeWindow; + + var pos; + + // TODO: can fail + ClientToScreen(windowHandle, ref pos); + + SetCursorPos(pos.X, pos.Y); + } + + public override static void Init() + { + RegisterRIDs(); + } + + //public override + + public override static void Impl_NewFrame() + { + Window window = Application.Get().Window; + Debug.Profiler.ProfileFunction!(); Swap!(CurrentState, LastState); @@ -177,7 +229,7 @@ namespace GlitchyEngine // Get the current mouse position if (GetCursorPos(out CurrentState.CursorPosition) != 0) { - HWnd windowHandle = (HWnd)(int)Application.Get().Window.NativeWindow; + HWnd windowHandle = (HWnd)(int)window.NativeWindow; if (ScreenToClient(windowHandle, ref CurrentState.CursorPosition) == 0) { DirectX.Common.HResult errorCode = (.)GetLastError(); @@ -190,8 +242,25 @@ namespace GlitchyEngine Log.EngineLogger.Error($"Failed to get mouse position. Message({(int32)errorCode}){errorCode}:"); } - // Calculate cursor movement - CurrentState.CursorPositionDifference = CurrentState.CursorPosition - LastState.CursorPosition; + CurrentState.RawCursorMovement = window.[Friend]_rawMouseMovementAccumulator; + + if (Mouse.LockedPosition == null) + { + // Calculate cursor movement + CurrentState.CursorPositionDifference = CurrentState.CursorPosition - LastState.CursorPosition; + } + else + { + // Calculate cursor movement + CurrentState.CursorPositionDifference = CurrentState.CursorPosition - Mouse.LockedPosition.Value; + CurrentState.CursorPosition = Mouse.LockedPosition.Value; + + } + } + + public override static void Impl_EndFrame() + { + Application.Get().Window.[Friend]_rawMouseMovementAccumulator = .Zero; } } } diff --git a/GlitchyEngine/src/Platform/Windows/WindowsWindow.bf b/GlitchyEngine/src/Platform/Windows/WindowsWindow.bf index ebf139f..106de61 100644 --- a/GlitchyEngine/src/Platform/Windows/WindowsWindow.bf +++ b/GlitchyEngine/src/Platform/Windows/WindowsWindow.bf @@ -10,6 +10,7 @@ using GlitchyEngine.Events; using System.Diagnostics; using GlitchyEngine.Math; using GlitchyEngine.Renderer; +using DirectX.Windows.Winuser.RawInput; using static System.Windows; using internal GlitchyEngine; @@ -66,13 +67,13 @@ namespace GlitchyEngine // // Size // - public override Point Size + public override Int2 Size { - get => *(Point*)&_clientRect.Width; + get => *(Int2*)&_clientRect.Width; set { - *(Point*)&_clientRect.Width = value; + *(Int2*)&_clientRect.Width = value; ApplyRectangle(); } } @@ -102,13 +103,13 @@ namespace GlitchyEngine // // Position // - public override Point Position + public override Int2 Position { - get => *(Point*)&_clientRect; + get => *(Int2*)&_clientRect; set { - *(Point*)&_clientRect = value; + *(Int2*)&_clientRect = value; ApplyRectangle(); } } @@ -262,6 +263,8 @@ namespace GlitchyEngine return .Ok; } + internal Int2 _rawMouseMovementAccumulator; + private static LRESULT MessageHandler(HWND hwnd, uint32 uMsg, WPARAM wParam, LPARAM lParam) { void* windowPtr = (void*)GetWindowLongPtrW(hwnd, GWL_USERDATA); @@ -458,6 +461,28 @@ namespace GlitchyEngine var event = scope MouseMovedEvent(x, y); window._eventCallback(event); } + case WM_INPUT: + { + uint32 dataSize = ?; + GetRawInputData((.)lParam, RID_INPUT, null, &dataSize, sizeof(RAWINPUTHEADER)); + + if (dataSize > 0) + { + uint8[] rawData = scope .[dataSize]; + if (GetRawInputData((.)lParam, RID_INPUT, rawData.CArray(), &dataSize, sizeof(RAWINPUTHEADER)) == dataSize) + { + RAWINPUT* raw = (.)rawData.CArray(); + if (raw.Header.Type == RIM_TYPEMOUSE) + { + var event = scope MouseMovedEvent(raw.Data.Mouse.lLastX, raw.Data.Mouse.lLastY); + window._eventCallback(event); + + // We accumulate the raw movements an collect them once each frame in WindowsInput.Impl_NewFrame() + window._rawMouseMovementAccumulator += Int2(raw.Data.Mouse.lLastX, raw.Data.Mouse.lLastY); + } + } + } + } // Todo: DirectInput diff --git a/GlitchyEngine/src/Renderer/BufferCollection.bf b/GlitchyEngine/src/Renderer/BufferCollection.bf index 1574b76..d3b3f17 100644 --- a/GlitchyEngine/src/Renderer/BufferCollection.bf +++ b/GlitchyEngine/src/Renderer/BufferCollection.bf @@ -1,9 +1,10 @@ using System; using System.Collections; +using GlitchyEngine.Core; namespace GlitchyEngine.Renderer { - public class BufferCollection : IEnumerable<(String Name, int Index, Buffer Buffer)> + public class BufferCollection : RefCounter, IEnumerable<(String Name, int Index, Buffer Buffer)> { public typealias BufferEntry = (String Name, int Index, Buffer Buffer); @@ -124,10 +125,7 @@ namespace GlitchyEngine.Renderer { Log.EngineLogger.AssertDebug(name == bufferEntry.Name); - bufferEntry.Buffer.ReleaseRef(); - - buffer.AddRef(); - bufferEntry.Buffer = buffer; + SetReference!(bufferEntry.Buffer, buffer); return true; } diff --git a/GlitchyEngine/src/Renderer/BufferVariable.bf b/GlitchyEngine/src/Renderer/BufferVariable.bf index e453dfc..7121340 100644 --- a/GlitchyEngine/src/Renderer/BufferVariable.bf +++ b/GlitchyEngine/src/Renderer/BufferVariable.bf @@ -29,6 +29,9 @@ namespace GlitchyEngine.Renderer public bool IsUsed => _isUsed; + public uint32 Columns => _columns; + public uint32 Rows => _rows; + /** * Gets a pointer to the start of the variable in the constant buffers backing data. */ @@ -49,15 +52,19 @@ namespace GlitchyEngine.Renderer public void EnsureTypeMatch(int rows, int cols, ShaderVariableType type) { + Debug.Profiler.ProfileRendererFunction!(); + #if GE_SHADER_MATRIX_MISMATCH_IS_ERROR - Log.EngineLogger.Assert(rows == _rows || cols == _columns, scope $"The matrix-dimensions do not match: Expected {_rows} rows and {_rows} columns but Received {rows} rows and {cols} columns instead. Variable: \"{_name}\" of buffer: \"{_constantBuffer.Name}\""); + if (rows != _rows || cols != _columns) + Log.EngineLogger.Assert(false, scope $"The matrix-dimensions do not match: Expected {_rows} rows and {_rows} columns but Received {rows} rows and {cols} columns instead. Variable: \"{_name}\" of buffer: \"{_constantBuffer.Name}\""); #elif GE_SHADER_MATRIX_MISMATCH_IS_WARNING if (rows != _rows || cols != _columns) Log.EngineLogger.Warning($"The matrix-dimensions do not match: Expected {_rows} rows and {_rows} columns but Received {rows} rows and {cols} columns instead. Variable: \"{_name}\" of buffer: \"{_constantBuffer.Name}\""); #endif #if GE_SHADER_VAR_TYPE_MISMATCH_IS_ERROR - Log.EngineLogger.Assert(type == _type, scope $"The types do not match: Expected \"{_type}\" but Received \"{type}\" instead. Variable: \"{_name}\" of buffer: \"{_constantBuffer.Name}\""); + if (type != _type) + Log.EngineLogger.Assert(false, scope $"The types do not match: Expected \"{_type}\" but Received \"{type}\" instead. Variable: \"{_name}\" of buffer: \"{_constantBuffer.Name}\""); #elif GE_SHADER_VAR_TYPE_MISMATCH_IS_WARNING if (type != _type) Log.EngineLogger.Warning($"The types do not match: Expected \"{_type}\" but Received \"{type}\" instead. Variable: \"{_name}\" of buffer: \"{_constantBuffer.Name}\""); @@ -89,6 +96,9 @@ namespace GlitchyEngine.Renderer case typeof(Int4): EnsureTypeMatch(1, 4, .Int); + case typeof(uint32): + EnsureTypeMatch(1, 1, .UInt); + case typeof(Matrix4x3): EnsureTypeMatch(4, 3, .Float); case typeof(Matrix3x3): @@ -130,12 +140,13 @@ namespace GlitchyEngine.Renderer public void SetData(Int2 value) => SetData(value); public void SetData(Int3 value) => SetData(value); public void SetData(Int4 value) => SetData(value); + + public void SetData(uint32 value) => SetData(value); public void SetData(ColorRGB value) => SetData(value); public void SetData(ColorRGBA value) => SetData(value); public void SetData(Color value) => SetData((ColorRGBA)value); - public void SetData(Matrix4x3 value) => SetData(value); public void SetData(Matrix4x3[] value) diff --git a/GlitchyEngine/src/Renderer/ColorHSV.bf b/GlitchyEngine/src/Renderer/ColorHSV.bf index 0764a68..0525b33 100644 --- a/GlitchyEngine/src/Renderer/ColorHSV.bf +++ b/GlitchyEngine/src/Renderer/ColorHSV.bf @@ -38,7 +38,7 @@ namespace GlitchyEngine.Renderer else if (hsv.H < 360.0f) rgb_ = ColorRGB(c, 0, x); - return .(rgb_.Red + m, rgb_.Green + m, rgb_.Blue + m); + return .(rgb_.R + m, rgb_.G + m, rgb_.B + m); } } } \ No newline at end of file diff --git a/GlitchyEngine/src/Renderer/ConstantBuffer.bf b/GlitchyEngine/src/Renderer/ConstantBuffer.bf index 8d6596d..0c2b995 100644 --- a/GlitchyEngine/src/Renderer/ConstantBuffer.bf +++ b/GlitchyEngine/src/Renderer/ConstantBuffer.bf @@ -102,6 +102,7 @@ namespace GlitchyEngine.Renderer Bool, Float, Int, + UInt // todo } } diff --git a/GlitchyEngine/src/Renderer/DebugRenderer.bf b/GlitchyEngine/src/Renderer/DebugRenderer.bf index 32966cc..28d056d 100644 --- a/GlitchyEngine/src/Renderer/DebugRenderer.bf +++ b/GlitchyEngine/src/Renderer/DebugRenderer.bf @@ -1,5 +1,6 @@ using GlitchyEngine.Math; using GlitchyEngine.World; +using System; namespace GlitchyEngine.Renderer { @@ -47,6 +48,145 @@ namespace GlitchyEngine.Renderer Renderer.DrawLine(.Zero, .Up, .Lime, transform); Renderer.DrawLine(.Zero, .Forward, .Blue, transform); } + + //static GeometryBinding _frustumGeometry; + //static VertexBuffer _frustumVertices; + + /** + * Draws the view frustum for the given camera transform and projection. + * @param worldTransform The cameras transform matrix. + * @param projection The cameras projection matrix. + * @param observerVP The view projection of the rendering camera. + * @param color The color of the frustum. + */ + public static void DrawViewFrustum(Matrix worldTransform, Matrix projection, ColorRGBA color = .Red) + { + /*if (_frustumGeometry == null) + { + _frustumGeometry = new GeometryBinding(); + _frustumGeometry.SetPrimitiveTopology(.LineList); + + _frustumVertices = new VertexBuffer(typeof(Vector4), 8, .Dynamic, .Write); + _frustumGeometry.SetVertexBufferSlot(_frustumVertices, 0); + + using (IndexBuffer indexBuffer = new IndexBuffer(24, .Immutable)) + { + uint16[24] indices = .( + 0, 1, + 1, 2, + 2, 3, + 3, 0, + + 4, 5, + 5, 6, + 6, 7, + 7, 4, + + 0, 4, + 1, 5, + 2, 6, + 3, 7); + + indexBuffer.SetData(indices); + + _frustumGeometry.SetIndexBuffer(indexBuffer); + } + }*/ + + uint16[24] indices = .( + 0, 1, + 1, 2, + 2, 3, + 3, 0, + + 4, 5, + 5, 6, + 6, 7, + 7, 4, + + 0, 4, + 1, 5, + 2, 6, + 3, 7); + + Vector4[8] corners; + // Perspective + if(projection._43 != 0.0f) + { + // near plane for perspective projection, far plane if reversed + float d1 = -projection._34 / projection._33; + + float d2 = projection._34 / (1.0f - projection._33); + + float gOverS = projection._11; + float g = projection._22; + + //var corners = //(Vector4*)&_vbFrustum.Data; + + if(Math.Abs(d1) >= 10000) + { + d1 = 10.0f; + corners[0] = .( d1 / gOverS, d1 / g , d1, 0.0f); + corners[1] = .( corners[0].X, -corners[0].Y, d1, 0.0f); + corners[2] = .(-corners[0].X, -corners[0].Y, d1, 0.0f); + corners[3] = .(-corners[0].X, corners[0].Y, d1, 0.0f); + } + else + { + corners[0] = .( d1 / gOverS, d1 / g , d1, 1.0f); + corners[1] = .( corners[0].X, -corners[0].Y, d1, 1.0f); + corners[2] = .(-corners[0].X, -corners[0].Y, d1, 1.0f); + corners[3] = .(-corners[0].X, corners[0].Y, d1, 1.0f); + } + + if(Math.Abs(d2) >= 10000) + { + d2 = 10.0f; + + corners[4] = .( d2 / gOverS, d2 / g , d2, 0.0f); + corners[5] = .( corners[4].X, -corners[4].Y, d2, 0.0f); + corners[6] = .(-corners[4].X, -corners[4].Y, d2, 0.0f); + corners[7] = .(-corners[4].X, corners[4].Y, d2, 0.0f); + } + else + { + corners[4] = .( d2 / gOverS, d2 / g , d2, 1.0f); + corners[5] = .( corners[4].X, -corners[4].Y, d2, 1.0f); + corners[6] = .(-corners[4].X, -corners[4].Y, d2, 1.0f); + corners[7] = .(-corners[4].X, corners[4].Y, d2, 1.0f); + } + } + else + { + float l = -(projection._14 + 1.0f) / projection._11; + float r = (1.0f - projection._14) / projection._11; + + float t = -(projection._24 + 1.0f) / projection._22; + float b = (1.0f - projection._24) / projection._22; + + float n = -projection._34 / projection._33; + float f = (1 - projection._34) / projection._33; + + //var corners = (Vector4*)&_vbFrustum.Data; + corners[0] = .(r, t, n, 1.0f); + corners[1] = .(r, b, n, 1.0f); + corners[2] = .(l, b, n, 1.0f); + corners[3] = .(l, t, n, 1.0f); + + corners[4] = .(r, t, f, 1.0f); + corners[5] = .(r, b, f, 1.0f); + corners[6] = .(l, b, f, 1.0f); + corners[7] = .(l, t, f, 1.0f); + } + + for (int i = 0; i < indices.Count; i += 2) + { + uint16 index0 = indices[i]; + uint16 index1 = indices[i + 1]; + + Renderer2D.DrawLine(worldTransform * corners[index0], worldTransform * corners[index1], color); + } + } public static void Render(EcsWorld world) { diff --git a/GlitchyEngine/src/Renderer/Effect.bf b/GlitchyEngine/src/Renderer/Effect.bf index 5800180..6639a2f 100644 --- a/GlitchyEngine/src/Renderer/Effect.bf +++ b/GlitchyEngine/src/Renderer/Effect.bf @@ -2,67 +2,76 @@ using System; using System.IO; using System.Collections; using GlitchyEngine.Core; +using GlitchyEngine.Math; +using GlitchyEngine.Content; -namespace GlitchyEngine.Renderer +namespace GlitchyEngine.Renderer; + +/*/// Obsolete because of the ContentManager? +public class EffectLibrary { - public class EffectLibrary + private Dictionary _effects = new .() ~ delete _; + + private List _ownedStrings = new .() ~ DeleteContainerAndItems!(_); + + public this() { - private Dictionary _effects = new .() ~ delete _; + } - private List _ownedStrings = new .() ~ DeleteContainerAndItems!(_); - - public this() + public ~this() + { + for(let pair in _effects) { + pair.value.ReleaseRef(); + } + } + + public void Add(Effect effect, String effectName = null) + { + Debug.Profiler.ProfileResourceFunction!(); + + String name; + + if(effectName == null) + { + name = effect.Name; + } + else + { + name = new String(effectName); + _ownedStrings.Add(name); } - public ~this() + Log.EngineLogger.AssertDebug(!Exists(name), "Can't add two effects with the same name to library."); + + _effects.Add(name, effect..AddRef()); + } + + /** + * Loads the effect with the given file name. + * @param filepath The path to the effect file. + * @param effectName The optional custom effect name which will be used to identify the effect. + * @returns The loaded Effect. Note: This function will increment the reference counter of the effect, so the programmer must decrement it once it's not used anymore. + * If the return-value is not needed, use LoadNoRefInc instead. + */ + public Effect Load(String filepath, String effectName = null) + { + Debug.Profiler.ProfileResourceFunction!(); + + String name = effectName; + + if(name == null) { - for(let pair in _effects) - { - pair.value.ReleaseRef(); - } + name = scope:: String(); + Path.GetFileNameWithoutExtension(filepath, name); } - public void Add(Effect effect, String effectName = null) + if (Exists(name)) { - Debug.Profiler.ProfileResourceFunction!(); - - String name; - - if(effectName == null) - { - name = effect.Name; - } - else - { - name = new String(effectName); - _ownedStrings.Add(name); - } - - Log.EngineLogger.AssertDebug(!Exists(name), "Can't add two effects with the same name to library."); - - _effects.Add(name, effect..AddRef()); + return Get(name); } - - /** - * Loads the effect with the given file name. - * @param filepath The path to the effect file. - * @param effectName The optional custom effect name which will be used to identify the effect. - * @returns The loaded Effect. Note: This function will increment the reference counter of the effect, so the programmer must decrement it once it's not used anymore. - * If the return-value is not needed, use LoadNoRefInc instead. - */ - public Effect Load(String filepath, String effectName = null) + else { - Debug.Profiler.ProfileResourceFunction!(); - - String name = effectName; - - if(name == null) - { - name = scope:: String(); - Path.GetFileNameWithoutExtension(filepath, name); - } - Log.EngineLogger.AssertDebug(!Exists(name), "Can't add two effects with the same name to library."); Effect effect = new Effect(filepath, name); @@ -70,422 +79,691 @@ namespace GlitchyEngine.Renderer return effect; } - - /** - * Loads the effect with the given file name. - * @param filepath The path to the effect file. - * @param effectName The optional custom effect name which will be used to identify the effect. - */ - public void LoadNoRefInc(String filepath, String effectName = null) - { - var v = Load(filepath, effectName); - v.ReleaseRef(); - } - - public Effect Get(String effectName) - { - Debug.Profiler.ProfileResourceFunction!(); - - Log.EngineLogger.AssertDebug(Exists(effectName), "Effect not found!"); - - return _effects.GetValue(effectName).Get()..AddRef(); - } - - public bool Exists(String effectName) => _effects.ContainsKey(effectName); } - public class Effect : RefCounter + /** + * Loads the effect with the given file name. + * @param filepath The path to the effect file. + * @param effectName The optional custom effect name which will be used to identify the effect. + */ + public void LoadNoRefInc(String filepath, String effectName = null) { - internal VertexShader _vs ~ _?.ReleaseRef(); - internal PixelShader _ps ~ _?.ReleaseRef(); - protected String _name ~ delete _; + var v = Load(filepath, effectName); + v.ReleaseRef(); + } - BufferCollection _bufferCollection ~ delete _; + public Effect Get(String effectName) + { + Debug.Profiler.ProfileResourceFunction!(); - BufferVariableCollection _variables ~ delete _; - - typealias TextureEntry = (Texture Texture, ShaderTextureCollection.ResourceEntry* VsSlot, ShaderTextureCollection.ResourceEntry* PsSlot); - Dictionary _textures ~ delete _; + Log.EngineLogger.AssertDebug(Exists(effectName), "Effect not found!"); - public Dictionary Textures => _textures; + return _effects.GetValue(effectName).Get()..AddRef(); + } - public VertexShader VertexShader + public bool Exists(String effectName) => _effects.ContainsKey(effectName); +}*/ + +public class Effect : Asset +{ + internal VertexShader _vs ~ _?.ReleaseRef(); + internal PixelShader _ps ~ _?.ReleaseRef(); + + typealias VariableDesc = Dictionary>; + + protected VariableDesc _variableDescriptions = new .() ~ { + for (var (key, value) in _) { - get => _vs; - set + delete key; + + for (var (entryKey, entry) in value) { - _vs?.ReleaseRef(); - _vs = value; - _vs?.AddRef(); - } - } - - public PixelShader PixelShader - { - get => _ps; - set - { - _ps?.ReleaseRef(); - _ps = value; - _ps?.AddRef(); + delete entryKey; + entry.Dispose(); } + + delete value; } - public BufferCollection Buffers => _bufferCollection; - public BufferVariableCollection Variables => _variables; + delete _; + }; - public String Name => _name; + protected Dictionary _engineBuffers = new .() ~ DeleteDictionaryAndKeysAndValues!(_); - [Obsolete("Will be removed in the future", false)] - public this() + BufferCollection _bufferCollection ~ _.ReleaseRef(); + + BufferVariableCollection _variables ~ delete _; + + public struct TextureEntry + { + public TextureViewBinding BoundTexture; + public ShaderTextureCollection.ResourceEntry* VsSlot; + public ShaderTextureCollection.ResourceEntry* PsSlot; + + public this(TextureViewBinding boundTexture, ShaderTextureCollection.ResourceEntry* vsSlot, ShaderTextureCollection.ResourceEntry* psSlot) { + BoundTexture = boundTexture; + VsSlot = vsSlot; + PsSlot = psSlot; } - - public this(String filename, String vsEntry, String psEntry, String shaderName = null) + } + + Dictionary _textures ~ delete _; + + public Dictionary Textures => _textures; + + public VertexShader VertexShader + { + get => _vs; + private set => SetReference!(_vs, value); + } + + public PixelShader PixelShader + { + get => _ps; + private set => SetReference!(_ps, value); + } + + public BufferCollection Buffers => _bufferCollection; + public BufferVariableCollection Variables => _variables; + + [Obsolete("", false)] + public this(String filename) + { + Debug.Profiler.ProfileResourceFunction!(); + + String fileContent = scope String(); + String vsName = scope String(); + String psName = scope String(); + + ProcessFile(filename, fileContent, vsName, psName, _variableDescriptions, _engineBuffers); + + Compile(fileContent, filename, vsName, psName); + + MergeResources(); + } + + public this(Stream data, StringView assetIdentifier, IContentManager contentManager) + { + Debug.Profiler.ProfileResourceFunction!(); + + String fileContent = scope String(); + String vsName = scope String(); + String psName = scope String(); + + ProcessStream(data, fileContent, vsName, psName, _variableDescriptions, _engineBuffers); + + Compile(fileContent, assetIdentifier, vsName, psName, contentManager); + + MergeResources(); + } + + public ~this() + { + Debug.Profiler.ProfileResourceFunction!(); + + for(let entry in _textures) { - Debug.Profiler.ProfileResourceFunction!(); - - CompileFromFile(filename, vsEntry, psEntry); - - if(shaderName == null) - { - _name = new String(shaderName); - } - else - { - _name = new String(); - Path.GetFileNameWithoutExtension(filename, _name); - } + entry.value.BoundTexture.Release(); } + } - public this(String filename, String shaderName = null) + public void SetTexture(String name, Texture texture) + { + Debug.Profiler.ProfileRendererFunction!(); + + if (texture == null) + return; + + [Inline]InternalSetTexture(name, texture.GetViewBinding()); + } + + public void SetTexture(String name, RenderTargetGroup renderTargetGroup, int32 firstTarget, uint32 targetCount = 1) + { + Debug.Profiler.ProfileRendererFunction!(); + + if (targetCount != 1) + Runtime.NotImplemented("Binding multiple rendertargets to a slot is not yet implemented."); + + // We have to release the viewBinding because GetViewBinding internally increases the counter + [Inline]InternalSetTexture(name, renderTargetGroup.GetViewBinding(firstTarget)); + } + + public void SetTexture(String name, TextureViewBinding textureViewBinding) + { + Debug.Profiler.ProfileRendererFunction!(); + + [Inline]InternalSetTexture(name, textureViewBinding..AddRef()); + } + + private void InternalSetTexture(String name, TextureViewBinding textureViewBinding) + { + Debug.Profiler.ProfileRendererFunction!(); + + ref TextureEntry entry = ref _textures[name]; + + entry.BoundTexture.Release(); + entry.BoundTexture = textureViewBinding; + + entry.VsSlot?.BoundTexture..Release() = entry.BoundTexture..AddRef(); + entry.PsSlot?.BoundTexture..Release() = entry.BoundTexture..AddRef(); + } + + /*private void ApplyTextures() + { + Debug.Profiler.ProfileRendererFunction!(); + + for(let (name, entry) in _textures) { - Debug.Profiler.ProfileResourceFunction!(); - - String fileContent = scope String(); - String vsName = scope String(); - String psName = scope String(); - - ProcessFile(filename, fileContent, vsName, psName); - - Compile(fileContent, vsName, psName); - - MergeResources(); + entry.VsSlot?.BoundTexture.Release(); + entry.VsSlot?.BoundTexture = entry.BoundTexture; + entry.VsSlot?.BoundTexture.AddRef(); - if(shaderName == null) + entry.PsSlot?.BoundTexture.Release(); + entry.PsSlot?.BoundTexture = entry.BoundTexture; + entry.PsSlot?.BoundTexture.AddRef(); + } + }*/ + + public void ApplyChanges() + { + Debug.Profiler.ProfileRendererFunction!(); + + //ApplyTextures(); + + for(let buffer in _bufferCollection) + { + if(let cbuffer = buffer.Buffer as ConstantBuffer) { - _name = new String(); - Path.GetFileNameWithoutExtension(filename, _name); - } - else - { - _name = new String(shaderName); + cbuffer.Update(); } } + } - public this(String shaderName, String vsPath, String vsEntry, String psPath, String psEntry) + public void Bind() + { + Debug.Profiler.ProfileRendererFunction!(); + + //ApplyTextures(); + //ApplyChanges(); + + RenderCommand.BindVertexShader(_vs); + RenderCommand.BindPixelShader(_ps); + } + + /*private void CompileFromFile(String filename, String vsEntry, String psEntry) + { + Debug.Profiler.ProfileResourceFunction!(); + + let vs = Shader.FromFile!(filename, vsEntry); + VertexShader = vs; + vs.ReleaseRef(); + let ps = Shader.FromFile!(filename, psEntry); + PixelShader = ps; + ps.ReleaseRef(); + }*/ + + private void Compile(String fileContent, StringView fileName, String vsEntry, String psEntry, IContentManager contentManager = null) + { + Debug.Profiler.ProfileResourceFunction!(); + + // TODO: vsEntry and psEntry could be empty (which is a valid case.) + let vs = new VertexShader(fileContent, fileName, vsEntry, contentManager); + VertexShader = vs; + vs.ReleaseRef(); + let ps = new PixelShader(fileContent, fileName, psEntry, contentManager); + PixelShader = ps; + ps.ReleaseRef(); + } + + const String effectKeyword = "#effect"; + + 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 arguments) + { + name = .(); + + int startOfLine; + int endOfLine; + do { - Debug.Profiler.ProfileResourceFunction!(); + startOfLine = code.IndexOf("#pragma", startindex); - Compile(vsPath, vsEntry, psPath, psEntry); + if (startOfLine == -1) + return .Err; + + endOfLine = code.IndexOf('\n', startOfLine); + + StringView line = (endOfLine != -1) ? code.Substring(startOfLine, endOfLine - startOfLine) : code.Substring(startOfLine); + + // 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(); - _name = new String(shaderName); - } + int rBracketIndex = line.IndexOf(']'); - public ~this() - { - Debug.Profiler.ProfileResourceFunction!(); - - for(let entry in _textures) + if (rBracketIndex == -1) { - entry.value.Texture?.ReleaseRef(); + 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); } } - public void SetTexture(String name, Texture texture) + return .Ok((startOfLine, endOfLine)); + } + + private static void ProcessStream(Stream rawData, String fileContent, String outVsName, String outPsName, VariableDesc outVarDescs, Dictionary outEngineBuffers) + { + Debug.Profiler.ProfileResourceFunction!(); + + StreamReader streamReader = scope .(rawData); + streamReader.ReadToEnd(fileContent); + // append line ending just in case the file doesn't end with one. + fileContent.Append('\n'); + + ProcessFileContent(fileContent, outVsName, outPsName, outVarDescs, outEngineBuffers); + } + + /** + * Loads the effect file and extracts the names of the vertex- and pixel-shader. + * @param filename The path of the effect file. + * @param fileContent The preprocessed effect file. + * @param outVsName The string that will receive the vertex shader entry point. + * @param outPsName The string that will receive the pixel shader entry point. + * @param outVarDescs The dictionary that will contain the Variable descriptions. + */ + private static void ProcessFile(String filename, String fileContent, String outVsName, String outPsName, VariableDesc outVarDescs, Dictionary outEngineBuffers) + { + Debug.Profiler.ProfileResourceFunction!(); + + File.ReadAllText(filename, fileContent, true); + // append line ending just in case the file doesn't end with one. + fileContent.Append('\n'); + + ProcessFileContent(fileContent, outVsName, outPsName, outVarDescs, outEngineBuffers); + } + + private static void ProcessFileContent(String fileContent, String outVsName, String outPsName, VariableDesc outVarDescs, Dictionary outEngineBuffers) + { + Debug.Profiler.ProfileResourceFunction!(); + + Dictionary arguments = scope .(); + + int index = 0; + + while (true) { - Debug.Profiler.ProfileRendererFunction!(); + Result<(int Start, int End)> result = GetNextPreprocessor(fileContent, index, let name, arguments..Clear()); - ref TextureEntry entry = ref _textures[name]; - - entry.Texture?.ReleaseRef(); - entry.Texture = texture; - entry.Texture?.AddRef(); - } - - private void ApplyTextures() - { - Debug.Profiler.ProfileRendererFunction!(); - - for(let (name, entry) in _textures) + if (result case .Err) + break; + else if (result case .Ok(let value)) { - entry.VsSlot?.Texture?.ReleaseRef(); - entry.VsSlot?.Texture = entry.Texture; - entry.VsSlot?.Texture?.AddRef(); - - entry.PsSlot?.Texture?.ReleaseRef(); - entry.PsSlot?.Texture = entry.Texture; - entry.PsSlot?.Texture?.AddRef(); - } - } + index = value.End; - private void ApplyChanges() - { - Debug.Profiler.ProfileRendererFunction!(); - - for(let buffer in _bufferCollection) - { - if(let cbuffer = buffer.Buffer as ConstantBuffer) + switch(name) { - cbuffer.Update(); - } - } - } - - public void Bind(GraphicsContext context) - { - Debug.Profiler.ProfileRendererFunction!(); - - ApplyTextures(); - ApplyChanges(); - - context.SetVertexShader(_vs); - context.SetPixelShader(_ps); - } - - - - private void CompileFromFile(String filename, String vsEntry, String psEntry) - { - Debug.Profiler.ProfileResourceFunction!(); - - let vs = Shader.FromFile!(filename, vsEntry); - VertexShader = vs; - vs.ReleaseRef(); - let ps = Shader.FromFile!(filename, psEntry); - PixelShader = ps; - ps.ReleaseRef(); - } - - private void Compile(String fileContent, String vsEntry, String psEntry) - { - Debug.Profiler.ProfileResourceFunction!(); - - // TODO: vsEntry and psEntry could be empty (which is a valid case.) - let vs = new VertexShader(fileContent, vsEntry); - VertexShader = vs; - vs.ReleaseRef(); - let ps = new PixelShader(fileContent, psEntry); - PixelShader = ps; - ps.ReleaseRef(); - } - - const String effectKeyword = "#effect"; - - /** - * Loads the effect file and extracts the names of the vertex- and pixel-shader. - * @param filename The path of the effect file. - * @param fileContent The preprocessed effect file. - * @param vsName The string that will receive the vertex shader entry point. - * @param psName The string that will receive the pixel shader entry point. - */ - private static void ProcessFile(String filename, String fileContent, String vsName, String psName) - { - Debug.Profiler.ProfileResourceFunction!(); - - File.ReadAllText(filename, fileContent, true); - // append line ending just in case the file doesn't end with one. - fileContent.Append('\n'); - - int effectIndex = fileContent.IndexOf(effectKeyword, true); - - Log.EngineLogger.Assert(effectIndex >= 0, "Could not find #effect preprocessor directive."); - - int lineEndIndex = fileContent.IndexOf('\n', effectIndex + effectKeyword.Length); - - // String containing the #effect directive - StringView effectDirective = fileContent.Substring(effectIndex, lineEndIndex - effectIndex); - - int paramStartIndex = effectDirective.IndexOf('['); - - Log.EngineLogger.Assert(paramStartIndex >= 0, "Expected '[' after \"#effect\""); - - StringView effectToBracket = effectDirective.Substring(effectKeyword.Length, paramStartIndex - effectKeyword.Length); - - // Make sure there is only whitespace between "#effect" and "[" - Log.EngineLogger.Assert(effectToBracket.IsWhiteSpace, "Expected '[' after \"#effect\""); - - int paramEndIndex = effectDirective.IndexOf(']'); - - Log.EngineLogger.Assert(paramEndIndex >= 0, "Expected ']'"); - - StringView parameters = effectDirective.Substring(paramStartIndex + 1, paramEndIndex - paramStartIndex - 1); - - for(StringView parameter in parameters.Split(',')) - { - int indexOfEquals = parameter.IndexOf("="); - - Log.EngineLogger.Assert(indexOfEquals >= 0, "Expected '='"); - - StringView paramName = parameter.Substring(0, indexOfEquals); - paramName.Trim(); - - StringView paramValue = parameter.Substring(indexOfEquals + 1); - paramValue.Trim(); - - switch(paramName) - { - case "VS", "VertexShader": - vsName.Append(paramValue); - case "PS", "PixelShader": - psName.Append(paramValue); - default: - Log.EngineLogger.Assert(false, scope $"Unknown parameter name \"{paramName}\"."); - } - } - - // remove preprocessor directive from string so that the compiler wont try process it - fileContent.Remove(effectIndex, lineEndIndex - effectIndex); - } - - protected extern void Compile(String vsPath, String vsEntry, String psPath, String psEntry); - - private void MergeResources() - { - Debug.Profiler.ProfileResourceFunction!(); - - MergeConstantBuffers(); - MergeBufferVariables(); - MergeTextures(); - } - - private void MergeConstantBuffers() - { - Debug.Profiler.ProfileResourceFunction!(); - - _bufferCollection = new BufferCollection(); - - HashSet bufferNames = scope HashSet(); - - AddShaderBuffers(_vs, bufferNames); - AddShaderBuffers(_ps, bufferNames); - - int internalIndex = 0; - - for(String bufferName in bufferNames) - { - let vsBuffer = _vs.Buffers.TryGetBufferEntry(bufferName); - let psBuffer = _ps.Buffers.TryGetBufferEntry(bufferName); - - if(vsBuffer != null && psBuffer != null) - { - BufferCollection.BufferEntry* fxBuffer = null; - // choose the larger of the two - if(psBuffer.Buffer.Description.Size > vsBuffer.Buffer.Description.Size) - fxBuffer = psBuffer; - else - fxBuffer = vsBuffer; - - _bufferCollection.Add(internalIndex, bufferName, fxBuffer.Buffer); - - _vs.Buffers.TryReplaceBuffer(vsBuffer.Index, fxBuffer.Buffer); - _ps.Buffers.TryReplaceBuffer(psBuffer.Index, fxBuffer.Buffer); - } - else if(vsBuffer != null) - { - _bufferCollection.Add(internalIndex, bufferName, vsBuffer.Buffer); - } - else if(psBuffer != null) - { - _bufferCollection.Add(internalIndex, bufferName, psBuffer.Buffer); - } - - internalIndex++; - } - } - - private void MergeBufferVariables() - { - Debug.Profiler.ProfileResourceFunction!(); - - _variables = new BufferVariableCollection(false); - - for(let buffer in _bufferCollection) - { - if(let cbuffer = buffer.Buffer as ConstantBuffer) - { - for(let variable in cbuffer.Variables) + case "Effect": + for (let (argName, argValue) in arguments) { - _variables.TryAdd(variable); + switch(argName) + { + case "VS", "VertexShader": + outVsName.Append(argValue); + case "PS", "PixelShader": + outPsName.Append(argValue); + default: + Log.EngineLogger.Assert(false, scope $"Unknown parameter name \"{name}\"."); + } } + case "EditorVariable": + ProcessEditorVariables(arguments, outVarDescs); + case "EngineBuffer": + ProcessEngineBuffer(arguments, outEngineBuffers); + default: + continue; } + + CommentLine(fileContent, value.Start); } } + } - private void AddShaderBuffers(Shader shader, HashSet bufferNames) + private static void ProcessEngineBuffer(Dictionary arguments, Dictionary outEngineBuffers) + { + String nameInEngine = null; + String nameInShader = null; + + for (var (argName, argValue) in arguments) { - Debug.Profiler.ProfileResourceFunction!(); - - if(shader != null) + if (argValue.StartsWith('"') && argValue.EndsWith('"')) { - for(let buffer in shader.Buffers) - { - bufferNames.Add(buffer.Name); - } + argValue = argValue[1...^2]; } - } - - /// Merges the texture slots of all shaders into one dictionary. - private void MergeTextures() - { - Debug.Profiler.ProfileResourceFunction!(); - - delete _textures; - _textures = new .(); - - EnumerateShaderTextures(_vs); - EnumerateShaderTextures(_ps); - } - - /** @brief Merges all textures of the given shader into the _textures dictionary. - * @param shader The shader whose textures will be merged into the dictionary. - */ - private void EnumerateShaderTextures(T shader) where T : Shader - { - Debug.Profiler.ProfileResourceFunction!(); - - //for(var (name, index, texture) in shader.Resources) - for(var shaderEntry in ref shader.Textures) + switch (argName) { - TextureEntry entry; - - // Get existing entry or create new - if(!_textures.TryGetValue(shaderEntry.Name, out entry)) - { - entry = (shaderEntry.Texture, null, null); - entry.Texture?.AddRef(); - } - - // Set the corresponding shader resource slot - if(typeof(T) == typeof(VertexShader)) - { - entry.VsSlot = &shaderEntry; - } - else if(typeof(T) == typeof(PixelShader)) - { - entry.PsSlot = &shaderEntry; - } - - // If the entry has no texture but the shader has one -> set texture - if(entry.Texture == null && shaderEntry.Texture != null) - { - entry.Texture = shaderEntry.Texture; - entry.Texture?.AddRef(); - } - - // save entry - _textures[shaderEntry.Name] = entry; + case "Name": + nameInShader = new String(argValue); + case "Binding": + nameInEngine = new String(argValue); + default: + Log.EngineLogger.Assert(false, scope $"Unknown parameter for EngineBuffer: \"{argName}\"."); } } + + Log.EngineLogger.AssertDebug(nameInEngine != null); + Log.EngineLogger.AssertDebug(nameInShader != null); + + outEngineBuffers.Add(nameInEngine, nameInShader); + } + + private static void ProcessEditorVariables(Dictionary arguments, VariableDesc outVarDescs) + { + String variableName = null; + + Dictionary parameters = new .(); + + for (var (name, value) in arguments) + { + if (value.StartsWith('"') && value.EndsWith('"')) + { + value = value[1...^2]; + } + + switch(name) + { + case "Name": + variableName = new String(value); + case "Min", "Max": + Variant paramValue = ParseVariableValue(value); + parameters.Add(new String(name), paramValue); + default: + Variant paramValue = Variant.Create(new String(value), true); + parameters.Add(new String(name), paramValue); + } + } + + Log.EngineLogger.AssertDebug(variableName != null, "Missing argument \"Name\" int variable description."); + + outVarDescs.Add(variableName, parameters); + + } + + private static Variant ParseVariableValue(StringView valueString) + { + if (valueString[0].IsDigit || valueString[0] == '-') + { + var valueString; + + if (valueString.EndsWith('f')) + valueString.Length--; + + var result = float.Parse(valueString); + + Log.EngineLogger.AssertDebug(result case .Ok); + + if (result case .Ok(let value)) + return Variant.Create(value); + } + else if (valueString.StartsWith("float")) + { + int index = 5; + + int numComponents = valueString[index++] - '0'; + + while (valueString[index] != '(') + { + Log.EngineLogger.AssertDebug(valueString[index].IsWhiteSpace, "Expected '('."); + + index++; + } + + Log.EngineLogger.AssertDebug(numComponents >= 2 && numComponents <= 4, scope $"Unsupported component count {numComponents}. Value must be between 2 and 4"); + + 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(*(Vector2*)floats.Ptr); + else if (numComponents == 3) + return Variant.Create(*(Vector3*)floats.Ptr); + else if (numComponents == 4) + return Variant.Create(*(Vector4*)floats.Ptr); + } + else + { + Log.EngineLogger.Error($"Unsupported variable value: \"{valueString}\""); + } + + return Variant.Create(0.0f); + } + + //protected extern void Compile(String code, String fileName, String vsEntry, String psEntry); + + private void MergeResources() + { + Debug.Profiler.ProfileResourceFunction!(); + + MergeConstantBuffers(); + MergeBufferVariables(); + MergeTextures(); + } + + private void MergeConstantBuffers() + { + Debug.Profiler.ProfileResourceFunction!(); + + _bufferCollection = new BufferCollection(); + + HashSet bufferNames = scope HashSet(); + + AddShaderBuffers(_vs, bufferNames); + AddShaderBuffers(_ps, bufferNames); + + int internalIndex = 0; + + for(String bufferName in bufferNames) + { + let vsBuffer = _vs.Buffers.TryGetBufferEntry(bufferName); + let psBuffer = _ps.Buffers.TryGetBufferEntry(bufferName); + + if(vsBuffer != null && psBuffer != null) + { + BufferCollection.BufferEntry* fxBuffer = null; + // choose the larger of the two + if(psBuffer.Buffer.Description.Size > vsBuffer.Buffer.Description.Size) + fxBuffer = psBuffer; + else + fxBuffer = vsBuffer; + + _bufferCollection.Add(internalIndex, bufferName, fxBuffer.Buffer); + + _vs.Buffers.TryReplaceBuffer(vsBuffer.Index, fxBuffer.Buffer); + _ps.Buffers.TryReplaceBuffer(psBuffer.Index, fxBuffer.Buffer); + } + else if(vsBuffer != null) + { + _bufferCollection.Add(internalIndex, bufferName, vsBuffer.Buffer); + } + else if(psBuffer != null) + { + _bufferCollection.Add(internalIndex, bufferName, psBuffer.Buffer); + } + + internalIndex++; + } + + SetReference!(_vs.[Friend]_buffers, _bufferCollection); + SetReference!(_ps.[Friend]_buffers, _bufferCollection); + } + + private void MergeBufferVariables() + { + Debug.Profiler.ProfileResourceFunction!(); + + _variables = new BufferVariableCollection(false); + + outer: for(let buffer in _bufferCollection) + { + for (let eb in _engineBuffers) + { + if (eb.value == buffer.Name) + { + continue outer; + } + } + + if(let cbuffer = buffer.Buffer as ConstantBuffer) + { + for(let variable in cbuffer.Variables) + { + _variables.TryAdd(variable); + } + } + } + } + + private void AddShaderBuffers(Shader shader, HashSet bufferNames) + { + Debug.Profiler.ProfileResourceFunction!(); + + if(shader != null) + { + for(let buffer in shader.Buffers) + { + bufferNames.Add(buffer.Name); + } + } + } + + /// Merges the texture slots of all shaders into one dictionary. + private void MergeTextures() + { + Debug.Profiler.ProfileResourceFunction!(); + + delete _textures; + _textures = new .(); + + EnumerateShaderTextures(_vs); + EnumerateShaderTextures(_ps); + } + + /** @brief Merges all textures of the given shader into the _textures dictionary. + * @param shader The shader whose textures will be merged into the dictionary. + */ + private void EnumerateShaderTextures(T shader) where T : Shader + { + Debug.Profiler.ProfileResourceFunction!(); + + //for(var (name, index, texture) in shader.Resources) + for(var shaderEntry in ref shader.Textures) + { + TextureEntry entry; + + // Get existing entry or create new + if(!_textures.TryGetValue(shaderEntry.Name, out entry)) + { + entry = .(shaderEntry.BoundTexture, null, null); + entry.BoundTexture.AddRef(); + } + + // Set the corresponding shader resource slot + if(typeof(T) == typeof(VertexShader)) + { + entry.VsSlot = &shaderEntry; + } + else if(typeof(T) == typeof(PixelShader)) + { + entry.PsSlot = &shaderEntry; + } + + // If the entry has no texture but the shader has one -> set texture + if(entry.BoundTexture.IsEmpty && !shaderEntry.BoundTexture.IsEmpty) + { + entry.BoundTexture = shaderEntry.BoundTexture; + entry.BoundTexture.AddRef(); + } + + // save entry + _textures[shaderEntry.Name] = entry; + } } } diff --git a/GlitchyEngine/src/Renderer/FullscreenQuad.bf b/GlitchyEngine/src/Renderer/FullscreenQuad.bf new file mode 100644 index 0000000..7725e0a --- /dev/null +++ b/GlitchyEngine/src/Renderer/FullscreenQuad.bf @@ -0,0 +1,56 @@ +using GlitchyEngine.Math; + +namespace GlitchyEngine.Renderer +{ + static class FullscreenQuad + { + static GeometryBinding s_fullscreenQuadGeometry; + + public static void Init() + { + s_fullscreenQuadGeometry = new GeometryBinding(); + s_fullscreenQuadGeometry.SetPrimitiveTopology(.TriangleList); + + using(var quadVertices = new VertexBuffer(typeof(Vector4), 3, .Immutable)) + { + Vector4[4] vertices = .( + .(-1, 1, 0, 0), + .( 3, 1, 2, 0), + .(-1,-3, 0, 2), + ); + + quadVertices.SetData(vertices); + s_fullscreenQuadGeometry.SetVertexBufferSlot(quadVertices, 0); + } + + using(var quadIndices = new IndexBuffer(3, .Immutable)) + { + uint16[3] indices = .(0, 1, 2); + + quadIndices.SetData(indices); + s_fullscreenQuadGeometry.SetIndexBuffer(quadIndices); + } + + VertexElement[] vertexElements = new .( + VertexElement(.R32G32_Float, "POSITION"), + VertexElement(.R32G32_Float, "TEXCOORD") + ); + + using (var quadBatchLayout = new VertexLayout(vertexElements, true)) + { + s_fullscreenQuadGeometry.SetVertexLayout(quadBatchLayout); + } + } + + public static void Deinit() + { + s_fullscreenQuadGeometry.ReleaseRef(); + } + + public static void Draw() + { + s_fullscreenQuadGeometry.Bind(); + RenderCommand.DrawIndexed(s_fullscreenQuadGeometry); + } + } +} \ No newline at end of file diff --git a/GlitchyEngine/src/Renderer/GeometryBinding.bf b/GlitchyEngine/src/Renderer/GeometryBinding.bf index 3aafd20..485a8d0 100644 --- a/GlitchyEngine/src/Renderer/GeometryBinding.bf +++ b/GlitchyEngine/src/Renderer/GeometryBinding.bf @@ -1,10 +1,12 @@ using System; using System.Collections; +using GlitchyEngine.Content; using GlitchyEngine.Core; namespace GlitchyEngine.Renderer { - public class GeometryBinding : RefCounter + // Todo: Rename to Mesh? + public class GeometryBinding : Asset { internal List _vertexBuffers = new .() ~ delete _; internal IndexBuffer _indexBuffer ~ _?.ReleaseRef(); diff --git a/GlitchyEngine/src/Renderer/GraphicsContext.bf b/GlitchyEngine/src/Renderer/GraphicsContext.bf index a5de076..3ba694f 100644 --- a/GlitchyEngine/src/Renderer/GraphicsContext.bf +++ b/GlitchyEngine/src/Renderer/GraphicsContext.bf @@ -34,6 +34,11 @@ namespace GlitchyEngine.Renderer * @param setDepthTarget If set to true the depth stencil target of the given renderTarget will be bound (only applies if slot is 0). */ public extern void SetRenderTarget(RenderTarget2D renderTarget, int slot = 0, bool setDepthTarget = true); + + /** + * Unbinds all rendertargets. + */ + public extern void UnbindRenderTargets(); /** * Binds all. @@ -74,6 +79,8 @@ namespace GlitchyEngine.Renderer public extern void Draw(uint32 vertexCount, uint32 startVertexIndex = 0); public extern void DrawIndexed(uint32 indexCount, uint32 startIndexLocation = 0, int32 vertexOffset = 0); + + public extern void DrawIndexedInstanced(uint32 indexCountPerInstance, uint32 instanceCount, uint32 startIndexLocation, int32 baseVertexLocation, uint32 startInstanceLocation); public void SetIndexBuffer(IndexBuffer indexBuffer, uint32 byteOffset = 0) { @@ -91,16 +98,9 @@ namespace GlitchyEngine.Renderer } [Inline] - public void SetViewports(Viewport[] viewports) + public void SetViewports(Span viewports) { - SetViewports((.)viewports.Count, viewports.CArray()); - } - - [Inline] - public void SetViewports(Viewport[CSize] viewports) where CSize : const uint32 - { - var viewports; - SetViewports(CSize, &viewports); + SetViewports((.)viewports.Length, viewports.Ptr); } public extern void SetViewports(uint32 viewportsLength, Viewport* viewports); @@ -109,8 +109,12 @@ namespace GlitchyEngine.Renderer public extern void SetPrimitiveTopology(PrimitiveTopology primitiveTopology); - public extern void SetVertexShader(VertexShader vertexShader); + public extern void BindVertexShader(VertexShader vertexShader); - public extern void SetPixelShader(PixelShader pixelShader); + public extern void BindPixelShader(PixelShader pixelShader); + + public extern void UnbindTextures(); + + public extern void BindConstantBuffer(Buffer buffer, int slot, ShaderStage stage); } } diff --git a/GlitchyEngine/src/Renderer/Material.bf b/GlitchyEngine/src/Renderer/Material.bf index 96b5131..7822bfc 100644 --- a/GlitchyEngine/src/Renderer/Material.bf +++ b/GlitchyEngine/src/Renderer/Material.bf @@ -1,208 +1,233 @@ -using System; -using System.Collections; +using GlitchyEngine.Content; using GlitchyEngine.Core; using GlitchyEngine.Math; +using System; +using System.Collections; using internal GlitchyEngine.Renderer; -namespace GlitchyEngine.Renderer +namespace GlitchyEngine.Renderer; + +public class Material : Asset { - public class Material : RefCounter + private Effect _effect ~ _?.ReleaseRef(); + + private uint8[] _rawVariables ~ delete _; + + private Dictionary> _textures = new .() ~ delete _; + + private Dictionary _variables = new .() ~ delete _; + + public Effect Effect => _effect; + + public this(Effect effect) { - private Effect _effect ~ _?.ReleaseRef(); + _effect = effect..AddRef(); - private uint8[] _rawVariables ~ delete _; + // TODO: get variables from effect - private Dictionary _textures = new .(); - - private Dictionary _variables = new .() ~ delete _; - - public Effect Effect => _effect; - - public this(Effect effect) + // Get texture slots from effect + for(let (name, entry) in _effect.Textures) { - _effect = effect..AddRef(); + // TODO: We need to be able to define default textures in the shader. + // At least things like "Black", "White", "Normal" + // At best whole paths. Shouldn't be that hard to do... + /*var texture = entry.BoundTexture;*/ - // TODO: get variables from effect - - for(let (name, entry) in _effect.Textures) - { - var texture = entry.Texture; - texture?.AddRef(); - - _textures.Add(name, texture); - } - - InitRawData(); + _textures.Add(name, .Invalid); } - public ~this() - { - for(let (name, texture) in _textures) - { - texture?.ReleaseRef(); - } + InitRawData(); + } - delete _textures; + + /** @brief Initializes the raw data array for the variables. + */ + private void InitRawData() + { + uint32 bufferSize = 0; + + for(let variable in _effect.Variables) + { + _variables.Add(variable.Name, (bufferSize, variable)); + + bufferSize += variable._sizeInBytes; } - /** @brief Initializes the raw data array for the variables. - */ - private void InitRawData() + _rawVariables = new uint8[bufferSize]; + } + + /** + * Binds the materials Shaders and Parameters to the given context. + */ + public void Bind() + { + Debug.Profiler.ProfileRendererFunction!(); + + for(let (name, texture) in _textures) { - uint32 bufferSize = 0; - - for(let variable in _effect.Variables) - { - _variables.Add(variable.Name, (bufferSize, variable)); - - bufferSize += variable._sizeInBytes; - } - - _rawVariables = new uint8[bufferSize]; + _effect.SetTexture(name, texture); } - /** - * Binds the materials Shaders and Parameters to the given context. - */ - public void Bind(GraphicsContext context) + for(let (name, variable) in _variables) { - for(let (name, texture) in _textures) - { - _effect.SetTexture(name, texture); - } - - for(let (name, variable) in _variables) - { - variable.Variable.SetRawData(&RawPointer!(variable.Offset)); - } - - _effect.Bind(context); + variable.Variable.SetRawData(RawPointer!(variable.Offset)); } - /** @brief Sets a texture of the material. - * @param name The name of the texture to set. - * @param texture The texture to bind to the effect. - */ - public void SetTexture(String name, Texture texture) + _effect.ApplyChanges(); + _effect.Bind(); + } + + /** @brief Sets a texture of the material. + * @param name The name of the texture to set. + * @param texture The texture to bind to the effect. + */ + public void SetTexture(String name, AssetHandle texture) + { + if(_textures.TryGetValue(name, var entry)) { - if(_textures.TryGetValue(name, var entry)) + //entry?.ReleaseRef(); + _textures[name] = texture; + //texture?.AddRef(); + } + else + { + Log.EngineLogger.Assert(false); + } + } + + private mixin RawPointer(uint32 offset) + { + (T*)(&_rawVariables[offset]) + } + + [Inline] + private void SetVariable(String name, T value) where T : struct + { + Debug.Profiler.ProfileRendererFunction!(); + + if(_variables.TryGetValue(name, let entry)) + { + entry.Variable.EnsureTypeMatch(); + + *RawPointer!(entry.Offset) = value; + } + else + { + Log.EngineLogger.Assert(false, scope $"The effect doesn't contain a variable named \"{name}\""); + } + } + + public void SetVariable(String name, float value) => SetVariable(name, value); + public void SetVariable(String name, Vector2 value) => SetVariable(name, value); + public void SetVariable(String name, Vector3 value) => SetVariable(name, value); + public void SetVariable(String name, Vector4 value) => SetVariable(name, value); + + public void SetVariable(String name, int32 value) => SetVariable(name, value); + public void SetVariable(String name, Int2 value) => SetVariable(name, value); + public void SetVariable(String name, Int3 value) => SetVariable(name, value); + public void SetVariable(String name, Int4 value) => SetVariable(name, value); + + public void SetVariable(String name, uint32 value) => SetVariable(name, value); + + public void SetVariable(String name, Color value) => SetVariable(name, (ColorRGBA)value); + public void SetVariable(String name, ColorRGB value) => SetVariable(name, value); + public void SetVariable(String name, ColorRGBA value) => SetVariable(name, value); + + public void SetVariable(String name, Matrix3x3 value) + { + if(_variables.TryGetValue(name, let entry)) + { + entry.Variable.EnsureTypeMatch(); + + // TODO: I'm not sure how to handle Matrix3x3 + // It seems to be 44 Bytes (11 Floats) large. + Log.EngineLogger.AssertDebug(entry.Variable._sizeInBytes == 44, "Made wrong assumption about the size of float3x3 in a hlsl constant-buffer."); + +#unwarn + *RawPointer!(entry.Offset) = *(float[11]*)&Matrix4x3(value); + } + else + { + Log.EngineLogger.Assert(false, scope $"The effect doesn't contain a variable named \"{name}\""); + } + } + + public void SetVariable(String name, Matrix3x3[] values) + { + if(_variables.TryGetValue(name, let entry)) + { + entry.Variable.EnsureTypeMatch(); + + int count = Math.Min(values.Count, entry.Variable._elements); + + for(int i < count) { - entry?.ReleaseRef(); - _textures[name] = texture; - texture?.AddRef(); - } - else - { - Log.EngineLogger.Assert(false); + (RawPointer!(entry.Offset))[i] = Matrix4x3(values[i]); } } - - private mixin RawPointer(uint32 offset) + else { - *(T*)(&_rawVariables[offset]) + Log.EngineLogger.Assert(false, scope $"The effect doesn't contain a variable named \"{name}\""); } + } - [Inline] - private void SetVariable(String name, T value) where T : struct + public void SetVariable(String name, Matrix4x3 value) => SetVariable(name, value); + public void SetVariable(String name, Matrix value) => SetVariable(name, value); + + public void SetVariable(String name, Matrix[] values) + { + if(_variables.TryGetValue(name, let entry)) { - if(_variables.TryGetValue(name, let entry)) - { - entry.Variable.EnsureTypeMatch(); + entry.Variable.EnsureTypeMatch(); - RawPointer!(entry.Offset) = value; - } - else - { - Log.EngineLogger.Assert(false, scope $"The effect doesn't contain a variable named \"{name}\""); - } + Internal.MemCpy(RawPointer!(entry.Offset), values.Ptr, sizeof(Matrix) * Math.Min(values.Count, entry.Variable._elements)); } - - public void SetVariable(String name, float value) => SetVariable(name, value); - public void SetVariable(String name, Vector2 value) => SetVariable(name, value); - public void SetVariable(String name, Vector3 value) => SetVariable(name, value); - public void SetVariable(String name, Vector4 value) => SetVariable(name, value); - - public void SetVariable(String name, int32 value) => SetVariable(name, value); - public void SetVariable(String name, Int2 value) => SetVariable(name, value); - public void SetVariable(String name, Int3 value) => SetVariable(name, value); - public void SetVariable(String name, Int4 value) => SetVariable(name, value); - - public void SetVariable(String name, Color value) => SetVariable(name, value); - public void SetVariable(String name, ColorRGB value) => SetVariable(name, value); - public void SetVariable(String name, ColorRGBA value) => SetVariable(name, value); - - public void SetVariable(String name, Matrix3x3 value) + else { - if(_variables.TryGetValue(name, let entry)) - { - entry.Variable.EnsureTypeMatch(); - - RawPointer!(entry.Offset) = Matrix4x3(value); - } - else - { - Log.EngineLogger.Assert(false, scope $"The effect doesn't contain a variable named \"{name}\""); - } + Log.EngineLogger.Assert(false, scope $"The effect doesn't contain a variable named \"{name}\""); } - - public void SetVariable(String name, Matrix3x3[] values) + } + + // Supporeted types + // Float, Float2, Float3, Float4 + // Color, ColorRGB, ColorRGBA + // Int, Int2, Int3, Int4 + // UInt + // Matrix3x3, Matrix4x3, Matrix + + // TODO: Add missing variable types + // UInt2, UInt3, UInt4 + // Bool, Bool2, Bool3, Bool4 + // Half, Half2, Half3, Half4 + // Byte, Byte2, Byte3, Byte4 + + /** + * Sets the raw data of the variable. + * @param rawData The pointer to the raw data. If rawData is null the raw data will be set to zero. + */ + internal void SetRawData(uint32 offset, void* rawData, uint32 byteCount) + { + if(rawData != null) + Internal.MemCpy(&_rawVariables + offset, rawData, byteCount); + else + Internal.MemSet(&_rawVariables + offset, 0, byteCount); + } + + public void GetVariable(String name, out T value) where T : struct + { + Debug.Profiler.ProfileRendererFunction!(); + + if(_variables.TryGetValue(name, let entry)) { - if(_variables.TryGetValue(name, let entry)) - { - entry.Variable.EnsureTypeMatch(); - - int count = Math.Min(values.Count, entry.Variable._elements); - - for(int i < count) - { - (&RawPointer!(entry.Offset))[i] = Matrix4x3(values[i]); - } - } - else - { - Log.EngineLogger.Assert(false, scope $"The effect doesn't contain a variable named \"{name}\""); - } + entry.Variable.EnsureTypeMatch(); + + value = *RawPointer!(entry.Offset); } - - public void SetVariable(String name, Matrix4x3 value) => SetVariable(name, value); - public void SetVariable(String name, Matrix value) => SetVariable(name, value); - - public void SetVariable(String name, Matrix[] values) + else { - if(_variables.TryGetValue(name, let entry)) - { - entry.Variable.EnsureTypeMatch(); - - Internal.MemCpy(&RawPointer!(entry.Offset), values.Ptr, sizeof(Matrix) * Math.Min(values.Count, entry.Variable._elements)); - } - else - { - Log.EngineLogger.Assert(false, scope $"The effect doesn't contain a variable named \"{name}\""); - } + value = ?; + Log.EngineLogger.Assert(false, scope $"The effect doesn't contain a variable named \"{name}\""); } - - /** - * Sets the raw data of the variable. - * @param rawData The pointer to the raw data. If rawData is null the raw data will be set to zero. - */ - internal void SetRawData(uint32 offset, void* rawData, uint32 byteCount) - { - if(rawData != null) - Internal.MemCpy(&_rawVariables + offset, rawData, byteCount); - else - Internal.MemSet(&_rawVariables + offset, 0, byteCount); - } - - // public void Set(String name, VALUE)... - - // Float, Float2, Float3, Float4 - // Color, ColorRGB, ColorRGBA - // Matrix3x3, Matrix4x3, Matrix - // Int, Int2, Int3, Int4 - // UInt, UInt2, UInt3, UInt4 - // Bool, Bool2, Bool3, Bool4 - // Half, Half2, Half3, Half4 - // Byte, Byte2, Byte3, Byte4 } } diff --git a/GlitchyEngine/src/Renderer/MeshComponent.bf b/GlitchyEngine/src/Renderer/MeshComponent.bf index 9a33453..3558a57 100644 --- a/GlitchyEngine/src/Renderer/MeshComponent.bf +++ b/GlitchyEngine/src/Renderer/MeshComponent.bf @@ -1,27 +1,11 @@ using System; using GlitchyEngine.World; +using GlitchyEngine.Content; namespace GlitchyEngine.Renderer { - public struct MeshComponent : IDisposableComponent + public struct MeshComponent { - private GeometryBinding _mesh; - public GeometryBinding Mesh - { - [Inline] - get => _mesh; - set mut - { - if(_mesh == value) - return; - - SetReference!(_mesh, value); - } - } - - public void Dispose() mut - { - ReleaseRefAndNullify!(_mesh); - } + public AssetHandle Mesh = .Invalid; } } diff --git a/GlitchyEngine/src/Renderer/PixelShader.bf b/GlitchyEngine/src/Renderer/PixelShader.bf index cd0c971..7d222b5 100644 --- a/GlitchyEngine/src/Renderer/PixelShader.bf +++ b/GlitchyEngine/src/Renderer/PixelShader.bf @@ -1,11 +1,12 @@ using System; +using GlitchyEngine.Content; namespace GlitchyEngine.Renderer { public class PixelShader : Shader { [AllowAppend] - public this(String source, String entryPoint, ShaderDefine[] macros = null) - : base(source, entryPoint, macros) { } + public this(StringView code, StringView? fileName, String entryPoint, IContentManager contentManager, ShaderDefine[] macros = null) + : base(code, fileName, entryPoint, contentManager, macros) { } } } diff --git a/GlitchyEngine/src/Renderer/RenderCommand.bf b/GlitchyEngine/src/Renderer/RenderCommand.bf index bf4b234..82358a1 100644 --- a/GlitchyEngine/src/Renderer/RenderCommand.bf +++ b/GlitchyEngine/src/Renderer/RenderCommand.bf @@ -3,20 +3,17 @@ using GlitchyEngine.Math; namespace GlitchyEngine.Renderer { - /* - public enum DepthStencilClearFlag - { - None = 0, - Depth = 1, - Stencil = 2 - } - */ public enum ClearOptions { None = 0, Color = 1, Depth = 2, - Stencil = 4 + Stencil = 4, + + ColorDepth = Color | Depth, + ColorStencil = Color | Stencil, + DepthStencil = Depth | Stencil, + All = Color | Depth | Stencil } public static class RenderCommand @@ -37,7 +34,9 @@ namespace GlitchyEngine.Renderer _rendererAPI.Init(); } - [Inline] + // REPORT!!!!!!!!! + // Inline doesn't compile + //[Inline] public static void Clear(RenderTarget2D renderTarget, ColorRGBA color) { _rendererAPI.Clear(renderTarget, color); @@ -53,16 +52,31 @@ namespace GlitchyEngine.Renderer _rendererAPI.Clear(renderTarget, options, color, depth, stencil); } + public static void Clear(RenderTargetGroup renderTarget, ClearOptions options, ColorRGBA? color = null, float? depth = null, uint8? stencil = null) + { + _rendererAPI.Clear(renderTarget, options, color, depth, stencil); + } + public static void SetRenderTarget(RenderTarget2D renderTarget, int slot = 0, bool setDepthBuffer = false) { _rendererAPI.SetRenderTarget(renderTarget, slot, setDepthBuffer); } + public static void SetRenderTargetGroup(RenderTargetGroup renderTarget, bool setDepthBuffer = true) + { + _rendererAPI.SetRenderTargetGroup(renderTarget, setDepthBuffer); + } + public static void SetDepthStencilTarget(DepthStencilTarget target) { _rendererAPI.SetDepthStencilTarget(target); } + public static void UnbindRenderTargets() + { + _rendererAPI.UnbindRenderTargets(); + } + public static void BindRenderTargets() { _rendererAPI.BindRenderTargets(); @@ -82,7 +96,7 @@ namespace GlitchyEngine.Renderer { _rendererAPI.SetDepthStencilState(depthStencilState, stencilReference); } - + [Inline] public static void DrawIndexed(GeometryBinding geometry) { @@ -104,5 +118,25 @@ namespace GlitchyEngine.Renderer { SetViewport(.(left, top, width, height, minDepth, maxDepth)); } + + public static void UnbindTextures() + { + _rendererAPI.UnbindTextures(); + } + + public static void BindConstantBuffer(Buffer buffer, int slot, ShaderStage stage) + { + _rendererAPI.BindConstantBuffer(buffer, slot, stage); + } + + public static void BindVertexShader(VertexShader vertexShader) + { + _rendererAPI.BindVertexShader(vertexShader); + } + + public static void BindPixelShader(PixelShader pixelShader) + { + _rendererAPI.BindPixelShader(pixelShader); + } } } diff --git a/GlitchyEngine/src/Renderer/RenderTarget.bf b/GlitchyEngine/src/Renderer/RenderTarget.bf index 9b50c8c..12bdf0a 100644 --- a/GlitchyEngine/src/Renderer/RenderTarget.bf +++ b/GlitchyEngine/src/Renderer/RenderTarget.bf @@ -1,4 +1,7 @@ using GlitchyEngine.Core; +using System; +using System.Collections; +using GlitchyEngine.Math; namespace GlitchyEngine.Renderer { @@ -33,34 +36,23 @@ namespace GlitchyEngine.Renderer } } - public class RenderTarget2D : RefCounter + public class RenderTarget2D : Texture { private RenderTarget2DDescription _description; public RenderTarget2DDescription Description => _description; - public uint32 Width => _description.Width; - public uint32 Height => _description.Height; - + public override uint32 Width => _description.Width; + public override uint32 Height => _description.Height; + public override uint32 Depth => 1; + public override uint32 ArraySize => _description.ArraySize; + public override uint32 MipLevels => _description.MipLevels; + protected internal DepthStencilTarget _depthStenilTarget ~ _?.ReleaseRef(); // TODO: DepthStencilTarget is just a renderTarget public DepthStencilTarget DepthStencilTarget => _depthStenilTarget; - protected SamplerState _samplerState ~ _?.ReleaseRef(); - - public SamplerState SamplerState - { - get => _samplerState; - set - { - if(_samplerState == value) - return; - - SetReference!(_samplerState, value); - } - } - public this(RenderTarget2DDescription description) { Debug.Profiler.ProfileResourceFunction!(); @@ -79,5 +71,222 @@ namespace GlitchyEngine.Renderer public extern void Resize(uint32 width, uint32 height); protected extern void PlatformApplyChanges(); + + public override TextureViewBinding GetViewBinding() + { + return PlatformGetViewBinding(); + } + + protected extern TextureViewBinding PlatformGetViewBinding(); + + protected internal override void SneakySwappyTexture(Texture otherTexture) + { + Log.EngineLogger.AssertDebug(otherTexture is RenderTarget2D, "Swapping texture must be a RenderTarget2D!"); + + SamplerState = otherTexture.SamplerState; + + PlatformSneakySwappyTexture(otherTexture as RenderTarget2D); + } + + protected extern void PlatformSneakySwappyTexture(RenderTarget2D otherTexture); + } + + [AllowDuplicates] + public enum RenderTargetFormat : uint8 + { + /// Sets the most significant bit to 1. + const uint8 DepthMarker = 1 << 7; + + case None = 0; + + case R8_SInt; + case R32_UInt; + + case R8G8B8A8_UNorm; + case R8G8B8A8_SNorm; + + case R16G16B16A16_SNorm; + case R16G16B16A16_Float; + + case R32G32B32A32_Float; + + case D24_UNorm_S8_UInt = DepthMarker | 1; + + /// Default depth format + case Depth = D24_UNorm_S8_UInt; + + public bool IsDepth => HasFlag(DepthMarker); + } + + public enum ClearColor + { + /// Clears the render target to the default value (Zero). + case Default; + /// The render target will be cleared with a RGBA color. + case Color(ColorRGBA ClearColor); + /// The render target will be cleared with a UInt value. + /// @remarks Some platforms don't support clearing a UInt render target. + /// In these cases a draw call will be performed that draws a solid color into the target. + /// This can result in modified context state so it is recommended to rebind effects, etc. after clearing a uint rendertarget. + case UInt(uint32 ClearValue); + /// Only valid for depth buffers. + case DepthStencil(float Depth, uint8 Stencil); + + public static implicit operator ClearColor(ColorRGBA color) + { + return .Color(color); + } + } + + public struct TargetDescription + { + public RenderTargetFormat Format = .None; + + public bool IsSwapchainTarget = false; + + public bool IsShaderReadable = true; + + public ClearColor ClearColor = .Default; + + public SamplerStateDescription SamplerDescription = .(); + + public this() { } + + public this(RenderTargetFormat format, bool isSwapchainTarget = false, bool isShaderReadable = true, ClearColor clearColor = .Default, SamplerStateDescription samplerDescription = .()) + { + Format = format; + IsSwapchainTarget = isSwapchainTarget; + IsShaderReadable = isShaderReadable; + SamplerDescription = samplerDescription; + ClearColor = clearColor; + } + + public static implicit operator Self(RenderTargetFormat format) + { + return Self(format); + } + } + + public struct RenderTargetGroupDescription + { + public uint32 Width = 0, Height = 0, ArraySize = 1, MipLevels = 1; + + public uint32 Samples = 1; // TODO: SampleQuality? + + public Span ColorTargetDescriptions = null; + + public TargetDescription DepthTargetDescription = .(.None); + + // TODO: CpuAccess + + public this() { } + + public this(uint32 width, uint32 height, Span colorTargetDescriptions = null, TargetDescription depthTargetDescription = .()) + { + Width = width; + Height = height; + ColorTargetDescriptions = colorTargetDescriptions; + DepthTargetDescription = depthTargetDescription; + } + } + + public class RenderTargetGroup : RefCounter + { + internal RenderTargetGroupDescription _description; + + internal TargetDescription[] _colorTargetDescriptions ~ delete _; + internal SamplerState[] _colorSamplerStates ~ DeleteContainerAndReleaseItems!(_); + internal SamplerState _depthSamplerState ~ _?.ReleaseRef(); + + internal TargetDescription _depthTargetDescription; + + public uint32 Width => _description.Width; + public uint32 Height => _description.Height; + public uint32 ArraySize => _description.ArraySize; + public uint32 MipLevels => _description.MipLevels; + + public uint32 Samples => _description.Samples; + + public int TargetCount => _colorTargetDescriptions.Count + (_depthTargetDescription.Format.IsDepth ? 1 : 0); + public int ColorTargetCount => _colorTargetDescriptions.Count; + + [AllowAppend] + public this(RenderTargetGroupDescription description) + { + _description = description; + + Log.EngineLogger.AssertDebug(_description.Width != 0); + Log.EngineLogger.AssertDebug(_description.Height != 0); + + var colorTargets = description.ColorTargetDescriptions; + + if (!colorTargets.IsNull && !colorTargets.IsEmpty) + { + _colorTargetDescriptions = new TargetDescription[colorTargets.Length]; + _colorSamplerStates = new SamplerState[colorTargets.Length]; + + bool swapchainTargetBound = false; + + for (int i < colorTargets.Length) + { + Log.EngineLogger.AssertDebug(!colorTargets[i].Format.IsDepth, "Cannot use depth format as color target."); + + _colorTargetDescriptions[i] = colorTargets[i]; + + if (colorTargets[i].IsSwapchainTarget) + { + Log.EngineLogger.AssertDebug(!swapchainTargetBound, "Cannot bind swapchaintarget multiple times."); + + swapchainTargetBound = true; + } + + _colorSamplerStates[i] = SamplerStateManager.GetSampler(_colorTargetDescriptions[i].SamplerDescription); + + if (_colorTargetDescriptions[i].ClearColor case .Default) + _colorTargetDescriptions[i].ClearColor = .Color(.Black); + } + } + + _depthTargetDescription = description.DepthTargetDescription; + + if (_depthTargetDescription.Format != .None) + { + Log.EngineLogger.AssertDebug(_depthTargetDescription.Format.IsDepth, "Depth target must have depth format."); + + if (_depthTargetDescription.ClearColor case .Default) + _depthTargetDescription.ClearColor = .DepthStencil(0.0f, 0); + + Log.EngineLogger.AssertDebug(_depthTargetDescription.ClearColor case .DepthStencil, "Clear color for depth stencil target must be of type DepthStencil."); + + _depthSamplerState = SamplerStateManager.GetSampler(_depthTargetDescription.SamplerDescription); + } + + ApplyChanges(); + } + + public extern void ApplyChanges(); + + public extern void Resize(uint32 width, uint32 height); + + /// -1 for Depthbuffer + public TextureViewBinding GetViewBinding(int index) + { + return PlatformGetViewBinding(index); + } + + protected extern TextureViewBinding PlatformGetViewBinding(int index); + + protected extern Result PlatformGetData(void* destination, uint32 elementSize, + uint32 x, uint32 y, uint32 width, uint32 height, int renderTarget, uint32 arraySlice, uint32 mipLevel); // mapType? + + public Result GetData(T* data, int renderTarget, uint32 left, uint32 top, uint32 width, uint32 height, uint32 arraySlice = 0, uint32 mipSlice = 0) + { + Log.EngineLogger.AssertDebug(left + width < Width); + Log.EngineLogger.AssertDebug(top + height < Height); + + return PlatformGetData(data, (.)sizeof(T), left, top, width, height, renderTarget, arraySlice, mipSlice); + } + + public extern void CopyTo(RenderTargetGroup destination, int dstTarget, Int2 dstTopLeft, Int2 size, Int2 srcTopLeft, int srcTarget); } } diff --git a/GlitchyEngine/src/Renderer/Renderer.bf b/GlitchyEngine/src/Renderer/Renderer.bf index e3f462d..7400de1 100644 --- a/GlitchyEngine/src/Renderer/Renderer.bf +++ b/GlitchyEngine/src/Renderer/Renderer.bf @@ -1,5 +1,8 @@ using GlitchyEngine.Math; using GlitchyEngine.World; +using System.Collections; +using System; +using GlitchyEngine.Content; namespace GlitchyEngine.Renderer { @@ -8,6 +11,9 @@ namespace GlitchyEngine.Renderer struct SceneConstants { public Matrix ViewProjection; + public Vector3 CameraPosition; + public RenderTargetGroup CameraTarget; + public RenderTargetGroup CompositionTarget; } struct ObjectConstants @@ -15,49 +21,119 @@ namespace GlitchyEngine.Renderer public Matrix Transform; } - static internal GraphicsContext _context ~ _?.ReleaseRef(); + class GBuffer + { + private uint32 _width; + private uint32 _height; + + public uint32 Width => _width; + public uint32 Height => _height; - //static Buffer _sceneConstants ~ _?.ReleaseRef(); + public Int2 Size => .(_width, _height); - //static Buffer _objectConstants ~ _?.ReleaseRef(); + public RenderTargetGroup Target ~ _?.ReleaseRef(); + + public void EnsureSize(uint32 width, uint32 height) + { + if (width <= _width && height <= _height) + return; + + if (_width == 0 || _height == 0) + { + SamplerStateDescription desc = .(); + desc.MinFilter = .Point; + desc.MagFilter = .Point; + + RenderTargetGroupDescription targetDesc = .(width, height, + TargetDescription[]( + .(RenderTargetFormat.R8G8B8A8_UNorm){SamplerDescription = desc}, + .(RenderTargetFormat.R16G16B16A16_SNorm){SamplerDescription = desc}, + .(RenderTargetFormat.R16G16B16A16_SNorm){SamplerDescription = desc}, + .(RenderTargetFormat.R32G32B32A32_Float){SamplerDescription = desc}, + .(RenderTargetFormat.R8G8B8A8_UNorm){SamplerDescription = desc}, + .(RenderTargetFormat.R32_UInt){SamplerDescription = desc, ClearColor = .UInt(uint32.MaxValue)}, + ), + TargetDescription(.D24_UNorm_S8_UInt){ + SamplerDescription = desc, + ClearColor = .DepthStencil(1.0f, 0) + }); + Target = new RenderTargetGroup(targetDesc); + } + + _width = width; + _height = height; + + Target.Resize(_width, _height); + } + + public void Bind() + { + RenderCommand.SetRenderTargetGroup(Target); + RenderCommand.BindRenderTargets(); + } + + public void Clear() + { + RenderCommand.Clear(Target, .ColorDepth); + } + } static SceneConstants _sceneConstants; - static Effect LineEffect ~ _?.ReleaseRef(); - static VertexBuffer LineVertices ~ _?.ReleaseRef(); - static GeometryBinding LineGeometry ~ _?.ReleaseRef(); + //static AssetHandle LineEffect; + static VertexBuffer LineVertices; + static GeometryBinding LineGeometry; - public static void Init(GraphicsContext context, EffectLibrary effectLibrary) + static GBuffer _gBuffer; + static AssetHandle TestFullscreenEffect; + static AssetHandle s_tonemappingEffect; + + static BlendState _gBufferBlend; + static BlendState _lightBlend; + static DepthStencilState _fullscreenDepthState; + + static Buffer _sceneBuffer; + static Buffer _objectBuffer; + + [Ordered, CRepr] + struct ObjectConstantsBuffer + { + public Matrix Transform; + public Matrix4x3 Transform_InvT; + + public uint32 EntityId; + private Vector3 _padding; + } + + public static void Init() { Debug.Profiler.ProfileFunction!(); - _context = context..AddRef(); - /* - _sceneConstants = new Buffer(.(0, .Constant, .Dynamic, .Write)); - _sceneConstants.Update(); - - _objectConstants = new Buffer(.(0, .Constant, .Dynamic, .Write)); - _objectConstants.Update(); - */ - RenderCommand.Init(); Renderer2D.Init(); + FullscreenQuad.Init(); - InitLineRenderer(effectLibrary); + InitLineRenderer(); + InitDeferredRenderer(); } public static void Deinit() { Debug.Profiler.ProfileFunction!(); + DeinitDeferredRenderer(); + + DeinitLineRenderer(); + + FullscreenQuad.Deinit(); Renderer2D.Deinit(); } - static void InitLineRenderer(EffectLibrary effectLibrary) + static void InitLineRenderer() { Debug.Profiler.ProfileFunction!(); - LineEffect = effectLibrary.Load("content\\Shaders\\lineShader.hlsl"); + //LineEffect = Content.LoadAsset("Shaders\\lineShader.hlsl"); LineGeometry = new GeometryBinding(); LineGeometry.SetPrimitiveTopology(.LineList); @@ -75,37 +151,251 @@ namespace GlitchyEngine.Renderer VertexElement[] vertexElements = new VertexElement[1]; vertexElements[0] = .(.R32G32B32_Float, "POSITION"); - VertexLayout layout = new VertexLayout(vertexElements, true, LineEffect.VertexShader); + VertexLayout layout = new VertexLayout(vertexElements, true); LineGeometry.SetVertexLayout(layout..ReleaseRefNoDelete()); } - // TODO - /*public static void BeginScene(EcsWorld world, EcsEntity cameraEntity) + static void DeinitLineRenderer() { - Debug.Profiler.ProfileRendererFunction!(); + LineVertices.ReleaseRef(); + LineGeometry.ReleaseRef(); + } - var camera = world.GetComponent(cameraEntity); - var transform = world.GetComponent(cameraEntity); + static void InitDeferredRenderer() + { + TestFullscreenEffect = Content.LoadAsset("Shaders\\simpleLight.hlsl"); + s_tonemappingEffect = Content.LoadAsset("Shaders\\SimpleTonemapping.hlsl"); - var trans = transform.WorldTransform; - var view = trans.Invert(); - var proj = camera.Projection; + _gBuffer = new GBuffer(); + BlendStateDescription gBufferBlendDesc = .Default; + _gBufferBlend = new BlendState(gBufferBlendDesc); - _sceneConstants.ViewProjection = proj * view; - }*/ + BlendStateDescription lightBlendDesc = .Default; + lightBlendDesc.RenderTarget[0] = .(){ + BlendEnable = true, + SourceBlend = .One, + DestinationBlend = .One, + BlendOperation = .Add, + SourceBlendAlpha = .One, + DestinationBlendAlpha = .Zero, + BlendOperationAlpha = .Add, + RenderTargetWriteMask = .All + }; + _lightBlend = new BlendState(lightBlendDesc); + DepthStencilStateDescription dsDesc = .Default; + dsDesc.DepthEnabled = false; + _fullscreenDepthState = new DepthStencilState(dsDesc); + + BufferDescription sceneBufferDesc = .(sizeof(Matrix), .Constant, .Dynamic, .Write); + _sceneBuffer = new Buffer(sceneBufferDesc); + + BufferDescription objectBufferDesc = .(sizeof(ObjectConstantsBuffer), .Constant, .Dynamic, .Write); + _objectBuffer = new Buffer(objectBufferDesc); + } + + static void DeinitDeferredRenderer() + { + _objectBuffer.ReleaseRef(); + _sceneBuffer.ReleaseRef(); + + _fullscreenDepthState.ReleaseRef(); + _lightBlend.ReleaseRef(); + _gBufferBlend.ReleaseRef(); + delete _gBuffer; + } + + // [Obsolete("", false)] public static void BeginScene(OldCamera camera) { Debug.Profiler.ProfileRendererFunction!(); _sceneConstants.ViewProjection = camera.ViewProjection; - //_sceneConstants.Data.ViewProjection = camera.ViewProjection; - //_sceneConstants.Update(); + } + + public static void BeginScene(Camera camera, Matrix transform, RenderTargetGroup renderTarget, RenderTargetGroup finalTarget) + { + Debug.Profiler.ProfileRendererFunction!(); + + Matrix viewProjection = camera.Projection * Matrix.Invert(transform); + _sceneConstants.ViewProjection = viewProjection; + _sceneConstants.CameraPosition = transform.Translation; + _sceneConstants.CameraTarget = renderTarget; + _sceneConstants.CompositionTarget = finalTarget; + + _sceneBuffer.SetData(viewProjection , 0, .WriteDiscard); + } + + public static void BeginScene(EditorCamera camera, RenderTargetGroup finalTarget) + { + Debug.Profiler.ProfileRendererFunction!(); + + Matrix viewProjection = camera.Projection * camera.View; + _sceneConstants.ViewProjection = viewProjection; + _sceneConstants.CameraPosition = camera.Position; + _sceneConstants.CameraTarget = camera.RenderTarget; + _sceneConstants.CompositionTarget = finalTarget; + + _sceneBuffer.SetData(viewProjection, 0, .WriteDiscard); + } + + public static int SortMeshes(SubmittedMesh left, SubmittedMesh right) + { + // TODO: Once Material "inheritance" is ready we could perhaps check how similar materials are (e.g. shared textures/variables/etc...) + // Similar thing could be done for Meshes. Both would probably require a different way to sort however?... + + int cmp = (int)Internal.UnsafeCastToPtr(left.Material) <=> (int)Internal.UnsafeCastToPtr(right.Material); + + // Material equal: Sort by Mesh + if (cmp == 0) + { + cmp = (int)Internal.UnsafeCastToPtr(left.Mesh) <=> (int)Internal.UnsafeCastToPtr(right.Mesh); + + // Material and Mesh equal: sort by distance + if (cmp == 0) + { + float distLeftSq = Vector3.DistanceSquared(_sceneConstants.CameraPosition, left.Transform.Translation); + float distRightSq = Vector3.DistanceSquared(_sceneConstants.CameraPosition, right.Transform.Translation); + + // Whether or not the values are squared doesn't affect the order (because square(root) is a monotonic function) + cmp = distLeftSq <=> distRightSq; + } + } + + return 0; } public static void EndScene() { Debug.Profiler.ProfileRendererFunction!(); + + { + Debug.Profiler.ProfileRendererScope!("Sort Meshes"); + + _queue.Sort(scope => SortMeshes); + } + // Deferred renderer: + + // TODO: foreach light: draw shadow map + + // foreach camera: + // { + + { + Debug.Profiler.ProfileRendererScope!("Draw GBuffer"); + + _gBuffer.EnsureSize(_sceneConstants.CameraTarget.Width, _sceneConstants.CameraTarget.Height); + _gBuffer.Clear(); + RenderCommand.UnbindRenderTargets(); + _gBuffer.Bind(); + + RenderCommand.SetViewport(0, 0, _sceneConstants.CameraTarget.Width, _sceneConstants.CameraTarget.Height); + + RenderCommand.SetBlendState(_gBufferBlend); + + + for (SubmittedMesh entry in _queue) + { + Debug.Profiler.ProfileRendererScope!("Draw Mesh"); + + { + Debug.Profiler.ProfileRendererScope!("Update object buffer"); + + ObjectConstantsBuffer objectData = ?; + objectData.Transform = entry.Transform; + + Matrix4x3 mat = Matrix4x3((Matrix3x3)(entry.Transform).Invert().Transpose()); + objectData.Transform_InvT = mat; + + objectData.EntityId = entry.EntityId; + + _objectBuffer.SetData(objectData, 0, .WriteDiscard); + } + + entry.Material.Bind(); + + RenderCommand.BindConstantBuffer(_sceneBuffer, 0, .All); + RenderCommand.BindConstantBuffer(_objectBuffer, 1, .All); + + entry.Mesh.Bind(); + RenderCommand.DrawIndexed(entry.Mesh); + } + } + + { + Debug.Profiler.ProfileRendererScope!("Draw Lights"); + + RenderCommand.UnbindRenderTargets(); + RenderCommand.SetRenderTargetGroup(_sceneConstants.CameraTarget); + RenderCommand.BindRenderTargets(); + + RenderCommand.Clear(_sceneConstants.CameraTarget, .ColorDepth); + + RenderCommand.SetBlendState(_lightBlend); + RenderCommand.SetDepthStencilState(_fullscreenDepthState); + + // Scaling to make sure that only the part of the gbuffer that we actually used gets rendered into the viewport. + Vector2 scaling = Vector2(_sceneConstants.CameraTarget.Width, _sceneConstants.CameraTarget.Height) / (Vector2)_gBuffer.Size; + + for (SubmittedLight light in _lights) + { + Debug.Profiler.ProfileRendererScope!("Draw Light"); + + Vector3 lightDir = -light.Transform.Forward; + + Effect fsEffect = TestFullscreenEffect.Get(); + fsEffect.SetTexture("GBuffer_Albedo", _gBuffer.Target, 0); + fsEffect.SetTexture("GBuffer_Normal", _gBuffer.Target, 1); + fsEffect.SetTexture("GBuffer_Tangent", _gBuffer.Target, 2); + fsEffect.SetTexture("GBuffer_Position", _gBuffer.Target, 3); + fsEffect.SetTexture("GBuffer_Material", _gBuffer.Target, 4); + + fsEffect.Variables["LightColor"].SetData(light.Light.Color); + fsEffect.Variables["Illuminance"].SetData(light.Light.Illuminance); + fsEffect.Variables["LightDir"].SetData(lightDir); + + fsEffect.Variables["CameraPos"].SetData(_sceneConstants.CameraPosition); + + fsEffect.Variables["Scaling"].SetData(scaling); + + fsEffect.ApplyChanges(); + fsEffect.Bind(); + + //RenderCommand.BindEffect(TestFullscreenEffect); + + FullscreenQuad.Draw(); + } + + _lights.Clear(); + + // Copy EntityIDs to compositionTarget + _gBuffer.Target.CopyTo(_sceneConstants.CompositionTarget, 1, Int2.Zero, Int2(_sceneConstants.CameraTarget.Width, _sceneConstants.CameraTarget.Height), Int2.Zero, 5); + + RenderCommand.SetBlendState(_gBufferBlend); + + RenderCommand.UnbindRenderTargets(); + RenderCommand.BindRenderTargets(); + RenderCommand.SetRenderTargetGroup(_sceneConstants.CompositionTarget, true); + + Effect toneMappingFx = s_tonemappingEffect.Get(); + // TODO: Postprocessing effects + toneMappingFx.SetTexture("CameraTarget", _sceneConstants.CameraTarget, 0); + toneMappingFx.ApplyChanges(); + toneMappingFx.Bind(); + + RenderCommand.BindRenderTargets(); + //RenderCommand.BindEffect(s_tonemappingEffect); + + FullscreenQuad.Draw(); + + RenderCommand.UnbindTextures(); + } + + // TODO: Draw lights to camera target + // } + + // Queue entries increase the reference counter of the mesh/material thus we have to dispose of them. + ClearAndDisposeItems!(_queue); } public static void Submit(GeometryBinding geometry, Effect effect, Matrix transform = .Identity) @@ -123,24 +413,73 @@ namespace GlitchyEngine.Renderer effect.Variables["ViewProjection"].SetData(_sceneConstants.ViewProjection); effect.Variables["Transform"].SetData(transform); - - effect.Bind(_context); + + effect.ApplyChanges(); + effect.Bind(); geometry.Bind(); RenderCommand.DrawIndexed(geometry); } + struct SubmittedMesh : IDisposable + { + public GeometryBinding Mesh; + public Material Material; + public Matrix Transform; + public uint32 EntityId; + + public this(GeometryBinding mesh, Material material, Matrix transform, uint32 id) + { + Mesh = mesh..AddRef(); + Material = material..AddRef(); + Transform = transform; + EntityId = id; + } + + public void Dispose() + { + Mesh.ReleaseRef(); + Material.ReleaseRef(); + } + } + + struct SubmittedLight + { + public SceneLight Light; + public Matrix Transform; + + public this(SceneLight light, Matrix transform) + { + Light = light; + Transform = transform; + } + } + + private static List _queue = new .(10000) ~ DeleteContainerAndDisposeItems!(_); + private static List _lights = new .(100) ~ delete _; + public static void Submit(GeometryBinding geometry, Material material, Matrix transform = .Identity) { Debug.Profiler.ProfileRendererFunction!(); - material.SetVariable("ViewProjection", _sceneConstants.ViewProjection); - material.SetVariable("Transform", transform); + _queue.Add(SubmittedMesh(geometry, material, transform, uint32.MaxValue)); + } - material.Bind(_context); + public static void Submit(GeometryBinding geometry, Material material, EcsEntity entity, Matrix transform = .Identity) + { + Debug.Profiler.ProfileRendererFunction!(); - geometry.Bind(); - RenderCommand.DrawIndexed(geometry); + if (geometry == null || material == null) + return; + + _queue.Add(SubmittedMesh(geometry, material, transform, entity.[Friend]Index)); + } + + public static void Submit(SceneLight light, Matrix transform = .Identity) + { + Debug.Profiler.ProfileRendererFunction!(); + + _lights.Add(SubmittedLight(light, transform)); } /** @brief Draws a line. @@ -148,9 +487,10 @@ namespace GlitchyEngine.Renderer * @param end The end point of the line. * @param color The color of the line. */ - public static void DrawLine(Vector3 start, Vector3 end, Color color) + public static void DrawLine(Vector3 start, Vector3 end, ColorRGBA color) { - DrawLine(Vector4(start, 1.0f), Vector4(end, 1.0f), color, .Identity); + Renderer2D.DrawLine(start, end, color); + //DrawLine(Vector4(start, 1.0f), Vector4(end, 1.0f), (ColorRGBA)color, .Identity); } /** @brief Draws a line. @@ -159,9 +499,9 @@ namespace GlitchyEngine.Renderer * @param color The color of the line. * @param transform A transform matrix transforming the line. */ - public static void DrawLine(Vector3 start, Vector3 end, Color color, Matrix transform) + public static void DrawLine(Vector3 start, Vector3 end, ColorRGBA color, Matrix transform) { - DrawLine(Vector4(start, 1.0f), Vector4(end, 1.0f), color, transform); + Renderer2D.DrawLine(transform * Vector4(start, 1.0f), transform * Vector4(end, 1.0f), color); } /** @brief Draws a ray. @@ -169,9 +509,9 @@ namespace GlitchyEngine.Renderer * @param direction The direction of the ray. * @param color The color of the ray. */ - public static void DrawRay(Vector3 start, Vector3 direction, Color color) + public static void DrawRay(Vector3 start, Vector3 direction, ColorRGBA color) { - DrawLine(Vector4(start, 1.0f), Vector4(direction, 0.0f), color, .Identity); + Renderer2D.DrawRay(start, direction, color); } /** @brief Draws a ray. @@ -180,29 +520,9 @@ namespace GlitchyEngine.Renderer * @param color The color of the ray. * @param transform A transform matrix transforming the ray. */ - public static void DrawRay(Vector3 start, Vector3 direction, Color color, Matrix transform) + public static void DrawRay(Vector3 start, Vector3 direction, ColorRGBA color, Matrix transform) { - DrawLine(Vector4(start, 1.0f), Vector4(direction, 0.0f), color, transform); - } - - /** @brief Draws a line. - * @param start The start point of the line. - * @param end The end point of the line. - * @param color The color of the line. - * @param transform A transform matrix transforming the line. - */ - public static void DrawLine(Vector4 start, Vector4 end, Color color, Matrix transform) - { - Debug.Profiler.ProfileRendererFunction!(); - - LineVertices.SetData(Vector4[2](start, end), 0, .WriteDiscard); - LineEffect.Variables["ViewProjection"].SetData(_sceneConstants.ViewProjection * transform); - LineEffect.Variables["Color"].SetData(color); - - LineEffect.Bind(_context); - - LineGeometry.Bind(); - RenderCommand.DrawIndexed(LineGeometry); + Renderer2D.DrawLine(transform * Vector4(start, 1.0f), transform * Vector4(direction, 0.0f), color); } } } diff --git a/GlitchyEngine/src/Renderer/Renderer2D.bf b/GlitchyEngine/src/Renderer/Renderer2D.bf index 989611d..e521991 100644 --- a/GlitchyEngine/src/Renderer/Renderer2D.bf +++ b/GlitchyEngine/src/Renderer/Renderer2D.bf @@ -42,29 +42,48 @@ namespace GlitchyEngine.Renderer ); } } + + [CRepr] + struct LineBatchVertex + { + public Vector4 Position; + public ColorRGBA Color; + + public uint32 EntityId; + + public this(Vector4 position, ColorRGBA color, uint32 entityId) + { + Position = position; + Color = color; + EntityId = entityId; + } + } [CRepr] - struct BatchVertex + struct QuadBatchVertex { public Matrix Transform; public ColorRGBA Color; public Vector4 UVTransform; - public this(Matrix transform, ColorRGBA color, Vector4 uvTransform) + public uint32 EntityId; + + public this(Matrix transform, ColorRGBA color, Vector4 uvTransform, uint32 entityId) { Transform = transform; Color = color; UVTransform = uvTransform; + EntityId = entityId; } } [CRepr] - struct CircleBatchVertex : BatchVertex + struct CircleBatchVertex : QuadBatchVertex { public float InnerRadius; - public this(Matrix transform, ColorRGBA color, Vector4 uvTransform, float innerRadius) - : base(transform, color, uvTransform) + public this(Matrix transform, ColorRGBA color, Vector4 uvTransform, float innerRadius, uint32 entityId) + : base(transform, color, uvTransform, entityId) { InnerRadius = innerRadius; } @@ -90,15 +109,17 @@ namespace GlitchyEngine.Renderer */ FrontToBack } + + struct QueueLine: this(Vector4 Start, Vector4 End, ColorRGBA Color, float Depth, uint32 entityId = uint32.MaxValue) { } - struct QueueQuad: this(Matrix Transform, ColorRGBA Color, Texture2D Texture, float Depth, Vector4 uvTransform) { } + struct QueueQuad: this(Matrix Transform, ColorRGBA Color, Texture Texture, float Depth, Vector4 uvTransform, uint32 entityId = uint32.MaxValue) { } struct QueueCircle : QueueQuad { public float InnerRadius; - public this(Matrix Transform, ColorRGBA Color, Texture2D Texture, float Depth, Vector4 uvTransform, float innerRadius) - : base(Transform, Color, Texture, Depth, uvTransform) + public this(Matrix Transform, ColorRGBA Color, Texture Texture, float Depth, Vector4 uvTransform, float innerRadius, uint32 entityId = uint32.MaxValue) + : base(Transform, Color, Texture, Depth, uvTransform, entityId) { InnerRadius = innerRadius; } @@ -109,32 +130,40 @@ namespace GlitchyEngine.Renderer private static bool s_sceneRunning; #endif - private static Effect s_batchEffect; + private static Effect s_quadBatchEffect; private static Effect s_circleBatchEffect; - + private static Effect s_lineBatchEffect; + private static GeometryBinding s_quadGeometry; private static Texture2D s_whiteTexture; private static GeometryBinding s_quadBatchBinding; private static GeometryBinding s_circleBatchBinding; + private static GeometryBinding s_lineBatchBinding; private static VertexBuffer s_quadInstanceBuffer; private static VertexBuffer s_circleInstanceBuffer; + private static VertexBuffer s_lineInstanceBuffer; private static uint32 s_maxInstancesPerBatch = 8192; - private static BatchVertex[] s_rawQuadInstances; + private static QuadBatchVertex[] s_rawQuadInstances; private static CircleBatchVertex[] s_rawCircleInstances; - private static uint32 s_setInstances = 0; + private static LineBatchVertex[] s_rawLineVertices; + private static uint32 s_setQuadInstances = 0; + private static uint32 s_setCircleInstances = 0; + private static uint32 s_setLineInstances = 0; private static List s_QuadinstanceQueue; private static List s_circleInstanceQueue; + private static List s_lineInstanceQueue; private static DrawOrder s_drawOrder; /// The effect that is currently used to draw the sprites. - private static Effect s_currentEffect; + private static Effect s_currentQuadEffect; private static Effect s_currentCircleEffect; + private static Effect s_currentLineEffect; public static uint32 MaxInstancesPerBatch { @@ -154,8 +183,9 @@ namespace GlitchyEngine.Renderer { Debug.Profiler.ProfileFunction!(); - s_batchEffect = new Effect("content\\Shaders\\spritebatch.hlsl"); + s_quadBatchEffect = new Effect("content\\Shaders\\spritebatch.hlsl"); s_circleBatchEffect = new Effect("content\\Shaders\\circlebatch.hlsl"); + s_lineBatchEffect = new Effect("content\\Shaders\\linebatch.hlsl"); } private static void InitGeometry() @@ -184,7 +214,7 @@ namespace GlitchyEngine.Renderer 0, 1, 2, 2, 3, 0 ); - + quadIndices.SetData(indices); s_quadGeometry.SetIndexBuffer(quadIndices); } @@ -205,13 +235,14 @@ namespace GlitchyEngine.Renderer VertexElement(.R32G32B32A32_Float, "TRANSFORM", false, 2, 1, (.)-1, .PerInstanceData, 1), VertexElement(.R32G32B32A32_Float, "TRANSFORM", false, 3, 1, (.)-1, .PerInstanceData, 1), VertexElement(.R32G32B32A32_Float, "COLOR", false, 0, 1, (.)-1, .PerInstanceData, 1), - VertexElement(.R32G32B32A32_Float, "TEXCOORD", false, 1, 1, (.)-1, .PerInstanceData, 1) + VertexElement(.R32G32B32A32_Float, "TEXCOORD", false, 1, 1, (.)-1, .PerInstanceData, 1), + VertexElement(.R32_UInt, "ENTITYID", false, 0, 1, (.)-1, .PerInstanceData, 1) ); s_quadBatchBinding = new GeometryBinding(); s_quadBatchBinding.SetPrimitiveTopology(.TriangleList); - using (var quadBatchLayout = new VertexLayout(vertexElements, true, s_batchEffect.VertexShader)) + using (var quadBatchLayout = new VertexLayout(vertexElements, true)) { s_quadBatchBinding.SetVertexLayout(quadBatchLayout); } @@ -232,13 +263,14 @@ namespace GlitchyEngine.Renderer VertexElement(.R32G32B32A32_Float, "TRANSFORM", false, 3, 1, (.)-1, .PerInstanceData, 1), VertexElement(.R32G32B32A32_Float, "COLOR", false, 0, 1, (.)-1, .PerInstanceData, 1), VertexElement(.R32G32B32A32_Float, "TEXCOORD", false, 1, 1, (.)-1, .PerInstanceData, 1), + VertexElement(.R32_UInt, "ENTITYID", false, 0, 1, (.)-1, .PerInstanceData, 1), VertexElement(.R32_Float, "TEXCOORD", false, 2, 1, (.)-1, .PerInstanceData, 1) ); s_circleBatchBinding = new GeometryBinding(); s_circleBatchBinding.SetPrimitiveTopology(.TriangleList); - using (var circleBatchLayout = new VertexLayout(vertexElements, true, s_circleBatchEffect.VertexShader)) + using (var circleBatchLayout = new VertexLayout(vertexElements, true)) { s_circleBatchBinding.SetVertexLayout(circleBatchLayout); } @@ -247,6 +279,23 @@ namespace GlitchyEngine.Renderer s_circleBatchBinding.SetIndexBuffer(s_quadGeometry.GetIndexBuffer(), 0); } + // Line + { + VertexElement[] vertexElements = new .( + VertexElement(.R32G32B32A32_Float, "POSITION", false, 0, 0, 0, .PerVertexData, 0), + VertexElement(.R32G32B32A32_Float, "COLOR", false, 0, 0, (.)-1, .PerVertexData, 0), + VertexElement(.R32_UInt, "ENTITYID", false, 0, 0, (.)-1, .PerVertexData, 0) + ); + + s_lineBatchBinding = new GeometryBinding(); + s_lineBatchBinding.SetPrimitiveTopology(.LineList); + + using (var lineBatchLayout = new VertexLayout(vertexElements, true)) + { + s_lineBatchBinding.SetVertexLayout(lineBatchLayout); + } + } + ApplyInstanceCount(); } @@ -257,7 +306,7 @@ namespace GlitchyEngine.Renderer // Quads { - VertexBuffer quadInstanceBuffer = new VertexBuffer(typeof(BatchVertex), s_maxInstancesPerBatch, .Dynamic, .Write); + VertexBuffer quadInstanceBuffer = new VertexBuffer(typeof(QuadBatchVertex), s_maxInstancesPerBatch, .Dynamic, .Write); quadInstanceBuffer.SetData(0); s_quadInstanceBuffer?.ReleaseRef(); @@ -266,7 +315,7 @@ namespace GlitchyEngine.Renderer delete s_rawQuadInstances; delete s_QuadinstanceQueue; - s_rawQuadInstances = new BatchVertex[s_maxInstancesPerBatch]; + s_rawQuadInstances = new QuadBatchVertex[s_maxInstancesPerBatch]; s_QuadinstanceQueue = new List(s_maxInstancesPerBatch); } @@ -285,6 +334,21 @@ namespace GlitchyEngine.Renderer s_rawCircleInstances = new CircleBatchVertex[s_maxInstancesPerBatch]; s_circleInstanceQueue = new List(s_maxInstancesPerBatch); } + + // Lines + { + VertexBuffer lineInstanceBuffer = new VertexBuffer(typeof(LineBatchVertex), s_maxInstancesPerBatch, .Dynamic, .Write); + lineInstanceBuffer.SetData(0); + + s_lineInstanceBuffer?.ReleaseRef(); + s_lineInstanceBuffer = lineInstanceBuffer; + s_lineBatchBinding.SetVertexBufferSlot(s_lineInstanceBuffer, 0); + + delete s_rawLineVertices; + delete s_lineInstanceQueue; + s_rawLineVertices = new LineBatchVertex[s_maxInstancesPerBatch]; + s_lineInstanceQueue = new List(s_maxInstancesPerBatch); + } } private static void InitWhitetexture() @@ -313,9 +377,32 @@ namespace GlitchyEngine.Renderer sampler.ReleaseRef(); } + private static BlendState s_opaqueBlendState; + private static BlendState s_transparentBlendState; + + private static void InitStates() + { + s_opaqueBlendState = new BlendState(.Default); + + // TODO: alpha is a bitch. + // This is for premultiplied alpha!!!! However the engine has basically no support for that >:[ + BlendStateDescription transparentDesc = .Default; + transparentDesc.IndependentBlendEnable = true; + transparentDesc.RenderTarget[0] = .(true, + .One, .InvertedSourceAlpha, .Add, + .One, .One, .Add, .All); + + s_transparentBlendState = new BlendState(transparentDesc); + } + public static void Init() { Debug.Profiler.ProfileFunction!(); +#if DEBUG + Log.EngineLogger.AssertDebug(!s_initialized, "Renderer2D is already initialized."); +#endif + + InitStates(); InitEffect(); InitGeometry(); @@ -333,13 +420,14 @@ namespace GlitchyEngine.Renderer { Debug.Profiler.ProfileFunction!(); #if DEBUG - Log.EngineLogger.AssertDebug(s_initialized, "Renderer2D was not initialized."); + Log.EngineLogger.AssertDebug(s_initialized, "Renderer2D is not initialized."); #endif FontRenderer.Deinit(); - s_batchEffect.ReleaseRef(); + s_quadBatchEffect.ReleaseRef(); s_circleBatchEffect.ReleaseRef(); + s_lineBatchEffect.ReleaseRef(); s_quadGeometry.ReleaseRef(); @@ -347,16 +435,24 @@ namespace GlitchyEngine.Renderer s_quadBatchBinding.ReleaseRef(); s_circleBatchBinding.ReleaseRef(); + s_lineBatchBinding.ReleaseRef(); s_quadInstanceBuffer.ReleaseRef(); s_circleInstanceBuffer.ReleaseRef(); + s_lineInstanceBuffer.ReleaseRef(); delete s_rawQuadInstances; delete s_rawCircleInstances; + delete s_rawLineVertices; delete s_QuadinstanceQueue; delete s_circleInstanceQueue; + delete s_lineInstanceQueue; - s_currentEffect?.ReleaseRef(); + s_currentQuadEffect?.ReleaseRef(); s_currentCircleEffect?.ReleaseRef(); + s_currentLineEffect?.ReleaseRef(); + + s_opaqueBlendState.ReleaseRef(); + s_transparentBlendState.ReleaseRef(); #if DEBUG s_initialized = false; @@ -374,14 +470,14 @@ namespace GlitchyEngine.Renderer //s_textureColorEffect.Bind(Renderer._context); - s_currentEffect?.ReleaseRef(); + s_currentQuadEffect?.ReleaseRef(); if(effect != null) { - s_currentEffect = effect..AddRef(); + s_currentQuadEffect = effect..AddRef(); } else { - s_currentEffect = s_batchEffect..AddRef(); + s_currentQuadEffect = s_quadBatchEffect..AddRef(); } s_currentCircleEffect?.ReleaseRef(); @@ -394,7 +490,7 @@ namespace GlitchyEngine.Renderer s_currentCircleEffect = s_circleBatchEffect..AddRef(); } - s_currentEffect.Variables["ViewProjection"].SetData(camera.ViewProjection); + s_currentQuadEffect.Variables["ViewProjection"].SetData(camera.ViewProjection); s_currentCircleEffect.Variables["ViewProjection"].SetData(camera.ViewProjection); s_drawOrder = drawOrder; @@ -414,14 +510,14 @@ namespace GlitchyEngine.Renderer //s_textureColorEffect.Bind(Renderer._context); - s_currentEffect?.ReleaseRef(); + s_currentQuadEffect?.ReleaseRef(); if(effect != null) { - s_currentEffect = effect..AddRef(); + s_currentQuadEffect = effect..AddRef(); } else { - s_currentEffect = s_batchEffect..AddRef(); + s_currentQuadEffect = s_quadBatchEffect..AddRef(); } s_currentCircleEffect?.ReleaseRef(); @@ -433,11 +529,75 @@ namespace GlitchyEngine.Renderer { s_currentCircleEffect = s_circleBatchEffect..AddRef(); } + + s_currentLineEffect?.ReleaseRef(); + /*if(circleEffect != null) + { + s_currentLineEffect = effect..AddRef(); + } + else + {*/ + s_currentLineEffect = s_lineBatchEffect..AddRef(); + //} Matrix viewProjection = camera.Projection * Matrix.Invert(transform); - s_currentEffect.Variables["ViewProjection"].SetData(viewProjection); + s_currentQuadEffect.Variables["ViewProjection"].SetData(viewProjection); s_currentCircleEffect.Variables["ViewProjection"].SetData(viewProjection); + s_currentLineEffect.Variables["ViewProjection"].SetData(viewProjection); + + s_drawOrder = drawOrder; + +#if DEBUG + s_sceneRunning = true; +#endif + } + + public static void BeginScene(EditorCamera camera, DrawOrder drawOrder = .SortByTexture, Effect effect = null, Effect circleEffect = null) + { + Debug.Profiler.ProfileRendererFunction!(); +#if DEBUG + Log.EngineLogger.AssertDebug(s_initialized, "Renderer2D was not initialized."); + Log.EngineLogger.AssertDebug(!s_sceneRunning, "You have to call EndScene before you can make another call to BeginScene."); +#endif + + //s_textureColorEffect.Bind(Renderer._context); + + s_currentQuadEffect?.ReleaseRef(); + if(effect != null) + { + s_currentQuadEffect = effect..AddRef(); + } + else + { + s_currentQuadEffect = s_quadBatchEffect..AddRef(); + } + + s_currentCircleEffect?.ReleaseRef(); + if(circleEffect != null) + { + s_currentCircleEffect = effect..AddRef(); + } + else + { + s_currentCircleEffect = s_circleBatchEffect..AddRef(); + } + + s_currentLineEffect?.ReleaseRef(); + /*if(circleEffect != null) + { + s_currentLineEffect = effect..AddRef(); + } + else + {*/ + s_currentLineEffect = s_lineBatchEffect..AddRef(); + //} + + Matrix viewProjection = camera.Projection * camera.View; + + s_currentQuadEffect.Variables["ViewProjection"].SetData(viewProjection); + s_currentCircleEffect.Variables["ViewProjection"].SetData(viewProjection); + s_currentLineEffect.Variables["ViewProjection"].SetData(viewProjection); s_drawOrder = drawOrder; @@ -472,35 +632,44 @@ namespace GlitchyEngine.Renderer /// Adds a quad instance to the instance queue. [Inline] - private static void QueueQuadInstance(Matrix transform, ColorRGBA color, Texture2D texture, float depth, Vector4 uvTransform) + private static void QueueQuadInstance(Matrix transform, ColorRGBA color, Texture texture, float depth, Vector4 uvTransform, uint32 id = uint32.MaxValue) { - s_QuadinstanceQueue.Add(QueueQuad(transform, color, texture ?? s_whiteTexture, depth, uvTransform)); + s_QuadinstanceQueue.Add(QueueQuad(transform, color, texture ?? s_whiteTexture, depth, uvTransform, id)); s_statistics.QuadCount++; } /// Adds a circle instance to the instance queue. [Inline] - private static void QueueCircleInstance(Matrix transform, ColorRGBA color, Texture2D texture, float depth, Vector4 uvTransform, float innerRadius) + private static void QueueCircleInstance(Matrix transform, ColorRGBA color, Texture2D texture, float depth, Vector4 uvTransform, float innerRadius, uint32 id = uint32.MaxValue) { - s_circleInstanceQueue.Add(QueueCircle(transform, color, texture ?? s_whiteTexture, depth, uvTransform, innerRadius)); + s_circleInstanceQueue.Add(QueueCircle(transform, color, texture ?? s_whiteTexture, depth, uvTransform, innerRadius, id)); s_statistics.CircleCount++; } - - private static void FlushInstances() + + /// Adds a line instance to the instance queue. + [Inline] + private static void QueueLineInstance(Vector4 start, Vector4 end, ColorRGBA color, uint32 id = uint32.MaxValue) + { + s_lineInstanceQueue.Add(QueueLine(start, end, color, id)); + s_statistics.LineCount++; + } + + private static void FlushQuadInstances() { Debug.Profiler.ProfileRendererFunction!(); - if(s_setInstances == 0) + if(s_setQuadInstances == 0) return; - s_quadInstanceBuffer.SetData(s_rawQuadInstances.Ptr, s_setInstances, 0, .WriteDiscard); + s_quadInstanceBuffer.SetData(s_rawQuadInstances.Ptr, s_setQuadInstances, 0, .WriteDiscard); - s_currentEffect.Bind(Renderer._context); - s_quadBatchBinding.InstanceCount = s_setInstances; + s_currentQuadEffect.ApplyChanges(); + s_currentQuadEffect.Bind(); + s_quadBatchBinding.InstanceCount = s_setQuadInstances; s_quadBatchBinding.Bind(); RenderCommand.DrawIndexedInstanced(s_quadBatchBinding); - s_setInstances = 0; + s_setQuadInstances = 0; s_statistics.QuadDrawCalls++; } @@ -509,20 +678,41 @@ namespace GlitchyEngine.Renderer { Debug.Profiler.ProfileRendererFunction!(); - if(s_setInstances == 0) + if(s_setCircleInstances == 0) return; - s_circleInstanceBuffer.SetData(s_rawCircleInstances.Ptr, s_setInstances, 0, .WriteDiscard); + s_circleInstanceBuffer.SetData(s_rawCircleInstances.Ptr, s_setCircleInstances, 0, .WriteDiscard); - s_currentCircleEffect.Bind(Renderer._context); - s_circleBatchBinding.InstanceCount = s_setInstances; + s_currentCircleEffect.ApplyChanges(); + s_currentCircleEffect.Bind(); + s_circleBatchBinding.InstanceCount = s_setCircleInstances; s_circleBatchBinding.Bind(); RenderCommand.DrawIndexedInstanced(s_circleBatchBinding); - s_setInstances = 0; + s_setCircleInstances = 0; s_statistics.CircleDrawCalls++; } + + private static void FlushLineInstances() + { + Debug.Profiler.ProfileRendererFunction!(); + + if(s_setLineInstances == 0) + return; + + s_lineInstanceBuffer.SetData(s_rawLineVertices.Ptr, s_setLineInstances, 0, .WriteDiscard); + + s_currentLineEffect.ApplyChanges(); + s_currentLineEffect.Bind(); + s_lineBatchBinding.VertexCount = (.)s_setLineInstances; + s_lineBatchBinding.Bind(); + RenderCommand.DrawIndexed(s_lineBatchBinding); + + s_setLineInstances = 0; + + s_statistics.LineDrawCalls++; + } // Quad comparison private static int TextureComparison(QueueQuad lhs, QueueQuad rhs) @@ -552,6 +742,20 @@ namespace GlitchyEngine.Renderer return lhs.Depth <=> rhs.Depth; } + // Line comparison + /*private static int TextureComparison(QueueLine lhs, QueueLine rhs) + { + return (int)Internal.UnsafeCastToPtr(lhs.Texture) - (int)Internal.UnsafeCastToPtr(rhs.Texture); + }*/ + private static int BackToFrontComparison(QueueLine lhs, QueueLine rhs) + { + return rhs.Depth <=> lhs.Depth; + } + private static int FrontToBackComparison(QueueLine lhs, QueueLine rhs) + { + return lhs.Depth <=> rhs.Depth; + } + private static void SortInstances() { Debug.Profiler.ProfileRendererFunction!(); @@ -561,12 +765,16 @@ namespace GlitchyEngine.Renderer case .SortByTexture: s_QuadinstanceQueue.Sort(scope => TextureComparison); s_circleInstanceQueue.Sort(scope => TextureComparison); + /*Lines cant be sorted by texture*/ + s_lineInstanceQueue.Sort(scope => BackToFrontComparison); case .BackToFront: s_QuadinstanceQueue.Sort(scope => BackToFrontComparison); s_circleInstanceQueue.Sort(scope => BackToFrontComparison); + s_lineInstanceQueue.Sort(scope => BackToFrontComparison); case .FrontToBack: s_QuadinstanceQueue.Sort(scope => FrontToBackComparison); s_circleInstanceQueue.Sort(scope => FrontToBackComparison); + s_lineInstanceQueue.Sort(scope => FrontToBackComparison); case .Immediate: default: Log.EngineLogger.Error("Unknown instance draw order."); @@ -577,13 +785,14 @@ namespace GlitchyEngine.Renderer { Debug.Profiler.ProfileRendererFunction!(); - if(s_QuadinstanceQueue.IsEmpty && s_circleInstanceQueue.IsEmpty) + if(s_QuadinstanceQueue.IsEmpty && s_circleInstanceQueue.IsEmpty && s_lineInstanceQueue.IsEmpty) return; SortInstances(); DrawDeferredQuads(); DrawDeferredCircles(); + DrawDeferredLines(); } private static void DrawDeferredQuads() @@ -592,11 +801,14 @@ namespace GlitchyEngine.Renderer if(s_QuadinstanceQueue.IsEmpty) return; + + // TODO: per object blendstate + RenderCommand.SetBlendState(s_transparentBlendState); - Texture2D texture = s_QuadinstanceQueue[0].Texture; - s_currentEffect.SetTexture("Texture", texture); + Texture texture = s_QuadinstanceQueue[0].Texture; + s_currentQuadEffect.SetTexture("Texture", texture); - s_setInstances = 0; + s_setQuadInstances = 0; for(int i < s_QuadinstanceQueue.Count) { @@ -605,21 +817,21 @@ namespace GlitchyEngine.Renderer // flush every time the texture changes if(quad.Texture != texture) { - FlushInstances(); + FlushQuadInstances(); texture = quad.Texture; - s_currentEffect.SetTexture("Texture", texture); + s_currentQuadEffect.SetTexture("Texture", texture); } - s_rawQuadInstances[s_setInstances++] = .(quad.Transform, quad.Color, quad.uvTransform); + s_rawQuadInstances[s_setQuadInstances++] = .(quad.Transform, quad.Color, quad.uvTransform, quad.entityId); - if(s_setInstances == s_rawQuadInstances.Count) + if(s_setQuadInstances == s_rawQuadInstances.Count) { - FlushInstances(); + FlushQuadInstances(); } } - FlushInstances(); + FlushQuadInstances(); s_QuadinstanceQueue.Clear(); } @@ -631,10 +843,13 @@ namespace GlitchyEngine.Renderer if(s_circleInstanceQueue.IsEmpty) return; - Texture2D texture = s_circleInstanceQueue[0].Texture; + // TODO: per object blendstate + RenderCommand.SetBlendState(s_transparentBlendState); + + Texture texture = s_circleInstanceQueue[0].Texture; s_currentCircleEffect.SetTexture("Texture", texture); - s_setInstances = 0; + s_setCircleInstances = 0; for(int i < s_circleInstanceQueue.Count) { @@ -649,9 +864,9 @@ namespace GlitchyEngine.Renderer s_currentCircleEffect.SetTexture("Texture", texture); } - s_rawCircleInstances[s_setInstances++] = .(circle.Transform, circle.Color, circle.uvTransform, circle.InnerRadius); + s_rawCircleInstances[s_setCircleInstances++] = .(circle.Transform, circle.Color, circle.uvTransform, circle.InnerRadius, circle.entityId); - if(s_setInstances == s_rawCircleInstances.Count) + if(s_setCircleInstances == s_rawCircleInstances.Count) { FlushCircleInstances(); } @@ -662,6 +877,36 @@ namespace GlitchyEngine.Renderer s_circleInstanceQueue.Clear(); } + private static void DrawDeferredLines() + { + Debug.Profiler.ProfileRendererFunction!(); + + if(s_lineInstanceQueue.IsEmpty) + return; + + // TODO: per object blendstate + RenderCommand.SetBlendState(s_transparentBlendState); + + s_setLineInstances = 0; + + for(int i < s_lineInstanceQueue.Count) + { + let line = ref s_lineInstanceQueue[i]; + + s_rawLineVertices[s_setLineInstances++] = .(line.Start, line.Color, line.entityId); + s_rawLineVertices[s_setLineInstances++] = .(line.End, line.Color, line.entityId); + + if(s_setLineInstances == s_rawLineVertices.Count) + { + FlushLineInstances(); + } + } + + FlushLineInstances(); + + s_lineInstanceQueue.Clear(); + } + /// A specialized function that calculates the 2D transform matrix private static Matrix Calculate2DTransform(Vector3 translation, Vector2 scale, float rotation) { @@ -681,6 +926,85 @@ namespace GlitchyEngine.Renderer } // Primitives + + /** @brief Draws a line. + * @param start The start point of the line. + * @param end The end point of the line. + * @param color The color of the line. + * @param entityId The optional ID of the entity that belongs to this line (for picking). + */ + public static void DrawLine(Vector3 start, Vector3 end, ColorRGBA color = .White, uint32 entityId = uint32.MaxValue) + { + DrawLine(Vector4(start, 1.0f), Vector4(end, 1.0f), color, entityId); + } + + /** @brief Draws a ray. + * @param start The start point of the ray. + * @param direction The direction of the ray. + * @param color The color of the ray. + * @param entityId The optional ID of the entity that belongs to this ray (for picking). + */ + public static void DrawRay(Vector3 start, Vector3 direction, ColorRGBA color = .White, uint32 entityId = uint32.MaxValue) + { + DrawLine(Vector4(start, 1.0f), Vector4(direction, 0.0f), color, entityId); + } + + /** @brief Draws a rectangle. + * @param position The center of the rectangle. + * @param size The size of the rectangle. + * @param color The color of the rectangle. + * @param entityId The optional ID of the entity that belongs to this rectangle (for picking). + */ + public static void DrawRect(Vector2 position, Vector2 size, ColorRGBA color = .White, uint32 entityId = uint32.MaxValue) + { + Vector2 halfSize = size / 2; + + Vector4 p0 = Vector4(position + Vector2(-halfSize.X, -halfSize.Y), 0.0f, 1.0f); + Vector4 p1 = Vector4(position + Vector2(halfSize.X, -halfSize.Y), 0.0f, 1.0f); + Vector4 p2 = Vector4(position + Vector2(halfSize.X, halfSize.Y), 0.0f, 1.0f); + Vector4 p3 = Vector4(position + Vector2(-halfSize.X, halfSize.Y), 0.0f, 1.0f); + + DrawLine(p0, p1, color, entityId); + DrawLine(p1, p2, color, entityId); + DrawLine(p2, p3, color, entityId); + DrawLine(p3, p0, color, entityId); + } + + /** @brief Draws a rectangle. + * @param transform The transform of the rectangle. + * @param color The color of the rectangle. + * @param entityId The optional ID of the entity that belongs to this rectangle (for picking). + */ + public static void DrawRect(Matrix transform, ColorRGBA color = .White, uint32 entityId = uint32.MaxValue) + { + Vector2 halfSize = Vector2.One / 2.0f; + + Vector4 p0 = transform * Vector4(-halfSize.X, -halfSize.Y, 0.0f, 1.0f); + Vector4 p1 = transform * Vector4(halfSize.X, -halfSize.Y, 0.0f, 1.0f); + Vector4 p2 = transform * Vector4(halfSize.X, halfSize.Y, 0.0f, 1.0f); + Vector4 p3 = transform * Vector4(-halfSize.X, halfSize.Y, 0.0f, 1.0f); + + DrawLine(p0, p1, color, entityId); + DrawLine(p1, p2, color, entityId); + DrawLine(p2, p3, color, entityId); + DrawLine(p3, p0, color, entityId); + } + + public static void DrawLine(Vector4 start, Vector4 end, ColorRGBA color = .White, uint32 entityId = uint32.MaxValue) + { + Debug.Profiler.ProfileRendererFunction!(); + +#if DEBUG + Log.EngineLogger.AssertDebug(s_sceneRunning, "Missing call of BeginScene."); +#endif + + QueueLineInstance(start, end, color, entityId); + + if(s_drawOrder == .Immediate) + { + DrawDeferred(); + } + } // Colored Quad @@ -736,7 +1060,7 @@ namespace GlitchyEngine.Renderer public static void DrawQuad(Matrix transform, SubTexture2D texture, ColorRGBA color = .White) { - DrawQuad(transform, texture.Texture, .White, texture.TexCoords); + DrawQuad(transform, texture.Texture, color, texture.TexCoords); } // Subtex + Texcoords @@ -755,28 +1079,28 @@ namespace GlitchyEngine.Renderer DrawQuad(position, size, rotation, subtexture.Texture, .White, uv); } - public static void DrawQuad(Matrix transform, SubTexture2D subtexture, ColorRGBA color = .White, Vector4 uvTransform = .(0, 0, 1, 1)) + public static void DrawQuad(Matrix transform, SubTexture2D subtexture, ColorRGBA color = .White, Vector4 uvTransform = .(0, 0, 1, 1), uint32 entityId = uint32.MaxValue) { Vector4 uv = CalculateSubTexcoords(subtexture.TexCoords, uvTransform); - DrawQuad(transform, subtexture.Texture, .White, uv); + DrawQuad(transform, subtexture.Texture, color, uv, entityId); } // Textured Quad - public static void DrawQuad(Vector2 position, Vector2 size, float rotation, Texture2D texture, ColorRGBA color = .White, Vector4 uvTransform = .(0, 0, 1, 1)) + public static void DrawQuad(Vector2 position, Vector2 size, float rotation, Texture texture, ColorRGBA color = .White, Vector4 uvTransform = .(0, 0, 1, 1)) { DrawQuad(Vector3(position, 0.0f), size, rotation, texture, color, uvTransform); } - public static void DrawQuad(Vector3 position, Vector2 size, float rotation, Texture2D texture, ColorRGBA color = .White, Vector4 uvTransform = .(0, 0, 1, 1)) + public static void DrawQuad(Vector3 position, Vector2 size, float rotation, Texture texture, ColorRGBA color = .White, Vector4 uvTransform = .(0, 0, 1, 1)) { Matrix transform = Calculate2DTransform(position, size, rotation); DrawQuad(transform, texture, color, uvTransform); } - public static void DrawQuad(Matrix transform, Texture2D texture, ColorRGBA color = .White, Vector4 uvTransform = .(0, 0, 1, 1)) + public static void DrawQuad(Matrix transform, Texture texture, ColorRGBA color = .White, Vector4 uvTransform = .(0, 0, 1, 1), uint32 entityId = uint32.MaxValue) { Debug.Profiler.ProfileRendererFunction!(); @@ -784,7 +1108,7 @@ namespace GlitchyEngine.Renderer Log.EngineLogger.AssertDebug(s_sceneRunning, "Missing call of BeginScene."); #endif - QueueQuadInstance(transform, color, texture, transform.Translation.Z, uvTransform); + QueueQuadInstance(transform, color, texture, transform.Translation.Z, uvTransform, entityId); if(s_drawOrder == .Immediate) { @@ -792,6 +1116,16 @@ namespace GlitchyEngine.Renderer } } + public static void DrawSprite(Matrix transform, SpriteRendererComponent* spriteRenderer, uint32 entityId) + { + DrawQuad(transform, spriteRenderer.Sprite.Get() ?? s_whiteTexture, spriteRenderer.Color, spriteRenderer.UvTransform, entityId); + } + + public static void DrawCircle(Matrix transform, CircleRendererComponent* spriteRenderer, uint32 entityId) + { + DrawCircle(transform, spriteRenderer.Sprite.Get() ?? s_whiteTexture, spriteRenderer.Color, spriteRenderer.InnerRadius, spriteRenderer.UvTransform, entityId); + } + // Textured quad pivot public static void DrawQuadPivotCorner(Vector2 position, Vector2 size, float rotation, Texture2D texture, ColorRGBA color = .White, Vector4 uvTransform = .(0, 0, 1, 1)) @@ -828,7 +1162,7 @@ namespace GlitchyEngine.Renderer DrawCircle(transform, texture, color, innerRadius, uvTransform); } - public static void DrawCircle(Matrix transform, Texture2D texture, ColorRGBA color = .White, float innerRadius = 1.0f, Vector4 uvTransform = .(0, 0, 1, 1)) + public static void DrawCircle(Matrix transform, Texture2D texture, ColorRGBA color = .White, float innerRadius = 1.0f, Vector4 uvTransform = .(0, 0, 1, 1), uint32 entityId = uint32.MaxValue) { Debug.Profiler.ProfileRendererFunction!(); @@ -836,7 +1170,7 @@ namespace GlitchyEngine.Renderer Log.EngineLogger.AssertDebug(s_sceneRunning, "Missing call of BeginScene."); #endif - QueueCircleInstance(transform, color, texture, transform.Translation.Z, uvTransform, innerRadius); + QueueCircleInstance(transform, color, texture, transform.Translation.Z, uvTransform, innerRadius, entityId); if(s_drawOrder == .Immediate) { @@ -848,21 +1182,26 @@ namespace GlitchyEngine.Renderer { public uint32 QuadDrawCalls = 0; public uint32 CircleDrawCalls = 0; + public uint32 LineDrawCalls = 0; + public uint32 QuadCount = 0; public uint32 CircleCount = 0; + public uint32 LineCount = 0; - public uint32 TotalDrawCalls => QuadDrawCalls + CircleDrawCalls; - public uint32 TotalInstanceCount => QuadCount + CircleCount; - public uint32 TotalVertexCount => TotalInstanceCount * 4; - public uint32 TotalTriangleCount => TotalInstanceCount * 2; - public uint32 TotalIndexCount => TotalInstanceCount * 6; + public uint32 TotalDrawCalls => QuadDrawCalls + CircleDrawCalls + LineDrawCalls; + public uint32 TotalInstanceCount => QuadCount + CircleCount + LineCount; + public uint32 TotalVertexCount => (QuadCount + CircleCount) * 4 + LineCount * 2; + public uint32 TotalTriangleCount => (QuadCount + CircleCount) * 2; + public uint32 TotalIndexCount => (QuadCount + CircleCount) * 6; public void Reset() mut { QuadDrawCalls = 0; CircleDrawCalls = 0; + LineDrawCalls = 0; QuadCount = 0; CircleCount = 0; + LineCount = 0; } } diff --git a/GlitchyEngine/src/Renderer/RendererAPI.bf b/GlitchyEngine/src/Renderer/RendererAPI.bf index 948c15d..5d60b74 100644 --- a/GlitchyEngine/src/Renderer/RendererAPI.bf +++ b/GlitchyEngine/src/Renderer/RendererAPI.bf @@ -21,6 +21,9 @@ namespace GlitchyEngine.Renderer public extern void Clear(DepthStencilTarget target, ClearOptions options, float depth, uint8 stencil); + //public extern void Clear(RenderTargetGroup renderTarget, ClearOptions options, ColorRGBA? color = null, float? depth = null, uint8? stencil = null); + public extern void Clear(RenderTargetGroup renderTarget, ClearOptions options, ClearColor? color = null, float? depth = null, uint8? stencil = null); + public void Clear(RenderTarget2D renderTarget, ClearOptions options, ColorRGBA color, float depth, uint8 stencil) { Debug.Profiler.ProfileRendererFunction!(); @@ -36,13 +39,17 @@ namespace GlitchyEngine.Renderer public extern void SetRenderTarget(RenderTarget2D renderTarget, int slot, bool setDepthBuffer); + public extern void SetRenderTargetGroup(RenderTargetGroup renderTarget, bool setDepthBuffer); + public extern void SetDepthStencilTarget(DepthStencilTarget target); + public extern void UnbindRenderTargets(); + public extern void BindRenderTargets(); public extern void SetRasterizerState(RasterizerState rasterizerState); - public extern void SetBlendState(BlendState blendState, ColorRGBA blendFactor); + public extern void SetBlendState(BlendState blendState, ColorRGBA blendFactor = .White); public extern void SetDepthStencilState(DepthStencilState depthStencilState, uint8 stencilReference); @@ -53,5 +60,13 @@ namespace GlitchyEngine.Renderer public extern void DrawIndexedInstanced(GeometryBinding geometry); public extern void SetViewport(Viewport viewport); + + public extern void UnbindTextures(); + + public extern void BindConstantBuffer(Buffer buffer, int slot, ShaderStage stage); + + public extern void BindVertexShader(VertexShader vertexShader); + + public extern void BindPixelShader(PixelShader pixelShader); } } diff --git a/GlitchyEngine/src/Renderer/SamplerState.bf b/GlitchyEngine/src/Renderer/SamplerState.bf index c52c570..579924b 100644 --- a/GlitchyEngine/src/Renderer/SamplerState.bf +++ b/GlitchyEngine/src/Renderer/SamplerState.bf @@ -2,12 +2,14 @@ using System; using GlitchyEngine.Core; using GlitchyEngine.Math; using System.Collections; +using Bon; namespace GlitchyEngine.Renderer { /** * Defines the filter function used when sampling from a texture. */ + [BonTarget, Reflect] public enum FilterFunction { /// Use point filtering (nearest neighbor) for sampling. @@ -17,7 +19,8 @@ namespace GlitchyEngine.Renderer /// Use anisotropic interpolation for sampling. Anisotropic } - + + [BonTarget, Reflect] public enum FilterMode { /// Just sample the texture. @@ -29,7 +32,8 @@ namespace GlitchyEngine.Renderer /// Return the maximum value of the fetched texels. Maximum } - + + [BonTarget, Reflect] public enum ComparisonFunction { /** @@ -65,7 +69,8 @@ namespace GlitchyEngine.Renderer */ Always = 8 } - + + [BonTarget, Reflect] public enum TextureAddressMode { /// Tile the texture at every (u,v) integer junction. @@ -92,13 +97,21 @@ namespace GlitchyEngine.Renderer */ MirrorOnce } - + + [BonTarget] public struct SamplerStateDescription : IHashable { + /// Sampling method used for minification. + /// If set to "Anisotropic" all Filters are set to "Anisotropic" internally. public FilterFunction MinFilter = .Linear; + /// Sampling method used for magnification. + /// If set to "Anisotropic" all Filters are set to "Anisotropic" internally. public FilterFunction MagFilter = .Linear; + /// Method used for mip-level sampling. + /// If set to "Anisotropic" all Filters are set to "Anisotropic" internally. public FilterFunction MipFilter = .Linear; + /// Filtering method to use when sampling a texture. public FilterMode FilterMode = .Default; /** @@ -107,8 +120,11 @@ namespace GlitchyEngine.Renderer */ public ComparisonFunction ComparisonFunction = .Never; + /// Method to use for resolving a u texture coordinate that is outside the 0 to 1 range. public TextureAddressMode AddressModeU = .Clamp; + /// Method to use for resolving a v texture coordinate that is outside the 0 to 1 range. public TextureAddressMode AddressModeV = .Clamp; + /// Method to use for resolving a w texture coordinate that is outside the 0 to 1 range. public TextureAddressMode AddressModeW = .Clamp; /** diff --git a/GlitchyEngine/src/Renderer/Shader.bf b/GlitchyEngine/src/Renderer/Shader.bf index f86cf3f..1d33a07 100644 --- a/GlitchyEngine/src/Renderer/Shader.bf +++ b/GlitchyEngine/src/Renderer/Shader.bf @@ -2,6 +2,7 @@ using System; using System.IO; using System.Collections; using GlitchyEngine.Core; +using GlitchyEngine.Content; namespace GlitchyEngine.Renderer { @@ -21,7 +22,7 @@ namespace GlitchyEngine.Renderer public abstract class Shader : RefCounter { - protected BufferCollection _buffers ~ delete _;//:append _; + protected internal BufferCollection _buffers ~ _.ReleaseRef();//:append _; protected ShaderTextureCollection _textures ~ delete _; @@ -30,7 +31,7 @@ namespace GlitchyEngine.Renderer public ShaderTextureCollection Textures => _textures; [AllowAppend] - public this(String source, String entryPoint, ShaderDefine[] macros = null) + public this(StringView code, StringView? fileName, String entryPoint, IContentManager contentManager, ShaderDefine[] macros = null) { Debug.Profiler.ProfileResourceFunction!(); @@ -39,7 +40,7 @@ namespace GlitchyEngine.Renderer _buffers = new BufferCollection(); _textures = new ShaderTextureCollection(); - CompileFromSource(source, entryPoint); + CompileFromSource(code, fileName, entryPoint, contentManager); } public ~this() @@ -47,20 +48,19 @@ namespace GlitchyEngine.Renderer Debug.Profiler.ProfileResourceFunction!(); } - public static mixin FromFile(String fileName, String entryPoint, ShaderDefine[] macros = null) where T : Shader + /*public static mixin FromFile(String fileName, String entryPoint, IContentManager contentManager, ShaderDefine[] macros = null) where T : Shader { Debug.Profiler.ProfileResourceFunction!(); String fileContent = new String(); - File.ReadAllText(fileName, fileContent, true); - T shader = new T(fileContent, entryPoint, macros); + T shader = new T(fileContent, (StringView)fileName, contentManager, entryPoint, macros); delete fileContent; shader - } + }*/ - public abstract void CompileFromSource(String code, String entryPoint, ShaderDefine[] macros = null); + public abstract void CompileFromSource(StringView code, StringView? fileName, String entryPoint, IContentManager contentManager, ShaderDefine[] macros = null); } } diff --git a/GlitchyEngine/src/Renderer/ShaderStage.bf b/GlitchyEngine/src/Renderer/ShaderStage.bf new file mode 100644 index 0000000..9721776 --- /dev/null +++ b/GlitchyEngine/src/Renderer/ShaderStage.bf @@ -0,0 +1,12 @@ +using System; +namespace GlitchyEngine.Renderer +{ + [AllowDuplicates] + public enum ShaderStage + { + Vertex = 1, + Pixel = 2, + + All = Vertex | Pixel + } +} \ No newline at end of file diff --git a/GlitchyEngine/src/Renderer/ShaderTextureCollection.bf b/GlitchyEngine/src/Renderer/ShaderTextureCollection.bf index 345e01f..08a5ab7 100644 --- a/GlitchyEngine/src/Renderer/ShaderTextureCollection.bf +++ b/GlitchyEngine/src/Renderer/ShaderTextureCollection.bf @@ -3,9 +3,9 @@ using System.Collections; namespace GlitchyEngine.Renderer { - public class ShaderTextureCollection : IEnumerable<(String Name, uint32 Index, Texture Texture)> + public class ShaderTextureCollection : IEnumerable<(String Name, uint32 Index, TextureViewBinding BoundTexture)> { - public typealias ResourceEntry = (String Name, uint32 Index, Texture Texture); + public typealias ResourceEntry = (String Name, uint32 Index, TextureViewBinding BoundTexture); List _textures ~ DeleteTextureEntries!(_); @@ -31,7 +31,7 @@ namespace GlitchyEngine.Renderer for(let entry in entries) { delete entry.Name; - entry.Texture?.ReleaseRef(); + entry.BoundTexture.Release(); } delete entries; @@ -39,15 +39,15 @@ namespace GlitchyEngine.Renderer // TODO: finish implementation (like BufferCollection) - public void Add(String name, uint32 index, Texture texture) + public void Add(String name, uint32 index, TextureViewBinding texture) { Add((name, index, texture)); } public void Add(ResourceEntry entry) { - ResourceEntry copy = (new String(entry.Name), entry.Index, entry.Texture); - entry.Texture?.AddRef(); + ResourceEntry copy = (new String(entry.Name), entry.Index, entry.BoundTexture); + entry.BoundTexture.AddRef(); _textures.Add(copy); diff --git a/GlitchyEngine/src/Renderer/Text/Font.bf b/GlitchyEngine/src/Renderer/Text/Font.bf index 4f516f7..7fc929e 100644 --- a/GlitchyEngine/src/Renderer/Text/Font.bf +++ b/GlitchyEngine/src/Renderer/Text/Font.bf @@ -707,9 +707,9 @@ namespace GlitchyEngine.Renderer.Text int index = ((desc.Height - y - 1) * desc.Width + x) * 4; - pixels[index + 0] = ToInt8(pixel.Red); - pixels[index + 1] = ToInt8(pixel.Green); - pixels[index + 2] = ToInt8(pixel.Blue); + pixels[index + 0] = ToInt8(pixel.R); + pixels[index + 1] = ToInt8(pixel.G); + pixels[index + 2] = ToInt8(pixel.B); pixels[index + 3] = Int8.MaxValue; } diff --git a/GlitchyEngine/src/Renderer/Text/FontRenderer.bf b/GlitchyEngine/src/Renderer/Text/FontRenderer.bf index adfa41f..93d11ec 100644 --- a/GlitchyEngine/src/Renderer/Text/FontRenderer.bf +++ b/GlitchyEngine/src/Renderer/Text/FontRenderer.bf @@ -3,6 +3,7 @@ using FreeType; using System.Diagnostics; using GlitchyEngine.Math; using System.Collections; +using GlitchyEngine.Core; using static FreeType.HarfBuzz; using internal GlitchyEngine.Renderer.Text; @@ -61,7 +62,7 @@ namespace GlitchyEngine.Renderer.Text FreeType.Done_FreeType(s_Library); } - public class PreparedText : RefCounted + public class PreparedText : RefCounter { //public List Lines ~ ClearAndDeleteItems!(_); public Font Font ~ _?.ReleaseRef(); @@ -332,8 +333,8 @@ namespace GlitchyEngine.Renderer.Text Renderer2D.Flush(); // TODO: this is very not good! - var lastEffect = Renderer2D.[Friend]s_currentEffect; - Renderer2D.[Friend]s_currentEffect = _msdfEffect..AddRef(); + var lastEffect = Renderer2D.[Friend]s_currentQuadEffect; + Renderer2D.[Friend]s_currentQuadEffect = _msdfEffect..AddRef(); // TODO: oh no.... // Copy viewProjection from current effect Matrix viewProjection = lastEffect.Variables["ViewProjection"].[Friend]GetData(); @@ -397,7 +398,7 @@ namespace GlitchyEngine.Renderer.Text Matrix glyphTransform = transform * Matrix.Translation(position) * Matrix.Scaling(viewportRect.Z, viewportRect.W, 1.0f); - Renderer2D.DrawQuad(glyphTransform, atlas, glyphColor, texRect); + Renderer2D.DrawQuad(glyphTransform, atlas, (ColorRGBA)glyphColor, texRect); //Renderer2D.DrawQuad(Vector2(viewportRect.X + viewportRect.Z / 2, viewportRect.Y + viewportRect.W / 2), .(viewportRect.Z, viewportRect.W), 0, atlas, glyphColor, texRect); // Show pen positions @@ -409,7 +410,7 @@ namespace GlitchyEngine.Renderer.Text // TODO: not good! // Change back effect _msdfEffect.ReleaseRef(); - Renderer2D.[Friend]s_currentEffect = lastEffect; + Renderer2D.[Friend]s_currentQuadEffect = lastEffect; // release all atlas textures for(int i < atlasses.Count) @@ -438,8 +439,8 @@ namespace GlitchyEngine.Renderer.Text Renderer2D.Flush(); // TODO: this is very not good! - var lastEffect = Renderer2D.[Friend]s_currentEffect; - Renderer2D.[Friend]s_currentEffect = _msdfEffect..AddRef(); + var lastEffect = Renderer2D.[Friend]s_currentQuadEffect; + Renderer2D.[Friend]s_currentQuadEffect = _msdfEffect..AddRef(); // TODO: oh no.... // Copy viewProjection from current effect Matrix viewProjection = lastEffect.Variables["ViewProjection"].[Friend]GetData(); @@ -648,7 +649,7 @@ namespace GlitchyEngine.Renderer.Text texRect /= Vector4(atlasSize, atlasSize); - Renderer2D.DrawQuad(Vector2(viewportRect.X + viewportRect.Z / 2, viewportRect.Y + viewportRect.W / 2), .(viewportRect.Z, viewportRect.W), 0, atlas, glyphColor, texRect); + Renderer2D.DrawQuad(Vector2(viewportRect.X + viewportRect.Z / 2, viewportRect.Y + viewportRect.W / 2), .(viewportRect.Z, viewportRect.W), 0, atlas, (ColorRGBA)glyphColor, texRect); //renderer.Draw(atlas, viewportRect.X, viewportRect.Y, viewportRect.Z, viewportRect.W, glyphColor, *(float*)(&depthInt), texRect); @@ -663,7 +664,7 @@ namespace GlitchyEngine.Renderer.Text // TODO: not good! // Change back effect _msdfEffect.ReleaseRef(); - Renderer2D.[Friend]s_currentEffect = lastEffect; + Renderer2D.[Friend]s_currentQuadEffect = lastEffect; // release all atlas textures for(int i < atlasses.Count) diff --git a/GlitchyEngine/src/Renderer/Texture.bf b/GlitchyEngine/src/Renderer/Texture.bf index 052a407..490f735 100644 --- a/GlitchyEngine/src/Renderer/Texture.bf +++ b/GlitchyEngine/src/Renderer/Texture.bf @@ -1,12 +1,13 @@ -using System; +using GlitchyEngine.Content; using GlitchyEngine.Core; using GlitchyEngine.Math; +using System; using System.IO; using System.Diagnostics; namespace GlitchyEngine.Renderer { - public abstract class Texture : RefCounter + public abstract class Texture : Asset { protected SamplerState _samplerState ~ _?.ReleaseRef(); @@ -17,10 +18,8 @@ namespace GlitchyEngine.Renderer { if(_samplerState == value) return; - - _samplerState?.ReleaseRef(); - _samplerState = value; - _samplerState?.AddRef(); + + SetReference!(_samplerState, value); } } @@ -29,6 +28,13 @@ namespace GlitchyEngine.Renderer public abstract uint32 Depth {get;} public abstract uint32 ArraySize {get;} public abstract uint32 MipLevels {get;} + + public abstract TextureViewBinding GetViewBinding(); + + /// Very dirtily swaps the internals with the given texture. + /// TODO: Please do this differently!!!!!!!!!!!!!!!!!!!!!! + /// This is for texture hot reloading POC, I know... it's bad... + protected internal abstract void SneakySwappyTexture(Texture otherTexture); } public struct Texture2DDesc @@ -57,86 +63,18 @@ namespace GlitchyEngine.Renderer public class Texture2D : Texture { - protected String _path ~ delete _; - //public override extern uint32 Width {get;} //public override extern uint32 Height {get;} public override uint32 Depth => 1; //public override extern uint32 ArraySize {get;} //public override extern uint32 MipLevels {get;} - - public this(StringView path) + + // TODO: remove + private this(Stream data) { - _path = new String(path); - LoadTexture(); + LoadDds(data); } - const String PngMagicWord = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"; - const String DdsMagicWord = "DDS "; - - private void LoadTexture() - { - Debug.Profiler.ProfileResourceFunction!(); - - Stream data = Application.Get().ContentManager.GetFile(_path); - defer delete data; - - var readResult = data.Read(); - - data.Position = 0; - - char8[8] magicWord; - - if (readResult case .Ok(out magicWord)) - { - StringView strView = .(&magicWord, magicWord.Count); - - if (strView.StartsWith(PngMagicWord)) - { - LoadPng(data); - } - else if (strView.StartsWith(DdsMagicWord)) - { - LoadDds(data); - } - else - { - Runtime.FatalError("Unknown image format."); - } - } - } - - protected void LoadPng(Stream stream) - { - Debug.Profiler.ProfileResourceFunction!(); - - uint8[] pngData = new:ScopedAlloc! uint8[stream.Length]; - - var result = stream.TryRead(pngData); - - if (result case .Err(let err)) - { - Log.EngineLogger.Error($"Failed to read data from stream. Texture: \"{_path}\", Error: {err}"); - } - - uint8* rawData = ?; - uint32 width = 0, height = 0; - - uint32 errorCode = LodePng.LodePng.Decode32(&rawData, &width, &height, pngData.Ptr, (.)pngData.Count); - - Debug.Assert(errorCode == 0, "Failed to load png File"); - - // TODO: load as SRGB because PNGs are usually not stored as linear - //Texture2DDesc desc = .(width, height, .R8G8B8A8_UNorm_SRGB, 1, 1, .Immutable); - Texture2DDesc desc = .(width, height, .R8G8B8A8_UNorm, 1, 1, .Immutable); - - PrepareTexturePlatform(desc, false); - - SetData((.)rawData); - - LodePng.LodePng.Free(rawData); - } - protected void LoadDds(Stream stream) { LoadDdsPlatform(stream); @@ -185,6 +123,24 @@ namespace GlitchyEngine.Renderer * Copies the data to the given destination. */ public extern void CopyTo(Texture2D destination); + + public override TextureViewBinding GetViewBinding() + { + return PlatformGetViewBinding(); + } + + protected extern TextureViewBinding PlatformGetViewBinding(); + + protected internal override void SneakySwappyTexture(Texture otherTexture) + { + Log.EngineLogger.AssertDebug(otherTexture is Texture2D, "Swapping texture must be a Texture2D!"); + + SamplerState = otherTexture.SamplerState; + + PlatformSneakySwappyTexture(otherTexture as Texture2D); + } + + protected extern void PlatformSneakySwappyTexture(Texture2D otherTexture); } public class TextureCube : Texture @@ -207,12 +163,24 @@ namespace GlitchyEngine.Renderer { Debug.Profiler.ProfileResourceFunction!(); - Stream data = Application.Get().ContentManager.GetFile(_path); + Stream data = Application.Get().ContentManager.GetStream(_path); defer delete data; LoadTexturePlatform(data); } protected extern void LoadTexturePlatform(Stream stream); + + public override TextureViewBinding GetViewBinding() + { + return PlatformGetViewBinding(); + } + + protected extern TextureViewBinding PlatformGetViewBinding(); + + protected internal override void SneakySwappyTexture(Texture otherTexture) + { + Runtime.NotImplemented(); + } } } diff --git a/GlitchyEngine/src/Renderer/TextureViewBinding.bf b/GlitchyEngine/src/Renderer/TextureViewBinding.bf new file mode 100644 index 0000000..9aa24fc --- /dev/null +++ b/GlitchyEngine/src/Renderer/TextureViewBinding.bf @@ -0,0 +1,16 @@ +using System; + +namespace GlitchyEngine.Renderer +{ + /// Represents a reference to a texture that can be used as shader input resource. + public struct TextureViewBinding : IRefCounted, IDisposable + { + /// True if the view binding actually has a texture. False otherwise. + public extern bool IsEmpty { get; } + + public extern void AddRef(); + public extern void Release(); + + public void Dispose() => Release(); + } +} diff --git a/GlitchyEngine/src/Renderer/VertexLayout.bf b/GlitchyEngine/src/Renderer/VertexLayout.bf index 005c226..5568284 100644 --- a/GlitchyEngine/src/Renderer/VertexLayout.bf +++ b/GlitchyEngine/src/Renderer/VertexLayout.bf @@ -3,7 +3,6 @@ using GlitchyEngine.Core; namespace GlitchyEngine.Renderer { - /** * Type of data contained in an input slot. */ @@ -61,7 +60,7 @@ namespace GlitchyEngine.Renderer public this() => this = default; - public this(Format format, String semanticName, bool ownsName = false, uint32 semanticIndex = 0, uint32 inputSlot = 0, uint32 offset = (.)-1, InputClassification slotClass = .PerVertexData, uint32 instanceStepRate = 0) + public this(Format format, String semanticName, bool ownsName = false, uint32 semanticIndex = 0, uint32 inputSlot = 0, uint32 offset = VertexElement.AppendAligned, InputClassification slotClass = .PerVertexData, uint32 instanceStepRate = 0) { Format = format; SemanticName = semanticName; @@ -86,6 +85,12 @@ namespace GlitchyEngine.Renderer public VertexElement[] Elements => _elements; + public this(VertexElement[] elements, bool ownsElements) + { + _elements = elements; + _ownsElements = ownsElements; + } + public ~this() { if(_ownsElements) @@ -99,8 +104,6 @@ namespace GlitchyEngine.Renderer delete _elements; } } - - protected extern void CreateNativeLayout(); } public interface IVertexData diff --git a/GlitchyEngine/src/Renderer/VertexShader.bf b/GlitchyEngine/src/Renderer/VertexShader.bf index a8bea4c..68fbc60 100644 --- a/GlitchyEngine/src/Renderer/VertexShader.bf +++ b/GlitchyEngine/src/Renderer/VertexShader.bf @@ -1,11 +1,12 @@ using System; +using GlitchyEngine.Content; namespace GlitchyEngine.Renderer { public class VertexShader : Shader { [AllowAppend] - public this(String source, String entryPoint, ShaderDefine[] macros = null) - : base(source, entryPoint, macros) { } + public this(StringView code, StringView? fileName, String entryPoint, IContentManager contentManager = null, ShaderDefine[] macros = null) + : base(code, fileName, entryPoint, contentManager, macros) { } } } diff --git a/GlitchyEngine/src/Window.bf b/GlitchyEngine/src/Window.bf index 8263533..5972cbf 100644 --- a/GlitchyEngine/src/Window.bf +++ b/GlitchyEngine/src/Window.bf @@ -54,7 +54,7 @@ namespace GlitchyEngine /** * Gets or Sets the width and height of the window. */ - public extern Point Size {get; set;} + public extern Int2 Size {get; set;} /** * Gets or Sets the width of the window. */ @@ -67,7 +67,7 @@ namespace GlitchyEngine /** * Gets or Sets the position of the upper-left corner of the client area of the window. */ - public extern Point Position {get; set;} + public extern Int2 Position {get; set;} /** * Gets or Sets the x-coordinate of the upper-left corner of the client area of the window. diff --git a/GlitchyEngine/src/World/AnimationComponent.bf b/GlitchyEngine/src/World/Components/AnimationComponent.bf similarity index 100% rename from GlitchyEngine/src/World/AnimationComponent.bf rename to GlitchyEngine/src/World/Components/AnimationComponent.bf diff --git a/GlitchyEngine/src/World/Components.bf b/GlitchyEngine/src/World/Components/Components.bf similarity index 65% rename from GlitchyEngine/src/World/Components.bf rename to GlitchyEngine/src/World/Components/Components.bf index 28e6b6b..90108d8 100644 --- a/GlitchyEngine/src/World/Components.bf +++ b/GlitchyEngine/src/World/Components/Components.bf @@ -1,6 +1,9 @@ +using System; using GlitchyEngine.Math; using GlitchyEngine.Renderer; -using System; +using GlitchyEngine.Core; +using Box2D; +using GlitchyEngine.Content; namespace GlitchyEngine.World { @@ -15,6 +18,22 @@ namespace GlitchyEngine.World } } + struct IDComponent + { + public readonly UUID ID; + + /// Create a new IDComponent with a random UUID. + public this() + { + ID = UUID(); + } + + public this(UUID id) + { + ID = id; + } + } + /// If an entity has the EditorComponent it won't be displayed in the scene hierarchy. struct EditorComponent { @@ -26,10 +45,12 @@ namespace GlitchyEngine.World } [Component("Sprite Renderer")] - struct SpriterRendererComponent : IDisposableComponent + struct SpriteRendererComponent { - public Texture2D Sprite = null; + public AssetHandle Sprite = .Invalid; + public ColorRGBA Color = .White; + public Vector4 UvTransform = .(0, 0, 1, 1); public this() { @@ -39,10 +60,25 @@ namespace GlitchyEngine.World { Color = color; } + } - public void Dispose() + [Component("Circle Renderer")] + struct CircleRendererComponent + { + public AssetHandle Sprite = .Invalid; + + public ColorRGBA Color = .White; + public Vector4 UvTransform = .(0, 0, 1, 1); + + public float InnerRadius = 0.0f; + + public this() { - Sprite?.ReleaseRef(); + } + + public this(ColorRGBA color) + { + Color = color; } } @@ -249,16 +285,25 @@ namespace GlitchyEngine.World } } - struct CameraComponent + struct CameraComponent : IDisposableComponent { - public SceneCamera Camera; + public SceneCamera Camera = .(); public bool Primary = true; // Todo: probably move into scene - public bool FixedAspectRatio = false; + private RenderTargetGroup _renderTarget = null; - public this() + public RenderTargetGroup RenderTarget { - Camera = .(); - } + get => _renderTarget; + set mut + { + SetReference!(_renderTarget, value); + } + } + + public void Dispose() + { + _renderTarget?.ReleaseRef(); + } } struct NativeScriptComponent : IDisposableComponent @@ -288,4 +333,113 @@ namespace GlitchyEngine.World DestroyInstanceFunction(&this); } } + + struct SceneLight + { + public enum LightType + { + Directional = 0, + Spot = 1, + Point = 2 + } + + private LightType _type = .Directional; + + private float _illuminance = 10.0f; + + private ColorRGB _color = .(1, 1, 1); + + public LightType LightType + { + get => _type; + set mut => _type = value; + } + + public ColorRGB Color + { + get => _color; + set mut => _color = value; + } + + public float Illuminance + { + get => _illuminance; + set mut => _illuminance = value; + } + } + + struct LightComponent + { + public SceneLight SceneLight; + } + + struct Rigidbody2DComponent + { + public enum BodyType { Static = 0, Dynamic = 1, Kinematic = 2 } + + public BodyType BodyType = .Static; + + public bool FixedRotation = false; + + private int _runtimeBody = 0; + + internal b2Body* RuntimeBody + { + [Inline] + get => (b2Body*)(void*)_runtimeBody; + [Inline] + set mut => _runtimeBody = (int)(void*)value; + } + } + + struct BoxCollider2DComponent + { + public Vector2 Offset = .(0.0f, 0.0f); + public Vector2 Size = .(0.5f, 0.5f); + + // TODO: move into 2D physics material + public float Density = 1.0f; + public float Friction = 0.5f; + public float Restitution = 0.0f; + public float RestitutionThreshold = 0.5f; + + private int _runtimeFixture = 0; + + internal b2Fixture* RuntimeFixture + { + [Inline] + get => (b2Fixture*)(void*)_runtimeFixture; + [Inline] + set mut => _runtimeFixture = (int)(void*)value; + } + + [Inline] + internal ref b2Vec2 b2Offset mut => ref *(Box2D.b2Vec2*)(void*)&Offset; + } + + struct CircleCollider2DComponent + { + public Vector2 Offset = .(0.0f, 0.0f); + + public float Radius = 0.5f; + + // TODO: move into 2D physics material + public float Density = 1.0f; + public float Friction = 0.5f; + public float Restitution = 0.0f; + public float RestitutionThreshold = 0.5f; + + private int _runtimeFixture = 0; + + internal b2Fixture* RuntimeFixture + { + [Inline] + get => (b2Fixture*)(void*)_runtimeFixture; + [Inline] + set mut => _runtimeFixture = (int)(void*)value; + } + + [Inline] + internal ref b2Vec2 b2Offset mut => ref *(Box2D.b2Vec2*)(void*)&Offset; + } } \ No newline at end of file diff --git a/GlitchyEngine/src/World/IDisposableComponent.bf b/GlitchyEngine/src/World/Components/IDisposableComponent.bf similarity index 100% rename from GlitchyEngine/src/World/IDisposableComponent.bf rename to GlitchyEngine/src/World/Components/IDisposableComponent.bf diff --git a/GlitchyEngine/src/World/Components/MeshRendererComponent.bf b/GlitchyEngine/src/World/Components/MeshRendererComponent.bf new file mode 100644 index 0000000..1146ee1 --- /dev/null +++ b/GlitchyEngine/src/World/Components/MeshRendererComponent.bf @@ -0,0 +1,12 @@ +using GlitchyEngine.Renderer; +using System; +using GlitchyEngine.Content; + +namespace GlitchyEngine.World +{ + /// A component that allows to render a mesh. + public struct MeshRendererComponent + { + public AssetHandle Material = .Invalid; + } +} diff --git a/GlitchyEngine/src/World/Components/NameComponent.bf b/GlitchyEngine/src/World/Components/NameComponent.bf new file mode 100644 index 0000000..1b7a7d3 --- /dev/null +++ b/GlitchyEngine/src/World/Components/NameComponent.bf @@ -0,0 +1,25 @@ +using System; +namespace GlitchyEngine.World +{ + struct NameComponent : IDisposableComponent + { + private String _name; + + public StringView Name + { + get => _name; + set mut + { + if (_name == null) + _name = new String(value); + else + _name.Set(value); + } + } + + public void Dispose() mut + { + DeleteAndNullify!(_name); + } + } +} diff --git a/GlitchyEngine/src/World/SkinnedMeshRendererComponent.bf b/GlitchyEngine/src/World/Components/SkinnedMeshRendererComponent.bf similarity index 100% rename from GlitchyEngine/src/World/SkinnedMeshRendererComponent.bf rename to GlitchyEngine/src/World/Components/SkinnedMeshRendererComponent.bf diff --git a/GlitchyEngine/src/World/TransformComponent.bf b/GlitchyEngine/src/World/Components/TransformComponent.bf similarity index 95% rename from GlitchyEngine/src/World/TransformComponent.bf rename to GlitchyEngine/src/World/Components/TransformComponent.bf index 747de1e..21e919f 100644 --- a/GlitchyEngine/src/World/TransformComponent.bf +++ b/GlitchyEngine/src/World/Components/TransformComponent.bf @@ -20,6 +20,7 @@ namespace GlitchyEngine.World /// The frame when the transform was recalculated public uint Frame; + // TODO: probably use UUID public EcsEntity Parent { get => _parent; @@ -43,7 +44,10 @@ namespace GlitchyEngine.World _localTransform = value; - Matrix.Decompose(_localTransform, out _position, out _rotation, out _scale); + Matrix.Decompose(_localTransform, out _position, let rotation, out _scale); + + Rotation = rotation; + IsDirty = true; } } diff --git a/GlitchyEngine/src/World/DebugNameComponent.bf b/GlitchyEngine/src/World/DebugNameComponent.bf deleted file mode 100644 index f2c86bf..0000000 --- a/GlitchyEngine/src/World/DebugNameComponent.bf +++ /dev/null @@ -1,26 +0,0 @@ -using System; -namespace GlitchyEngine.World -{ - struct DebugNameComponent : IDisposableComponent - { - private String _debugName; - - public String DebugName - { - get => _debugName; - set mut => SetName(value); - } - - public void SetName(StringView name) mut - { - delete _debugName; - - _debugName = new String(name); - } - - public void Dispose() mut - { - DeleteAndNullify!(_debugName); - } - } -} diff --git a/GlitchyEngine/src/World/EcsEntity.bf b/GlitchyEngine/src/World/EcsEntity.bf index 134561a..0859cbc 100644 --- a/GlitchyEngine/src/World/EcsEntity.bf +++ b/GlitchyEngine/src/World/EcsEntity.bf @@ -11,10 +11,10 @@ namespace GlitchyEngine.World // Data: Version Index [Inline] - internal uint32 Version => (uint32)this; + public uint32 Version => (uint32)this; [Inline] - internal uint32 Index => (uint32)(this >> 32); + public uint32 Index => (uint32)(this >> 32); [Inline] static internal EcsEntity CreateEntityID(uint32 index, uint32 version) diff --git a/GlitchyEngine/src/World/EcsWorld.bf b/GlitchyEngine/src/World/EcsWorld.bf index e17a685..c82edc8 100644 --- a/GlitchyEngine/src/World/EcsWorld.bf +++ b/GlitchyEngine/src/World/EcsWorld.bf @@ -8,7 +8,7 @@ namespace GlitchyEngine.World { public class EcsWorld { - const int MaxEntities = 1024; + const int MaxEntities = 16348; internal typealias BitmaskEntry = (EcsEntity ID, BitArray ComponentMask); internal List _entities = new .(); @@ -149,6 +149,28 @@ namespace GlitchyEngine.World } } + /// Returns whether the given entity is valid or not. + public bool IsValid(EcsEntity entity) + { + if(entity.Index > _entities.Count) + return false; + + var listEntity = ref _entities[entity.Index]; + + return entity == listEntity.ID; + } + + /// Returns the entity with the same ID and the current version. Or null, if no such entity exists. + public Result GetCurrentVersion(EcsEntity entity) + { + if(entity.Index > _entities.Count) + return .Err; + + var listEntity = ref _entities[entity.Index]; + + return listEntity.ID; + } + /** * Assigns a component of type T to the specified entity and returns it. */ @@ -208,7 +230,7 @@ namespace GlitchyEngine.World listEntity.ComponentMask[entry.Id] = false; } - + public void RemoveComponent(EcsEntity entity) where T : struct, new, IDisposableComponent { if(entity.Index > _entities.Count) diff --git a/GlitchyEngine/src/World/EditorCamera.bf b/GlitchyEngine/src/World/EditorCamera.bf new file mode 100644 index 0000000..a71e450 --- /dev/null +++ b/GlitchyEngine/src/World/EditorCamera.bf @@ -0,0 +1,338 @@ +using GlitchyEngine; +using GlitchyEngine.Renderer; +using GlitchyEngine.Math; +using System; +using GlitchyEngine.Events; + +namespace GlitchyEngine.World +{ + struct EditorCamera : Camera, IDisposable + { + private Vector3 _position; + private Quaternion _rotation; + + private Vector3 _focalPosition = .Zero; + private float _focalDistance = 5.0f; + + private float _cameraTranslationSpeed = 2.0f; + private float _cameraRotationSpeedX = 0.001f; + private float _cameraRotationSpeedY = 0.001f; + private float _cameraFastFactor = 10f; + + private Matrix _view; + + private float _fovY; + private float _nearPlane; + private float _aspectRatio; + + private RenderTargetGroup _renderTarget = null; + + internal bool BindMouse; + internal uint8 MouseCooldown; + + private bool _isAltMode = false; + + public Matrix View => _view; + + // Gets whether or not the camera is currently being moved. + public bool InUse => BindMouse; + + /// If true, the camera can be rotated/moved + public bool AllowMove; + + public RenderTargetGroup RenderTarget + { + get => _renderTarget; + set mut + { + if (_renderTarget == value) + return; + + SetReference!(_renderTarget, value); + } + } + + public Vector3 Position + { + get => _position; + set mut + { + if (_position == value) + return; + + _position = value; + UpdateView(); + } + } + + public Quaternion Rotation + { + get => _rotation; + set mut + { + if (_rotation == value) + return; + + _rotation = value; + UpdateView(); + } + } + + public Vector3 RotationEuler + { + get => Quaternion.ToEulerAngles(_rotation); + set mut + { + Quaternion quat = Quaternion.FromEulerAngles(value.Y, value.X, value.Z); + if (_rotation == quat) + return; + + _rotation = quat; + UpdateView(); + } + } + + public (Vector3 Axis, float Angle) RotationAxisAngle + { + get => _rotation.ToAxisAngle(); + set mut + { + Quaternion quat = Quaternion.FromAxisAngle(value.Axis, value.Angle); + if (_rotation == quat) + return; + + _rotation = quat; + UpdateView(); + } + } + + public float FovY + { + get => _fovY; + set mut + { + if (_fovY == value) + return; + + _fovY = value; + UpdateProjection(); + } + } + + public float NearPlane + { + get => _nearPlane; + set mut + { + if (_nearPlane == value) + return; + + _nearPlane = value; + UpdateProjection(); + } + } + + public float AspectRatio + { + get => _aspectRatio; + set mut + { + if (_aspectRatio == value) + return; + + _aspectRatio = value; + UpdateProjection(); + } + } + + public this(Vector3 position, Quaternion rotation, float fovY, float nearPlane, float aspectRatio) + { + _position = position; + _rotation = rotation; + _fovY = fovY; + _nearPlane = nearPlane; + _aspectRatio = aspectRatio; + + UpdateView(); + UpdateProjection(); + } + + public void Update(GameTime gameTime) mut + { + Debug.Profiler.ProfileFunction!(); + + BindMouse = false; + + if (AllowMove) + { + if (Input.IsKeyPressed(.Alt)) + { + AltController(gameTime); + _isAltMode = true; + } + else + { + FirstPersonController(gameTime); + _isAltMode = false; + } + } + + if (MouseCooldown != 0) + MouseCooldown--; + } + + void FirstPersonController(GameTime gameTime) mut + { + if (!Input.IsMouseButtonPressed(.RightButton)) + return; + + BindMouse = true; + + bool transformChanged = false; + + Vector3 movement = .(); + + if(Input.IsKeyPressed(Key.W)) + movement.Z += 1; + if(Input.IsKeyPressed(Key.S)) + movement.Z -= 1; + + if(Input.IsKeyPressed(Key.A)) + movement.X -= 1; + if(Input.IsKeyPressed(Key.D)) + movement.X += 1; + + if(Input.IsKeyPressed(Key.Space)) + movement.Y += 1; + if(Input.IsKeyPressed(Key.Control)) + movement.Y -= 1; + + if(movement != .Zero) + { + movement.Normalize(); + + if(Input.IsKeyPressed(Key.Shift)) + movement *= _cameraFastFactor; + + movement *= (float)(gameTime.DeltaTime) * _cameraTranslationSpeed; + + Vector4 delta = Vector4(movement, 1.0f) * _view; + + _position += delta.XYZ; + + transformChanged = true; + } + + // Camera rotation + var mouseDelta = Input.GetMouseMovement(); + + float rotY = mouseDelta.X * _cameraRotationSpeedX; + float rotX = mouseDelta.Y * _cameraRotationSpeedY; + + if (MouseCooldown == 0) + { + Vector3 rotationEuler = RotationEuler + Vector3(rotX, rotY, 0); + _rotation = Quaternion.FromEulerAngles(rotationEuler.Y, rotationEuler.X, rotationEuler.Z); + + transformChanged = true; + } + + if (transformChanged) + UpdateView(); + } + + private float GetZoomSpeed() + { + float dist = _focalDistance * 0.2f; + dist = Math.Max(dist, 0.0f); + + float speed = Math.Pow(dist, 1.5f); + speed = Math.Min(speed, 100.0f); + + return speed; + } + + void AltController(GameTime gameTime) mut + { + bool transformChanged = false; + + BindMouse = Input.IsMouseButtonPressed(.LeftButton) || Input.IsMouseButtonPressed(.RightButton); + + var mouseDelta = Input.GetMouseMovement(); + + if (MouseCooldown == 0 && Input.IsMouseButtonPressed(.LeftButton) && mouseDelta != .()) + { + Vector2 movement = .( + -mouseDelta.X, + mouseDelta.Y); + + movement *= (float)(gameTime.DeltaTime) * _cameraTranslationSpeed * GetZoomSpeed(); + + Vector4 delta = Vector4(movement, 0.0f, 1.0f) * _view; + + _focalPosition += delta.XYZ; + + transformChanged = true; + } + + if (MouseCooldown == 0 && Input.IsMouseButtonPressed(.RightButton) && mouseDelta != .()) + { + float rotY = mouseDelta.X * _cameraRotationSpeedX; + float rotX = mouseDelta.Y * _cameraRotationSpeedY; + + Vector3 rotationEuler = RotationEuler + Vector3(rotX, rotY, 0); + _rotation = Quaternion.FromEulerAngles(rotationEuler.Y, rotationEuler.X, rotationEuler.Z); + + transformChanged = true; + } + + if (transformChanged) + UpdateView(); + } + + private void UpdateView() mut + { + Matrix viewRotation = Matrix.RotationQuaternion(Quaternion.Inverse(_rotation)); + + Vector4 offset = Vector4(0, 0, -_focalDistance, 1.0f) * viewRotation; + if (_isAltMode) + _position = _focalPosition + offset.XYZ; + else + _focalPosition = _position - offset.XYZ; + + _view = viewRotation * Matrix.Translation(-_position); + + //_view = (Matrix.Translation(_position) * Matrix.RotationQuaternion(_rotation)).Invert(); + } + + private void UpdateProjection() mut + { + //_projection = Matrix.InfinitePerspectiveProjection(_fovY, _aspectRatio, _nearPlane); + _projection = Matrix.PerspectiveProjection(_fovY, _aspectRatio, _nearPlane, 10000); + } + + public void OnViewportResize(uint32 sizeX, uint32 sizeY) mut + { + _aspectRatio = (float)sizeX / sizeY; + UpdateProjection(); + } + + public bool OnMouseScrolled(MouseScrolledEvent event) mut + { + if (_isAltMode) + { + _focalDistance = Math.Max(_focalDistance - GetZoomSpeed() * event.YOffset, 0.01f); + UpdateView(); + + return true; + } + + return false; + } + + public void Dispose() + { + _renderTarget?.ReleaseRef(); + } + } +} diff --git a/GlitchyEngine/src/World/Entity.bf b/GlitchyEngine/src/World/Entity.bf index a29d108..2b3e302 100644 --- a/GlitchyEngine/src/World/Entity.bf +++ b/GlitchyEngine/src/World/Entity.bf @@ -1,5 +1,6 @@ using System; using System.Collections; +using GlitchyEngine.Core; using internal GlitchyEngine.World; @@ -27,7 +28,7 @@ namespace GlitchyEngine.World public ChildEnumerator EnumerateChildren => .(this); - public bool IsValid => _entity.IsValid; + public bool IsValid => _entity.IsValid && _scene != null; public Entity? Parent { @@ -63,6 +64,16 @@ namespace GlitchyEngine.World } } + public UUID UUID => GetComponent().ID; + + public StringView Name + { + get => GetComponent().Name; + set => GetComponent().Name = value; + } + + public TransformComponent* Transform => GetComponent(); + public T* AddComponent(T value = T()) where T: struct, new { Log.EngineLogger.AssertDebug(!HasComponent(), scope $"Entity already has component."); @@ -85,6 +96,18 @@ namespace GlitchyEngine.World { return _scene._ecsWorld.HasComponent(_entity); } + + public bool TryGetComponent(out T* component) where T: struct, new + { + if (HasComponent()) + { + component = GetComponent(); + return true; + } + + component = null; + return false; + } public void RemoveComponent() where T: struct, new { diff --git a/GlitchyEngine/src/World/MeshRendererComponent.bf b/GlitchyEngine/src/World/MeshRendererComponent.bf deleted file mode 100644 index 303eb8c..0000000 --- a/GlitchyEngine/src/World/MeshRendererComponent.bf +++ /dev/null @@ -1,31 +0,0 @@ -using GlitchyEngine.Renderer; -using System; - -namespace GlitchyEngine.World -{ - /// A component that allows to render a mesh. - public struct MeshRendererComponent : IDisposableComponent - { - private Material _material; - - public Material Material - { - [Inline] - get => _material; - set mut - { - if(_material == value) - return; - - _material?.ReleaseRef(); - _material = value; - _material?.AddRef(); - } - } - - public void Dispose() mut - { - _material?.ReleaseRef(); - } - } -} diff --git a/GlitchyEngine/src/World/ParentComponent.bf b/GlitchyEngine/src/World/ParentComponent.bf deleted file mode 100644 index ef5e821..0000000 --- a/GlitchyEngine/src/World/ParentComponent.bf +++ /dev/null @@ -1,10 +0,0 @@ -namespace GlitchyEngine.World -{ - /** - * A Component that allows to specify a parent entity. - */ - public struct ParentComponent - { - public EcsEntity Entity; - } -} diff --git a/GlitchyEngine/src/World/Scene.bf b/GlitchyEngine/src/World/Scene.bf index f0ea702..be82ee6 100644 --- a/GlitchyEngine/src/World/Scene.bf +++ b/GlitchyEngine/src/World/Scene.bf @@ -2,31 +2,48 @@ using GlitchyEngine.Math; using GlitchyEngine.Renderer; using System; using System.Collections; +using Box2D; +using GlitchyEngine.Core; +using GlitchyEngine.Content; namespace GlitchyEngine.World { using internal ScriptableEntity; + using internal GlitchyEngine.World; - class Scene + class Scene : RefCounter { internal EcsWorld _ecsWorld = new .() ~ delete _; - + + internal b2World* _physicsWorld2D; + private Dictionary _onComponentAddedHandlers = new .() ~ delete _; + private uint32 _viewportWidth, _viewportHeight; + + // Maps ids to the entities they represent. + private Dictionary _idToEntity = new .() ~ delete _; + + public Entity ActiveCamera => { + Entity cameraEntity = .(); + + for (var (entity, camera) in _ecsWorld.Enumerate()) + { + if (camera.Primary && camera.RenderTarget != null) + { + cameraEntity = .(entity, this); + } + } + + cameraEntity + }; + public this() { - Entity entity = CreateEntity("Green Quad"); - entity.AddComponent(.(ColorRGBA(0.2f, 0.9f, 0.15f))); - - Entity entity2 = CreateEntity("Red Square"); - var v = entity2.AddComponent(.(ColorRGBA(0.95f, 0.1f, 0.3f))); - v.Sprite = new Texture2D("Textures/rocket.dds"); - v.Sprite.SamplerState = SamplerStateManager.PointClamp; - _onComponentAddedHandlers.Add(typeof(CameraComponent), (e, t, c) => { CameraComponent* cameraComponent = (.)c; - cameraComponent.Camera.SetViewportSize(e.Scene.ViewportWidth, e.Scene.ViewportHeight); + cameraComponent.Camera.SetViewportSize(e.Scene._viewportWidth, e.Scene._viewportHeight); }); } @@ -34,56 +51,232 @@ namespace GlitchyEngine.World { } - public void Update(GameTime gameTime) + public void CopyTo(Scene target) { - TransformSystem.Update(_ecsWorld); - - for (var (entity, script) in _ecsWorld.Enumerate()) + // Copy entities + for (let sourceHandle in _ecsWorld.Enumerate()) { - if (script.Instance == null) - { - script.Instance = script.InstantiateFunction(); - script.Instance._entity = Entity(entity, this); - script.Instance.[Friend]OnCreate(); - } + Entity sourceEntity = .(sourceHandle, this); - script.Instance.[Friend]OnUpdate(gameTime); + target.CreateEntity(sourceEntity.Name, sourceEntity.UUID); } - Camera* primaryCamera = null; - Matrix primaryCameraTransform = default; + // TODO: perhaps use reflection and comptime + // Copy components + //CopyComponents(this, target); /* Parent will be copied below*/ + CopyComponents(this, target); + CopyComponents(this, target); + CopyComponents(this, target); + CopyComponents(this, target); + CopyComponents(this, target); + CopyComponents(this, target); + CopyComponents(this, target); + CopyComponents(this, target); + CopyComponents(this, target); + CopyComponents(this, target); + CopyComponents(this, target); - for (var (entity, transform, camera) in _ecsWorld.Enumerate()) + // Copy transforms + for (let (sourceHandle, sourceTransform) in _ecsWorld.Enumerate()) { - if (camera.Primary) + Entity sourceEntity = Entity(sourceHandle, this); + Entity sourceParent = Entity(sourceTransform.Parent, this); + + Entity targetEntity = target.GetEntityByID(sourceEntity.UUID); + *targetEntity.Transform = *sourceTransform; + + if (sourceParent.IsValid) { - primaryCamera = &camera.Camera; - primaryCameraTransform = transform.WorldTransform; + Entity targetParent = target.GetEntityByID(sourceParent.UUID); + targetEntity.Parent = targetParent; } } - // Sprite renderer - if (primaryCamera != null) - { - Renderer2D.BeginScene(*primaryCamera, primaryCameraTransform); - - for (var (entity, transform, sprite) in _ecsWorld.Enumerate()) + target.OnViewportResize(_viewportWidth, _viewportHeight); + } + + private static void CopyComponents(Scene source, Scene target) where TComponent : struct, new + { + for (let (sourceHandle, sourceComponent) in source._ecsWorld.Enumerate()) + { + Entity sourceEntity = .(sourceHandle, source); + + Entity targetEntity = target.GetEntityByID(sourceEntity.UUID); + targetEntity.AddComponent(*sourceComponent); + } + } + + b2Vec2 _gravity2D = .(0.0f, -9.8f); + + static b2BodyType GetBox2DBodyType(Rigidbody2DComponent.BodyType bodyType) + { + switch (bodyType) + { + case .Static: + return .b2_staticBody; + case .Dynamic: + return .b2_dynamicBody; + case .Kinematic: + return .b2_kinematicBody; + default: + Log.EngineLogger.AssertDebug(false, "Unknown body type"); + return .b2_staticBody; + } + } + + public void OnRuntimeStart() + { + OnSimulationStart(); + } + + public void OnRuntimeStop() + { + OnSimulationStop(); + } + + public void OnSimulationStart() + { + _physicsWorld2D = Box2D.World.Create(ref _gravity2D); + + for (var entry in _ecsWorld.Enumerate()) + { + Entity entity = .(entry.Entity, this); + + var transform = entity.Transform; + var rigidBody = entry.Component; + + b2BodyDef def = .(); + def.type = GetBox2DBodyType(rigidBody.BodyType); + + // TODO: breaks with hierarchy + def.position = b2Vec2(transform.Position.X, transform.Position.Y); + def.angle = transform.RotationEuler.Z; + + b2Body* body = Box2D.World.CreateBody(_physicsWorld2D, &def); + Box2D.Body.SetFixedRotation(body, rigidBody.FixedRotation); + + rigidBody.RuntimeBody = body; + + if (entity.TryGetComponent(let boxCollider)) { - Renderer2D.DrawQuad(transform.WorldTransform, sprite.Sprite, sprite.Color); + b2Shape* boxShape = Box2D.Shape.CreatePolygon(); + Box2D.Shape.PolygonSetAsBox(boxShape, boxCollider.Size.X * transform.Scale.X, boxCollider.Size.Y * transform.Scale.Y); + Box2D.Shape.PolygonSetAsBoxWithCenterAngle(boxShape, boxCollider.Size.X * transform.Scale.X, boxCollider.Size.Y * transform.Scale.Y, ref boxCollider.b2Offset, 0.0f); + + b2FixtureDef fixtureDef = .(); + fixtureDef.shape = boxShape; + fixtureDef.density = boxCollider.Density; + fixtureDef.friction = boxCollider.Friction; + fixtureDef.restitution = boxCollider.Restitution; + fixtureDef.restitutionThreshold = boxCollider.RestitutionThreshold; + + b2Fixture* fixture = Box2D.Body.CreateFixture(body, &fixtureDef); + boxCollider.RuntimeFixture = fixture; } + + if (entity.TryGetComponent(let circleCollider)) + { + b2Shape* circleShape = Box2D.Shape.CreateCircle(); + Box2D.Shape.CircleSetPosition(circleShape, ref circleCollider.b2Offset); + Box2D.Shape.SetRadius(circleShape, circleCollider.Radius); + + b2FixtureDef fixtureDef = .(); + fixtureDef.shape = circleShape; + fixtureDef.density = circleCollider.Density; + fixtureDef.friction = circleCollider.Friction; + fixtureDef.restitution = circleCollider.Restitution; + fixtureDef.restitutionThreshold = circleCollider.RestitutionThreshold; + + b2Fixture* fixture = Box2D.Body.CreateFixture(body, &fixtureDef); + circleCollider.RuntimeFixture = fixture; + } + } + } + + public void OnSimulationStop() + { + Box2D.World.Delete(_physicsWorld2D); + _physicsWorld2D = null; + } + + public enum UpdateMode + { + /// No special update configuration (this does NOT mean nothing will be updated!) + None = 0x00, + /// Update editor-specific stuff + Editor = 0x01, + /// Update the physics related stuff + Physics = 0x02, + /// Update the runtume related stuff (e.g. execute scripts). Also run physics! + Runtime = 0x04 | Physics, + } + + public void Update(GameTime gameTime, UpdateMode mode) + { + Debug.Profiler.ProfileRendererFunction!(); + + TransformSystem.Update(_ecsWorld); + + if (mode.HasFlag(.Runtime)) + { + // Run scripts + for (var (entity, script) in _ecsWorld.Enumerate()) + { + if (script.Instance == null) + { + script.Instance = script.InstantiateFunction(); + script.Instance._entity = Entity(entity, this); + script.Instance.[Friend]OnCreate(); + } - Renderer2D.EndScene(); + script.Instance.[Friend]OnUpdate(gameTime); + } + } + + if (mode.HasFlag(.Physics)) + { + // Update 2D physics + { + const int32 velocityIterations = 6; + const int32 positionIterations = 2; + const int32 particleIterations = 2; + + Box2D.World.Step(_physicsWorld2D, gameTime.DeltaTime, velocityIterations, positionIterations, particleIterations); + + // Retrieve transform from Box2D + for (var entry in _ecsWorld.Enumerate()) + { + Entity entity = .(entry.Entity, this); + + var transform = entity.Transform; + var rigidbody = entry.Component; + + b2Body* body = rigidbody.RuntimeBody; + b2Vec2 position = Box2D.Body.GetPosition(body); + float angle = Box2D.Body.GetAngle(body); + + transform.Position = .(position.x, position.y, transform.Position.Z); + transform.RotationEuler = .(transform.RotationEuler.XY, angle); + } + } } } /// Creates a new Entity with the given name. - public Entity CreateEntity(String name = "") + public Entity CreateEntity(StringView name = "", UUID id = default) { Entity entity = Entity(_ecsWorld.NewEntity(), this); entity.AddComponent(); - let nameComponent = entity.AddComponent(); - nameComponent.SetName(name.IsEmpty ? "Entity" : name); + let nameComponent = entity.AddComponent(); + nameComponent.Name = (name.IsEmpty ? "Entity" : name); + + // If no id is given generate a random one. + IDComponent idComponent = (id == default) ? IDComponent() : IDComponent(id); + + entity.AddComponent(idComponent); + + _idToEntity.Add(idComponent.ID, entity.Handle); return entity; } @@ -94,6 +287,8 @@ namespace GlitchyEngine.World */ public void DestroyEntity(Entity entity, bool destroyChildren = false) { + _idToEntity.Remove(entity.UUID); + if (destroyChildren) { for (Entity child in entity.EnumerateChildren) @@ -105,17 +300,25 @@ namespace GlitchyEngine.World _ecsWorld.RemoveEntity(entity.Handle); } - private uint32 ViewportWidth, ViewportHeight; + public Result GetEntityByID(UUID id) + { + if (_idToEntity.TryGetValue(id, let ecsEntity)) + { + return .Ok(Entity(ecsEntity, this)); + } + + return .Err; + } /// Sets the size of the viewport into which the scene will be rendered. public void OnViewportResize(uint32 width, uint32 height) { - ViewportWidth = width; - ViewportHeight = height; + _viewportWidth = width; + _viewportHeight = height; for (var (entity, cameraComponent) in _ecsWorld.Enumerate()) { - if (!cameraComponent.FixedAspectRatio) + if (!cameraComponent.Camera.FixedAspectRatio) { cameraComponent.Camera.SetViewportSize(width, height); } @@ -129,5 +332,25 @@ namespace GlitchyEngine.World handler(entity, componentType, component); } } + + public WorldEnumerator GetEntities() where TComponent : struct + { + return _ecsWorld.Enumerate(); + } + + public WorldEnumerator GetEntities() + where TComponent1 : struct + where TComponent2 : struct + { + return _ecsWorld.Enumerate(); + } + + public WorldEnumerator GetEntities() + where TComponent1 : struct + where TComponent2 : struct + where TComponent3 : struct + { + return _ecsWorld.Enumerate(); + } } -} \ No newline at end of file +} diff --git a/GlitchyEngine/src/World/SceneRenderer.bf b/GlitchyEngine/src/World/SceneRenderer.bf new file mode 100644 index 0000000..39aec61 --- /dev/null +++ b/GlitchyEngine/src/World/SceneRenderer.bf @@ -0,0 +1,212 @@ +using GlitchyEngine.Renderer; +using GlitchyEngine.Content; +using GlitchyEngine.Math; + +namespace GlitchyEngine.World; + +using internal GlitchyEngine.World; + +class SceneRenderer +{ + public Scene Scene { get; set; } + + private RenderTargetGroup _compositeTarget ~ _.ReleaseRef(); + + // Temporary target for camera. Needs to change as soon as we support multiple cameras + private RenderTargetGroup _cameraTarget ~ _.ReleaseRef(); + + private uint32 _viewportWidth; + private uint32 _viewportHeight; + + private AssetHandle _gammaCorrectEffect; + + public RenderTargetGroup CompositeTarget => _compositeTarget; + + public this() + { + RenderTargetGroupDescription desc = .(100, 100, + TargetDescription[]( + RenderTargetFormat.R16G16B16A16_Float, + .(RenderTargetFormat.R32_UInt) {ClearColor = .UInt(uint32.MaxValue)}), + RenderTargetFormat.D24_UNorm_S8_UInt); + _compositeTarget = new RenderTargetGroup(desc); + + _cameraTarget = new RenderTargetGroup(.(){ + Width = 100, + Height = 100, + ColorTargetDescriptions = TargetDescription[]( + .(.R16G16B16A16_Float), + .(.R32_UInt) + ), + DepthTargetDescription = .(.D24_UNorm_S8_UInt) + }); + + _gammaCorrectEffect = Content.LoadAsset("Shaders/GammaCorrect.hlsl");//Application.Get().EffectLibrary.Load("content/Shaders/GammaCorrect.hlsl"); + } + + /// Sets the size of the viewport into which the scene will be rendered. + public void OnViewportResize(uint32 width, uint32 height) + { + _viewportWidth = width; + _viewportHeight = height; + + _compositeTarget.Resize(_viewportWidth, _viewportHeight); + _cameraTarget.Resize(_viewportWidth, _viewportHeight); + } + + public void RenderRuntime(GameTime gameTime, RenderTargetGroup finalTarget) + { + Debug.Profiler.ProfileRendererFunction!(); + + // Find camera + Camera* primaryCamera = null; + Matrix primaryCameraTransform = default; + RenderTargetGroup renderTarget = null; + + for (var (entity, transform, camera) in Scene._ecsWorld.Enumerate()) + { + if (camera.Primary)// && camera.RenderTarget != null) + { + primaryCamera = &camera.Camera; + primaryCameraTransform = transform.WorldTransform; + // TODO: bind render targets to cameras + // renderTarget = camera.RenderTarget..AddRef(); + } + } + + if (primaryCamera == null) + return; + + finalTarget.AddRef(); + + renderTarget = _cameraTarget..AddRef(); + + // 3D render + Renderer.BeginScene(*primaryCamera, primaryCameraTransform, renderTarget, _compositeTarget); + + for (var (entity, transform, mesh, meshRenderer) in Scene._ecsWorld.Enumerate()) + { + Renderer.Submit(mesh.Mesh, meshRenderer.Material, entity, transform.WorldTransform); + } + + for (var (entity, transform, light) in Scene._ecsWorld.Enumerate()) + { + Renderer.Submit(light.SceneLight, transform.WorldTransform); + } + + Renderer.EndScene(); + + renderTarget.ReleaseRef(); + + // TODO: alphablending (handle in Renderer2D) + // TODO: 2D-Postprocessing requires rendering into separate target instead of directly into compositeTarget + + RenderCommand.SetRenderTargetGroup(_compositeTarget); + RenderCommand.BindRenderTargets(); + + // Sprite renderer + Renderer2D.BeginScene(*primaryCamera, primaryCameraTransform, .BackToFront); + + for (var (entity, transform, sprite) in Scene._ecsWorld.Enumerate()) + { + Renderer2D.DrawSprite(transform.WorldTransform, sprite, entity.Index); + } + + for (var (entity, transform, circle) in Scene._ecsWorld.Enumerate()) + { + Renderer2D.DrawCircle(transform.WorldTransform, circle, entity.Index); + } + + Renderer2D.EndScene(); + + // Gamma correct composit target and draw it into viewport + { + RenderCommand.UnbindRenderTargets(); + RenderCommand.SetRenderTargetGroup(finalTarget, false); + RenderCommand.BindRenderTargets(); + + Effect gammaEffect = Content.GetAsset(_gammaCorrectEffect); + + gammaEffect.SetTexture("Texture", _compositeTarget, 0); + // TODO: iiihhh + gammaEffect.ApplyChanges(); + gammaEffect.Bind(); + + FullscreenQuad.Draw(); + } + + finalTarget.ReleaseRef(); + } + + public void RenderEditor(GameTime gameTime, EditorCamera camera, RenderTargetGroup viewportTarget, delegate void() DebugDraw3D, delegate void() DrawDebug2D) + { + Debug.Profiler.ProfileRendererFunction!(); + + viewportTarget.AddRef(); + + // 3D render + Renderer.BeginScene(camera, _compositeTarget); + + for (var (entity, transform, mesh, meshRenderer) in Scene._ecsWorld.Enumerate()) + { + if (mesh.Mesh == .Invalid || meshRenderer.Material == .Invalid) + continue; + + Renderer.Submit(mesh.Mesh, meshRenderer.Material, entity, transform.WorldTransform); + } + + for (var (entity, transform, light) in Scene._ecsWorld.Enumerate()) + { + Renderer.Submit(light.SceneLight, transform.WorldTransform); + } + + DebugDraw3D(); + + Renderer.EndScene(); + + // TODO: alphablending (handle in Renderer2D) + // TODO: 2D-Postprocessing requires rendering into separate target instead of directly into compositeTarget + + RenderCommand.SetRenderTargetGroup(_compositeTarget); + RenderCommand.BindRenderTargets(); + + // Sprite renderer + Renderer2D.BeginScene(camera, .BackToFront); + + for (var (entity, transform, sprite) in Scene._ecsWorld.Enumerate()) + { + Renderer2D.DrawSprite(transform.WorldTransform, sprite, entity.Index); + } + + for (var (entity, transform, circle) in Scene._ecsWorld.Enumerate()) + { + Renderer2D.DrawCircle(transform.WorldTransform, circle, entity.Index); + } + + Renderer2D.EndScene(); + + Renderer2D.BeginScene(camera, .BackToFront); + + DrawDebug2D(); + + Renderer2D.EndScene(); + + // Gamma correct composit target and draw it into viewport + { + RenderCommand.UnbindRenderTargets(); + RenderCommand.SetRenderTargetGroup(viewportTarget, false); + RenderCommand.BindRenderTargets(); + + Effect gammaEffect = Content.GetAsset(_gammaCorrectEffect); + + gammaEffect.SetTexture("Texture", _compositeTarget, 0); + // TODO: iiihhh + gammaEffect.ApplyChanges(); + gammaEffect.Bind(); + + FullscreenQuad.Draw(); + } + + viewportTarget.ReleaseRef(); + } +} diff --git a/GlitchyEngine/src/World/SceneSerializer.bf b/GlitchyEngine/src/World/SceneSerializer.bf new file mode 100644 index 0000000..6c40bed --- /dev/null +++ b/GlitchyEngine/src/World/SceneSerializer.bf @@ -0,0 +1,619 @@ +using Bon; +using Bon.Integrated; +using System; +using System.Reflection; +using System.IO; +using GlitchyEngine.Math; +using GlitchyEngine.Core; +using System.Collections; +using GlitchyEngine.Renderer; +using GlitchyEngine.Content; + +namespace GlitchyEngine.World; + +using internal GlitchyEngine.World; + +class SceneSerializer +{ + private Scene _scene; + + // Maps from ParentID to ChildEntity + private Dictionary _parentIdToChild; + + private List<(Entity Entity, UUID ParentId)> _entitiesMissingParent; + + public this(Scene scene) + { + _scene = scene; + } + + public void Serialize(StringView filePath) + { + Debug.Profiler.ProfileResourceFunction!(); + + String buffer = scope String(); + let writer = scope BonWriter(buffer, true); + var length = Serialize.Start(writer); + + gBonEnv.serializeFlags |= .IncludeDefault | .Verbose; + + using (writer.ObjectBlock()) + { + // TODO: Scene name goes here! + Serialize.Value(writer, "Name", "Scene name here pls!!!"); + + writer.Identifier("Entities"); + + using (writer.ArrayBlock()) + { + for (EcsEntity e in _scene._ecsWorld.Enumerate()) + { + Entity entity = .(e, _scene); + + // TODO: also serialize object with EditorComponent (e.g. to save the location of the editor camera) + if (entity.HasComponent()) + continue; + + SerializeEntity(writer, entity); + } + } + + writer.EntryEnd(); + } + + Serialize.End(writer, length); + + String targetDirectory = Path.GetDirectoryPath(filePath, .. scope String()); + Directory.CreateDirectory(targetDirectory); + + File.WriteAllText(filePath, buffer); + } + + void SerializeEntity(BonWriter writer, Entity entity) + { + writer.EntryStart(); + + using (writer.ObjectBlock()) + { + Serialize.Value(writer, "Id", entity.UUID); + + SerializeComponent(writer, entity, "EditorComponent", scope (component) => {}); + + SerializeComponent(writer, entity, "NameComponent", scope (component) => + { + Serialize.Value(writer, "Name", component.Name); + }); + + SerializeComponent(writer, entity, "SpriteRendererComponent", scope (component) => + { + Serialize.Value(writer, "Color", component.Color); + Serialize.Value(writer, "Sprite", component.Sprite); + Serialize.Value(writer, "UvTransform", component.UvTransform); + }); + SerializeComponent(writer, entity, "CircleRendererComponent", scope (component) => + { + Serialize.Value(writer, "Color", component.Color); + Serialize.Value(writer, "InnerRadius", component.InnerRadius); + Serialize.Value(writer, "Sprite", component.Sprite); + Serialize.Value(writer, "UvTransform", component.UvTransform); + }); + + SerializeComponent(writer, entity, "TransformComponent", scope (component) => + { + // TODO: Use GUIDs + if (component.Parent != .InvalidEntity) + { + Entity parent = Entity(component.Parent, _scene); + Serialize.Value(writer, "ParentId", parent.UUID); + } + + Serialize.Value(writer, "Position", component.Position); + Serialize.Value(writer, "Rotation", component.Rotation); + Serialize.Value(writer, "Scale", component.Scale); + + Serialize.Value(writer, "EditorEulerRotation", component.EditorRotationEuler); + }); + + SerializeComponent(writer, entity, "CameraComponent", scope (component) => + { + SceneCamera camera = component.Camera; + + Serialize.Value(writer, "Primary", component.Primary); + + // TODO: Render target + + Serialize.Value(writer, "ProjectionType", camera.ProjectionType); + + Serialize.Value(writer, "PerspectiveFovY", camera.PerspectiveFovY); + Serialize.Value(writer, "PerspectiveNearPlane", camera.PerspectiveNearPlane); + Serialize.Value(writer, "PerspectiveFarPlane", camera.PerspectiveFarPlane); + Serialize.Value(writer, "OrthographicHeight", camera.OrthographicHeight); + Serialize.Value(writer, "OrthographicNearPlane", camera.OrthographicNearPlane); + Serialize.Value(writer, "OrthographicFarPlane", camera.OrthographicFarPlane); + Serialize.Value(writer, "AspectRatio", camera.AspectRatio); + Serialize.Value(writer, "FixedAspectRatio", camera.FixedAspectRatio); + }); + + // TODO: native script component + + SerializeComponent(writer, entity, "LightComponent", scope (component) => + { + SceneLight light = component.SceneLight; + + Serialize.Value(writer, "LightType", light.LightType); + + Serialize.Value(writer, "Illuminance", light.Illuminance); + + Serialize.Value(writer, "Color", light.Color); + }); + + SerializeComponent(writer, entity, "Rigidbody2D", scope (component) => + { + Serialize.Value(writer, "BodyType", component.BodyType); + + Serialize.Value(writer, "FixedRotation", component.FixedRotation); + }); + + SerializeComponent(writer, entity, "BoxCollider2D", scope (component) => + { + Serialize.Value(writer, "Offset", component.Offset); + Serialize.Value(writer, "Size", component.Size); + + Serialize.Value(writer, "Density", component.Density); + Serialize.Value(writer, "Friction", component.Friction); + Serialize.Value(writer, "Restitution", component.Restitution); + Serialize.Value(writer, "RestitutionThreshold", component.RestitutionThreshold); + }); + + SerializeComponent(writer, entity, "CircleCollider2D", scope (component) => + { + Serialize.Value(writer, "Offset", component.Offset); + Serialize.Value(writer, "Radius", component.Radius); + + Serialize.Value(writer, "Density", component.Density); + Serialize.Value(writer, "Friction", component.Friction); + Serialize.Value(writer, "Restitution", component.Restitution); + Serialize.Value(writer, "RestitutionThreshold", component.RestitutionThreshold); + }); + + SerializeComponent(writer, entity, "MeshComponent", scope (component) => + { + Serialize.Value(writer, "Mesh", component.Mesh); + }); + + SerializeComponent(writer, entity, "MeshRendererComponent", scope (component) => + { + Serialize.Value(writer, "Material", component.Material); + }); + } + + writer.EntryEnd(); + } + + static void SerializeComponent(BonWriter writer, Entity entity, String identifier, delegate void(T* component) serialize) where T : struct, new + { + if (!entity.HasComponent()) + return; + + var component = entity.GetComponent(); + + writer.Identifier(identifier); + + using (writer.ObjectBlock()) + { + serialize(component); + } + writer.EntryEnd(); + } + + public void SerializeRuntime(StringView filePath) + { + Runtime.NotImplemented(); + } + + public Result Deserialize(StringView filePath) + { + Debug.Profiler.ProfileResourceFunction!(); + + _parentIdToChild = scope Dictionary(); + _entitiesMissingParent = scope List<(Entity Entity, UUID ParentId)>(); + + String buffer = scope String(); + File.ReadAllText(filePath, buffer); + + let reader = scope BonReader(); + Try!(reader.Setup(buffer)); + Try!(Deserialize.Start(reader)); + + Try!(reader.ObjectBlock()); + + // TODO: Scene name goes here! + String testName; + Deserialize.Value(reader, "Name", out testName); + delete testName; + + Try!(reader.EntryEnd()); + + if (Try!(reader.Identifier()) != "Entities") + return .Err; + + Try!(reader.ArrayBlock()); + + bool first = true; + while (reader.ArrayHasMore()) + { + if (!first) + { + Try!(reader.EntryEnd()); + } + + Try!(DeserializeEntity(reader)); + + first = false; + } + + Try!(reader.ArrayBlockEnd()); + + Try!(reader.ObjectBlockEnd()); + + Try!(Deserialize.End(reader)); + + // Find parents for entities that don't have their parent yet + for ((Entity Entity, UUID ParentId) entry in _entitiesMissingParent) + { + let parentResult = _scene.GetEntityByID(entry.ParentId); + + Log.EngineLogger.Assert(parentResult case .Ok, "Parent entity does not exist."); + + if (parentResult case .Ok(let parent)) + { + entry.Entity.Parent = parent; + } + } + + return .Ok; + } + + private Result DeserializeEntity(BonReader reader) + { + /*mixin DeserializeAsset(StringView identifier) where T : Asset + { + Asset asset = null; + + Try!(Deserialize.Value(reader, identifier, out asset)); + + if (asset != null && !(asset is T)) + { + Log.EngineLogger.Error($"Asset {asset.Identifier} is not a {nameof(T)}."); + return .Err; + } + + (T)asset + }*/ + + mixin DeserializeAssetHandle(StringView identifier) where T : Asset + { + Asset asset = null; + + Try!(Deserialize.Value(reader, identifier, out asset)); + + if (asset != null && !(asset is T)) + { + Log.EngineLogger.Error($"Asset {asset.Identifier} is not a {nameof(T)}."); + return .Err; + } + + asset?.Handle ?? .Invalid + } + + Try!(reader.ObjectBlock()); + + Deserialize.Value(reader, "Id", let uuid); + + Entity entity = _scene.CreateEntity("", UUID(uuid)); + + while(reader.ObjectHasMore()) + { + Try!(reader.EntryEnd()); + + StringView identifier = Try!(reader.Identifier()); + + switch(identifier) + { + case "EditorComponent": + Try!(DeserializeComponent(reader, entity, scope (component) => { return .Ok; })); + case "NameComponent": + Try!(DeserializeComponent(reader, entity, scope (component) => + { + String name; + + Deserialize.Value(reader, "Name", out name); + + component.Name = name; + + delete name; + + return .Ok; + })); + case "SpriteRendererComponent": + Try!(DeserializeComponent(reader, entity, scope (component) => + { + Try!(Deserialize.Value(reader, "Color", out component.Color)); + reader.EntryEnd(); + Try!(Deserialize.Value(reader, "Sprite", out component.Sprite)); + reader.EntryEnd(); + Try!(Deserialize.Value(reader, "UvTransform", out component.UvTransform)); + + return .Ok; + })); + case "CircleRendererComponent": + Try!(DeserializeComponent(reader, entity, scope (component) => + { + Try!(Deserialize.Value(reader, "Color", out component.Color)); + reader.EntryEnd(); + Try!(Deserialize.Value(reader, "InnerRadius", out component.InnerRadius)); + reader.EntryEnd(); + Try!(Deserialize.Value(reader, "Sprite", out component.Sprite)); + reader.EntryEnd(); + Try!(Deserialize.Value(reader, "UvTransform", out component.UvTransform)); + + return .Ok; + })); + case "TransformComponent": + Try!(DeserializeComponent(reader, entity, scope (component) => + { + let nextId = Try!(reader.Identifier()); + if (nextId == "ParentId") + { + UUID pId; + Deserialize.Value(reader, out pId); + reader.EntryEnd(); + + var parentEntity = _scene.GetEntityByID(pId); + + if (parentEntity case .Ok(let parent)) + { + component.Parent = parent.Handle; + } + else + { + _entitiesMissingParent.Add((entity, pId)); + } + + Deserialize.Value(reader, "Position", out component.[Friend]_position); + reader.EntryEnd(); + } + else if (nextId == "Position") + { + Deserialize.Value(reader, out component.[Friend]_position); + reader.EntryEnd(); + } + else + { + return .Err; + } + + Deserialize.Value(reader, "Rotation", out component.[Friend]_rotation); + reader.EntryEnd(); + Deserialize.Value(reader, "Scale", out component.[Friend]_scale); + reader.EntryEnd(); + + Deserialize.Value(reader, "EditorEulerRotation", out component.[Friend]_editorRotationEuler); + + component.IsDirty = true; + + return .Ok; + })); + case "CameraComponent": + Try!(DeserializeComponent(reader, entity, scope (component) => + { + SceneCamera camera = component.Camera; + + Deserialize.Value(reader, "Primary", out component.Primary); + reader.EntryEnd(); + + // TODO: Render target + + Deserialize.Value(reader, "ProjectionType", out camera.[Friend]_projectionType); + reader.EntryEnd(); + + Deserialize.Value(reader, "PerspectiveFovY", out camera.[Friend]_perspectiveFovY); + reader.EntryEnd(); + Deserialize.Value(reader, "PerspectiveNearPlane", out camera.[Friend]_perspectiveNearPlane); + reader.EntryEnd(); + Deserialize.Value(reader, "PerspectiveFarPlane", out camera.[Friend]_perspectiveFarPlane); + reader.EntryEnd(); + Deserialize.Value(reader, "OrthographicHeight", out camera.[Friend]_orthographicHeight); + reader.EntryEnd(); + Deserialize.Value(reader, "OrthographicNearPlane", out camera.[Friend]_orthographicNearPlane); + reader.EntryEnd(); + Deserialize.Value(reader, "OrthographicFarPlane", out camera.[Friend]_orthographicFarPlane); + reader.EntryEnd(); + Deserialize.Value(reader, "AspectRatio", out camera.[Friend]_aspectRatio); + reader.EntryEnd(); + Deserialize.Value(reader, "FixedAspectRatio", out camera.[Friend]_fixedAspectRatio); + + camera.[Friend]CalculateProjection(); + + return .Ok; + })); + // TODO: native script component + case "LightComponent": + Try!(DeserializeComponent(reader, entity, scope (component) => + { + ref SceneLight light = ref component.SceneLight; + + Deserialize.Value(reader, "LightType", out light.[Friend]_type); + reader.EntryEnd(); + + Deserialize.Value(reader, "Illuminance", out light.[Friend]_illuminance); + reader.EntryEnd(); + + Deserialize.Value(reader, "Color", out light.[Friend]_color); + return .Ok; + })); + case "Rigidbody2D": + Try!(DeserializeComponent(reader, entity, scope (component) => + { + Deserialize.Value(reader, "BodyType", out component.BodyType); + reader.EntryEnd(); + + Deserialize.Value(reader, "FixedRotation", out component.FixedRotation); + + return .Ok; + })); + case "BoxCollider2D": + Try!(DeserializeComponent(reader, entity, scope (component) => + { + Deserialize.Value(reader, "Offset", out component.Offset); + reader.EntryEnd(); + Deserialize.Value(reader, "Size", out component.Size); + reader.EntryEnd(); + + Deserialize.Value(reader, "Density", out component.Density); + reader.EntryEnd(); + Deserialize.Value(reader, "Friction", out component.Friction); + reader.EntryEnd(); + Deserialize.Value(reader, "Restitution", out component.Restitution); + reader.EntryEnd(); + Deserialize.Value(reader, "RestitutionThreshold", out component.RestitutionThreshold); + + return .Ok; + })); + case "CircleCollider2D": + Try!(DeserializeComponent(reader, entity, scope (component) => + { + Deserialize.Value(reader, "Offset", out component.Offset); + reader.EntryEnd(); + Deserialize.Value(reader, "Radius", out component.Radius); + reader.EntryEnd(); + + Deserialize.Value(reader, "Density", out component.Density); + reader.EntryEnd(); + Deserialize.Value(reader, "Friction", out component.Friction); + reader.EntryEnd(); + Deserialize.Value(reader, "Restitution", out component.Restitution); + reader.EntryEnd(); + Deserialize.Value(reader, "RestitutionThreshold", out component.RestitutionThreshold); + + return .Ok; + })); + case "MeshComponent": + Try!(DeserializeComponent(reader, entity, scope (component) => + { + Try!(Deserialize.Value(reader, "Mesh", out component.Mesh)); + + return .Ok; + })); + case "MeshRendererComponent": + Try!(DeserializeComponent(reader, entity, scope (component) => + { + Try!(Deserialize.Value(reader, "Material", out component.Material)); + + return .Ok; + })); + default: + Log.EngineLogger.AssertDebug(false, "Unknown component type"); + //return .Err; + } + } + + Try!(reader.ObjectBlockEnd()); + + return .Ok; + + /*writer.EntryStart(); + + using (writer.ObjectBlock()) + { + // TODO: Entity GUID goes here! + Serialize.Value(writer, "Id", entity.Handle.Index); + + SerializeComponent(writer, entity, "EditorComponent", scope (component) => {}); + + SerializeComponent(writer, entity, "NameComponent", scope (component) => + { + Serialize.Value(writer, "Name", component.DebugName); + }); + + SerializeComponent(writer, entity, "SpriterRendererComponent", scope (component) => + { + // TODO: Texture + + Serialize.Value(writer, "Color", component.Color); + }); + + SerializeComponent(writer, entity, "TransformComponent", scope (component) => + { + // TODO: Use GUIDs + if (component.Parent != .InvalidEntity) + Serialize.Value(writer, "ParentId", component.Parent.Index); + + Serialize.Value(writer, "Position", component.Position); + Serialize.Value(writer, "Rotation", component.Rotation); + Serialize.Value(writer, "Scale", component.Scale); + + Serialize.Value(writer, "EditorEulerRotation", component.EditorRotationEuler); + }); + + SerializeComponent(writer, entity, "CameraComponent", scope (component) => + { + SceneCamera camera = component.Camera; + + Serialize.Value(writer, "Primary", component.Primary); + + // TODO: Render target + + Serialize.Value(writer, "ProjectionType", camera.ProjectionType); + + Serialize.Value(writer, "PerspectiveFovY", camera.PerspectiveFovY); + Serialize.Value(writer, "PerspectiveNearPlane", camera.PerspectiveNearPlane); + Serialize.Value(writer, "PerspectiveFarPlane", camera.PerspectiveFarPlane); + Serialize.Value(writer, "OrthographicHeight", camera.OrthographicHeight); + Serialize.Value(writer, "OrthographicNearPlane", camera.OrthographicNearPlane); + Serialize.Value(writer, "OrthographicFarPlane", camera.OrthographicFarPlane); + Serialize.Value(writer, "AspectRatio", camera.AspectRatio); + Serialize.Value(writer, "FixedAspectRatio", camera.FixedAspectRatio); + }); + + // TODO: native script component + + + SerializeComponent(writer, entity, "LightComponent", scope (component) => + { + SceneLight light = component.SceneLight; + + Serialize.Value(writer, "LightType", light.LightType); + + Serialize.Value(writer, "Illuminance", light.Illuminance); + + Serialize.Value(writer, "Color", light.Color); + }); + } + + writer.EntryEnd();*/ + } + + static Result DeserializeComponent(BonReader reader, Entity entity, delegate Result(T* component) deserialize) where T : struct, new + { + Try!(reader.ObjectBlock()); + + T* component; + + if (entity.HasComponent()) + component = entity.GetComponent(); + else + component = entity.AddComponent(); + + Try!(deserialize(component)); + + Try!(reader.ObjectBlockEnd()); + + return .Ok; + } + + public bool DeserializeRuntime(StringView filePath) + { + Runtime.NotImplemented(); + } +} diff --git a/GlitchyEngine/src/World/WorldEnumerator.bf b/GlitchyEngine/src/World/WorldEnumerator.bf index d46bad2..c39b7da 100644 --- a/GlitchyEngine/src/World/WorldEnumerator.bf +++ b/GlitchyEngine/src/World/WorldEnumerator.bf @@ -13,6 +13,8 @@ namespace GlitchyEngine.World internal EcsWorld.BitmaskEntry* _currentEntry; internal EcsWorld.BitmaskEntry* _endEntry; + public bool IsEmpty => _bitMask == null; + public this(EcsWorld world, Type[] componentTypes) { _world = world; @@ -32,7 +34,13 @@ namespace GlitchyEngine.World } else { - Log.EngineLogger.AssertDebug(false, "Queried component is not registered for this world. This is invalid because the query would never return any results."); + // One of the components isn't registered in the world thus there can't be any entity with this component and we don't need to enumerate... +#if GE_WORLD_ENUMERATOR_UNREGISTERED_COMPONENT_IS_WARNING + Log.EngineLogger.Warning($"Queried component of type \"{type}\" is not registered for this world. The query will never return any results."); +#endif + DeleteAndNullify!(_bitMask); + _endEntry = _currentEntry; + break; } } } @@ -67,7 +75,14 @@ namespace GlitchyEngine.World public this(EcsWorld world) : base(world, scope Type[](typeof(TComponent))) { - _componentPool = &world.GetComponentPool(); + if (base.IsEmpty) + { + _componentPool = null; + } + else + { + _componentPool = &world.GetComponentPool(); + } } public new Result<(EcsEntity Entity, TComponent* Component)> GetNext() mut @@ -92,8 +107,16 @@ namespace GlitchyEngine.World public this(EcsWorld world) : base(world, scope Type[](typeof(TComponent0), typeof(TComponent1))) { - _componentPool0 = &world.GetComponentPool(); - _componentPool1 = &world.GetComponentPool(); + if (base.IsEmpty) + { + _componentPool0 = null; + _componentPool1 = null; + } + else + { + _componentPool0 = &world.GetComponentPool(); + _componentPool1 = &world.GetComponentPool(); + } } public new Result<(EcsEntity Entity, TComponent0* Component0, TComponent1* Component1)> GetNext() mut @@ -120,9 +143,18 @@ namespace GlitchyEngine.World public this(EcsWorld world) : base(world, scope Type[](typeof(TComponent0), typeof(TComponent1), typeof(TComponent2))) { - _componentPool0 = &world.GetComponentPool(); - _componentPool1 = &world.GetComponentPool(); - _componentPool2 = &world.GetComponentPool(); + if (base.IsEmpty) + { + _componentPool0 = null; + _componentPool1 = null; + _componentPool2 = null; + } + else + { + _componentPool0 = &world.GetComponentPool(); + _componentPool1 = &world.GetComponentPool(); + _componentPool2 = &world.GetComponentPool(); + } } public new Result<(EcsEntity Entity, TComponent0* Component0, TComponent1* Component1, TComponent2* Component2)> GetNext() mut @@ -151,10 +183,20 @@ namespace GlitchyEngine.World public this(EcsWorld world) : base(world, scope Type[](typeof(TComponent0), typeof(TComponent1), typeof(TComponent2), typeof(TComponent3))) { - _componentPool0 = &world.GetComponentPool(); - _componentPool1 = &world.GetComponentPool(); - _componentPool2 = &world.GetComponentPool(); - _componentPool3 = &world.GetComponentPool(); + if (base.IsEmpty) + { + _componentPool0 = null; + _componentPool1 = null; + _componentPool2 = null; + _componentPool3 = null; + } + else + { + _componentPool0 = &world.GetComponentPool(); + _componentPool1 = &world.GetComponentPool(); + _componentPool2 = &world.GetComponentPool(); + _componentPool3 = &world.GetComponentPool(); + } } public new Result<(EcsEntity Entity, TComponent0* Component0, TComponent1* Component1, TComponent2* Component2, TComponent3* Component3)> GetNext() mut diff --git a/GlitchyEngine/vendor/Beef.Linq b/GlitchyEngine/vendor/Beef.Linq new file mode 160000 index 0000000..4e4ed61 --- /dev/null +++ b/GlitchyEngine/vendor/Beef.Linq @@ -0,0 +1 @@ +Subproject commit 4e4ed6158b7602928cfc7ba391bd097b19ef2892 diff --git a/GlitchyEngine/vendor/bon b/GlitchyEngine/vendor/bon index 515b606..0c80e36 160000 --- a/GlitchyEngine/vendor/bon +++ b/GlitchyEngine/vendor/bon @@ -1 +1 @@ -Subproject commit 515b606cf3048179b19d51e55a503b3007ae94e4 +Subproject commit 0c80e365cd20d8d56649e01632a317bcbad6f61c diff --git a/GlitchyEngine/vendor/box2D b/GlitchyEngine/vendor/box2D new file mode 160000 index 0000000..5990578 --- /dev/null +++ b/GlitchyEngine/vendor/box2D @@ -0,0 +1 @@ +Subproject commit 5990578fdcfba275c96f91453d7150203865d2a1 diff --git a/GlitchyEngine/vendor/directx b/GlitchyEngine/vendor/directx index d1a21b6..4b9d0c5 160000 --- a/GlitchyEngine/vendor/directx +++ b/GlitchyEngine/vendor/directx @@ -1 +1 @@ -Subproject commit d1a21b6b88515cbb6130a637a2f4e0eb2cd690a9 +Subproject commit 4b9d0c5d7bfa0bd48f264f9597788b290d9d3a06 diff --git a/GlitchyEngine/vendor/gltf b/GlitchyEngine/vendor/gltf index d881f98..60b0a1b 160000 --- a/GlitchyEngine/vendor/gltf +++ b/GlitchyEngine/vendor/gltf @@ -1 +1 @@ -Subproject commit d881f985c239e529df2c1faa51efa0e437b07229 +Subproject commit 60b0a1be169bda363cb2a18261c2613f98061119 diff --git a/GlitchyEngine/vendor/imgui b/GlitchyEngine/vendor/imgui index b1d8913..827764b 160000 --- a/GlitchyEngine/vendor/imgui +++ b/GlitchyEngine/vendor/imgui @@ -1 +1 @@ -Subproject commit b1d891368e640f5a24b2af0385b95a58683a3095 +Subproject commit 827764b98ca48d0aaab538a8ef73bae81798042f diff --git a/GlitchyEngineHelper/BeefProj.toml b/GlitchyEngineHelper/BeefProj.toml index 1f97ac5..8933dc5 100644 --- a/GlitchyEngineHelper/BeefProj.toml +++ b/GlitchyEngineHelper/BeefProj.toml @@ -10,4 +10,10 @@ StartupObject = "GlitchyEngineHelper.Program" BuildCommandsOnCompile = "IfFilesChanged" BuildCommandsOnRun = "IfFilesChanged" LibPaths = ["$(ProjectDir)/out/build/x64-debug/GlitchyEngineHelper.lib"] -PostBuildCmds = ["$(ProjectDir)\\..\\bin\\vscmake.bat x64 $(ProjectDir) x64-debug"] +PreBuildCmds = ["$(ProjectDir)/../bin/vscmake.bat x64 $(ProjectDir) x64-debug"] + +[Configs.Release.Win64] +BuildCommandsOnCompile = "IfFilesChanged" +BuildCommandsOnRun = "IfFilesChanged" +LibPaths = ["$(ProjectDir)/out/build/x64-release/GlitchyEngineHelper.lib"] +PreBuildCmds = ["$(ProjectDir)/../bin/vscmake.bat x64 $(ProjectDir) x64-release"] diff --git a/GlitchyEngineHelper/CMakeLists.txt b/GlitchyEngineHelper/CMakeLists.txt index decfff2..f06ad13 100644 --- a/GlitchyEngineHelper/CMakeLists.txt +++ b/GlitchyEngineHelper/CMakeLists.txt @@ -11,5 +11,4 @@ add_library (GlitchyEngineHelper "GlitchyEngineHelper.cpp" "GlitchyEngineHelper. include_directories("vendor/DirectXTK/Inc") # Use statically linked multithreaded MSVC runtime -set_property(TARGET GlitchyEngineHelper PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreadedDebug") -#$<$:Debug> \ No newline at end of file +set_property(TARGET GlitchyEngineHelper PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") diff --git a/GlitchyEngineHelper/CMakePresets.json b/GlitchyEngineHelper/CMakePresets.json index abf4065..b699737 100644 --- a/GlitchyEngineHelper/CMakePresets.json +++ b/GlitchyEngineHelper/CMakePresets.json @@ -57,5 +57,15 @@ "CMAKE_BUILD_TYPE": "Release" } } + ], + "buildPresets": [ + { + "name": "x64-debug", + "configurePreset": "x64-debug" + }, + { + "name": "x64-release", + "configurePreset": "x64-release" + } ] } diff --git a/Sandbox/src/ExampleLayer.bf b/Sandbox/src/ExampleLayer.bf index 064a780..5725797 100644 --- a/Sandbox/src/ExampleLayer.bf +++ b/Sandbox/src/ExampleLayer.bf @@ -1,4 +1,4 @@ -using GlitchyEngine.Renderer; +/*using GlitchyEngine.Renderer; using GlitchyEngine.Events; using GlitchyEngine.ImGui; using GlitchyEngine.Math; @@ -63,8 +63,8 @@ namespace Sandbox Material _checkerMaterial ~ _?.ReleaseRef(); Material _logoMaterial ~ _?.ReleaseRef(); - Texture2D _texture ~ _?.ReleaseRef(); - Texture2D _ge_logo ~ _?.ReleaseRef(); + AssetHandle _texture; + AssetHandle _ge_logo; BlendState _alphaBlendState ~ _?.ReleaseRef(); BlendState _opaqueBlendState ~ _?.ReleaseRef(); @@ -85,21 +85,21 @@ namespace Sandbox _context = Application.Get().Window.Context..AddRef(); - var effectLibrary = Application.Get().EffectLibrary; + //var effectLibrary = Application.Get().EffectLibrary; - effectLibrary.LoadNoRefInc("content\\Shaders\\basicShader.hlsl"); + //effectLibrary.LoadNoRefInc("content\\Shaders\\basicShader.hlsl"); - effectLibrary.LoadNoRefInc("content\\Shaders\\testShader.hlsl"); + //effectLibrary.LoadNoRefInc("content\\Shaders\\testShader.hlsl"); - var textureEffect = effectLibrary.Load("content\\Shaders\\textureShader.hlsl"); + Effect textureEffect = Content.GetAsset(Content.LoadAsset("Shaders\\textureShader.hlsl")); _depthTarget = new DepthStencilTarget(_context.SwapChain.Width, _context.SwapChain.Height); // Create Input Layout - VertexLayout vertexLayout = new VertexLayout(VertexColorTexture.VertexElements, false, textureEffect.VertexShader); + VertexLayout vertexLayout = new VertexLayout(VertexColorTexture.VertexElements, false); - textureEffect.ReleaseRef(); + //textureEffect.ReleaseRef(); // Create hexagon { @@ -174,8 +174,11 @@ namespace Sandbox rsDesc.FrontCounterClockwise = false; _rasterizerStateClockWise = new RasterizerState(rsDesc); - _texture = new Texture2D("content/Textures/Checkerboard.dds"); - _ge_logo = new Texture2D("content/Textures/GE_Logo.dds"); + _texture = Content.LoadAsset("content/Textures/Checkerboard.dds");//new Texture2D("content/Textures/Checkerboard.dds"); + _ge_logo = Content.LoadAsset("content/Textures/GE_Logo.dds");//new Texture2D("content/Textures/GE_Logo.dds"); + + Texture2D texture = Content.GetAsset(_texture); + Texture2D ge_logo = Content.GetAsset(_ge_logo); let sampler = SamplerStateManager.GetSampler( SamplerStateDescription() @@ -183,8 +186,8 @@ namespace Sandbox MagFilter = .Point }); - _texture.SamplerState = sampler; - _ge_logo.SamplerState = sampler; + texture.SamplerState = sampler; + ge_logo.SamplerState = sampler; sampler.ReleaseRef(); @@ -214,7 +217,7 @@ namespace Sandbox void TestLoadModel() { - var testEffect = Application.Get().EffectLibrary.Get("testShader"); + Effect testEffect = Content.LoadAsset("Shaders\\testShader.hlsl");//Application.Get().EffectLibrary.Get("testShader"); var materialTestMaterial = new Material(testEffect); animationMat = materialTestMaterial; @@ -233,7 +236,7 @@ namespace Sandbox materialTestMaterial.SetVariable("BaseColor", Color.White); materialTestMaterial.SetVariable("LightDir", Vector3(1, 1, -0.5f).Normalized()); - ModelLoader.LoadModel("content\\Models\\RiggedFigure\\RiggedFigure.glb", testEffect, materialTestMaterial, _world, Clips); + ModelLoader.LoadModel("content\\Models\\RiggedFigure\\RiggedFigure.glb", materialTestMaterial, _world, Clips); materialTestMaterial.ReleaseRef(); testEffect.ReleaseRef(); @@ -266,7 +269,7 @@ namespace Sandbox _world.Register(); _world.Register(); - var basicEffect = Application.Get().EffectLibrary.Get("basicShader"); + Effect basicEffect = Content.LoadAsset("Shaders\\basicShader.hlsl");//Application.Get().EffectLibrary.Get("basicShader"); testMaterial1 = new Material(basicEffect); testMaterial1.SetVariable("BaseColor", _squareColor0); @@ -352,7 +355,7 @@ namespace Sandbox RenderCommand.SetBlendState(_opaqueBlendState); - var basicEffect = Application.Get().EffectLibrary.Get("basicShader"); + Effect basicEffect = Content.LoadAsset("Shaders\\basicShader.hlsl");//Application.Get().EffectLibrary.Get("basicShader"); RenderCommand.SetRasterizerState(_rasterizerState); @@ -518,4 +521,4 @@ namespace Sandbox } } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/Sandbox/src/ExampleLayer2D.bf b/Sandbox/src/ExampleLayer2D.bf index d3ffb27..27b4677 100644 --- a/Sandbox/src/ExampleLayer2D.bf +++ b/Sandbox/src/ExampleLayer2D.bf @@ -1,4 +1,4 @@ -using System; +/*using System; using GlitchyEngine; using GlitchyEngine.Events; using System.Diagnostics; @@ -12,6 +12,7 @@ using GlitchyEngine.Renderer.Text; using System.IO; using msdfgen; using System.Collections; +using GlitchyEngine.Content; namespace Sandbox { @@ -96,7 +97,7 @@ namespace Sandbox _depthTarget = new DepthStencilTarget(_context.SwapChain.Width, _context.SwapChain.Height); - _checkerTexture = new Texture2D("content/Textures/Checkerboard.dds"); + _checkerTexture = Content.LoadAsset("content/Textures/Checkerboard.dds");//new Texture2D("content/Textures/Checkerboard.dds"); let sampler = SamplerStateManager.GetSampler( SamplerStateDescription() @@ -125,7 +126,7 @@ namespace Sandbox _depthStencilState = new DepthStencilState(dssDesc); - _spriteSheet = new Texture2D("content/Rpg/textures/spritesheet.png"); + _spriteSheet = Content.LoadAsset("content/Rpg/textures/spritesheet.png");//new Texture2D("content/Rpg/textures/spritesheet.png"); _spriteSheet.SamplerState = SamplerStateManager.PointClamp; _treeSprite = SubTexture2D.CreateFromGrid(_spriteSheet, Vector2(5, 10), Vector2(128), .(1, 2)); @@ -282,4 +283,4 @@ namespace Sandbox return false; } } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/Sandbox/src/GammaTestLayer.bf b/Sandbox/src/GammaTestLayer.bf index 6b7e65b..efcbb66 100644 --- a/Sandbox/src/GammaTestLayer.bf +++ b/Sandbox/src/GammaTestLayer.bf @@ -43,11 +43,11 @@ namespace Sandbox _depthTarget = new DepthStencilTarget(_context.SwapChain.Width, _context.SwapChain.Height); - _checkerTexture = new Texture2D("content/Textures/Checkerboard.dds"); - _smallLines = new Texture2D("content/GammaTest/SmallLines.png"); - _gammaCorrectionBrightness = new Texture2D("content/GammaTest/gamma_correction_brightness.png"); + //_checkerTexture = new Texture2D("content/Textures/Checkerboard.dds"); + //_smallLines = new Texture2D("content/GammaTest/SmallLines.png"); + //_gammaCorrectionBrightness = new Texture2D("content/GammaTest/gamma_correction_brightness.png"); - _zeroPointFive = new Texture2D("content/GammaTest/zeroPointFive.png"); + //_zeroPointFive = new Texture2D("content/GammaTest/zeroPointFive.png"); let sampler = SamplerStateManager.GetSampler( SamplerStateDescription() diff --git a/Sandbox/src/SandboxApp.bf b/Sandbox/src/SandboxApp.bf index c19e7bc..5072df0 100644 --- a/Sandbox/src/SandboxApp.bf +++ b/Sandbox/src/SandboxApp.bf @@ -21,7 +21,7 @@ namespace Sandbox #if GAMMA_TEST PushLayer(new GammaTestLayer()); #elif SANDBOX_2D - PushLayer(new ExampleLayer2D()); + //PushLayer(new ExampleLayer2D()); #else PushLayer(new ExampleLayer()); #endif @@ -32,5 +32,10 @@ namespace Sandbox { return new SandboxApp(); } + + protected override IContentManager InitContentManager() + { + Runtime.NotImplemented(); + } } } diff --git a/Sandbox/src/TextureViewer.bf b/Sandbox/src/TextureViewer.bf index d5e13bf..751107f 100644 --- a/Sandbox/src/TextureViewer.bf +++ b/Sandbox/src/TextureViewer.bf @@ -39,7 +39,7 @@ namespace Sandbox public this() { - _context = Renderer.[Friend]_context..AddRef(); + _context = Application.Get().Window.Context..AddRef(); InitEffect(); InitState(); @@ -157,7 +157,7 @@ namespace Sandbox if(_moving) { - Point movement = Input.GetMouseMovement(); + Int2 movement = Input.GetMouseMovement(); _position.X += movement.X; _position.Y += movement.Y; diff --git a/bin/vscmake.bat b/bin/vscmake.bat index ddbf191..d296de1 100644 --- a/bin/vscmake.bat +++ b/bin/vscmake.bat @@ -4,6 +4,7 @@ for /f "usebackq tokens=*" %%i in (`vswhere.exe -latest -products * -requires Mi if exist "%%i\VC\Auxiliary\Build\vcvarsall.bat" ( "%%i\VC\Auxiliary\Build\vcvarsall.bat" %1 cd /D %2 - cmake --preset %3 + cmake --preset=%3 + cmake --build --preset=%3 ) )