diff --git a/.gitmodules b/.gitmodules index 599a7e2..f41617d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "GlitchyEngine/vendor/freetype"] path = GlitchyEngine/vendor/freetype url = https://github.com/aharabada/FreeType-beef.git +[submodule "GlitchyEngine/vendor/gltf"] + path = GlitchyEngine/vendor/gltf + url = https://github.com/aharabada/cgltf-beef.git diff --git a/BeefSpace.toml b/BeefSpace.toml index da02317..4d04ce9 100644 --- a/BeefSpace.toml +++ b/BeefSpace.toml @@ -1,5 +1,5 @@ FileVersion = 1 -Projects = {Sandbox = {Path = "Sandbox"}, GlitchyEngine = {Path = "GlitchyEngine"}, GlitchLog = {Path = "GlitchLog"}, DirectX = {Path = "GlitchyEngine/vendor/directx/DirectX"}, ImGui = {Path = "GlitchyEngine/vendor/imgui-beef/ImGui"}, ImGuiImplWin32 = {Path = "GlitchyEngine/vendor/imgui-beef/ImGuiImplWin32"}, ImGuiImplDX11 = {Path = "GlitchyEngine/vendor/imgui-beef/ImGuiImplDX11"}, DirectXTK = {Path = "GlitchyEngine/vendor/DirectXTK/DirectXTK-beef"}, LodePng = {Path = "vendor/lodepng-beef/lodepng-beef"}, FreeType = {Path = "GlitchyEngine/vendor/freetype"}} +Projects = {Sandbox = {Path = "Sandbox"}, GlitchyEngine = {Path = "GlitchyEngine"}, GlitchLog = {Path = "GlitchLog"}, DirectX = {Path = "GlitchyEngine/vendor/directx/DirectX"}, ImGui = {Path = "GlitchyEngine/vendor/imgui-beef/ImGui"}, ImGuiImplWin32 = {Path = "GlitchyEngine/vendor/imgui-beef/ImGuiImplWin32"}, ImGuiImplDX11 = {Path = "GlitchyEngine/vendor/imgui-beef/ImGuiImplDX11"}, 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"}} [Workspace] StartupProject = "Sandbox" diff --git a/GlitchyEngine/BeefProj.toml b/GlitchyEngine/BeefProj.toml index c79e15f..f93d5da 100644 --- a/GlitchyEngine/BeefProj.toml +++ b/GlitchyEngine/BeefProj.toml @@ -1,5 +1,5 @@ FileVersion = 1 -Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", ImGui = "*", ImGuiImplWin32 = "*", ImGuiImplDX11 = "*", DirectXTK = "*", FreeType = "*"} +Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", ImGui = "*", ImGuiImplWin32 = "*", ImGuiImplDX11 = "*", DirectXTK = "*", FreeType = "*", cgltf-beef = "*"} [Project] Name = "GlitchyEngine" diff --git a/GlitchyEngine/src/Content/ModelLoader.bf b/GlitchyEngine/src/Content/ModelLoader.bf new file mode 100644 index 0000000..ed0da71 --- /dev/null +++ b/GlitchyEngine/src/Content/ModelLoader.bf @@ -0,0 +1,217 @@ +using System; +using System.Collections; +using cgltf; +using GlitchyEngine.Math; +using GlitchyEngine.Renderer; + +namespace GlitchyEngine.Content +{ + public static class ModelLoader + { + public static void LoadModel(String filename, GraphicsContext context, Effect validationEffect, List<(Matrix Transform, GeometryBinding Model)> output) + { + 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); + + Log.EngineLogger.Assert(result == .Success, "Failed to load buffers"); + + for(var node in data.Nodes) + { + if(node.Mesh != null) + { + Matrix transform = ?; + CGLTF.NodeTransformWorld(&node, (float*)&transform); + + for(var primitive in node.Mesh.Primitives) + { + GeometryBinding binding = ModelLoader.PrimitiveToGeoBinding(context, primitive, validationEffect); + + output.Add((transform, binding)); + } + } + } + + /* TODO make awesome stuff */ + CGLTF.Free(data); + } + + public static GeometryBinding PrimitiveToGeoBinding(GraphicsContext context, CGLTF.Primitive primitive, Effect validationEffect) + { + GeometryBinding binding = new GeometryBinding(context); + + // primitive topology + switch(primitive.Type) + { + case .Points: + binding.SetPrimitiveTopology(.PointList); + case .Lines: + binding.SetPrimitiveTopology(.LineList); + case .LineStrip: + binding.SetPrimitiveTopology(.LineStrip); + case .Triangles: + binding.SetPrimitiveTopology(.TriangleList); + case .TriangleStrip: + binding.SetPrimitiveTopology(.TriangleStrip); + default: + Log.EngineLogger.Assert(false, scope $"{primitive.Type} not supported."); + } + + // indices + { + CGLTF.Accessor* indices = primitive.Indices; + + if(indices != null) + { + bool is16bit = indices.ComponentType == .R_16u || indices.ComponentType == .R_16; + + IndexBuffer ib = new IndexBuffer(context, (.)indices.Count, .Immutable, .None, is16bit ? .Index16Bit : .Index32Bit); + + uint8* bufferData = (uint8*)indices.BufferView.Buffer.Data; + bufferData += indices.BufferView.Offset; + + ib.SetData(bufferData, (.)indices.BufferView.Size); + + binding.SetIndexBuffer(ib); + + ib.ReleaseRef(); + } + } + + // vertices + { + List elements = scope .(primitive.Attributes.Length); + Dictionary buffers = scope .(); + List bindings = scope .(); + + for(var attribute in primitive.Attributes) + { + // Get Input Element format + Format format = FormatFromVectorComponent(attribute.Data.Type, attribute.Data.ComponentType); + Log.EngineLogger.AssertDebug(format != .Unknown, "Vertex element format must not be \"Unknown.\""); + + VertexBuffer vertexBuffer = null; + + // Get vertex buffer + { + CGLTF.BufferView* bufferView = attribute.Data.BufferView; + + // if buffer doesn't exist -> create + if(!buffers.TryGetValue(bufferView, out vertexBuffer)) + { + vertexBuffer = new VertexBuffer(context, 1, (uint32)bufferView.Size, .Immutable)..ReleaseRefNoDelete(); + + uint8* bufferData = (uint8*)bufferView.Buffer.Data; + bufferData += bufferView.Offset; + + vertexBuffer.SetData(bufferData, (.)bufferView.Size); + + buffers.Add(attribute.Data.BufferView, vertexBuffer); + } + + Log.EngineLogger.AssertDebug(vertexBuffer != null); + } + + VertexBufferBinding bufferBinding = .(vertexBuffer, (.)attribute.Data.Stride, (.)attribute.Data.Offset); + + // get slot of bufferBinding + int bindingSlot = bindings.IndexOf(bufferBinding); + + // binding has no slot -> add to list + if(bindingSlot == -1) + { + bindingSlot = bindings.Count; + bindings.Add(bufferBinding); + + binding.SetVertexBufferSlot(bufferBinding, (.)bindingSlot); + } + + VertexElement element = .(format, new String(attribute.Name), true, (.)attribute.Index, (.)bindingSlot); + elements.Add(element); + } + + VertexElement[] vertexElements = new VertexElement[elements.Count]; + for(int i < elements.Count) + { + vertexElements[i] = elements[i]; + } + + // TODO: validate vertex layout somewhere else + + VertexLayout layout = new VertexLayout(context, vertexElements, true, validationEffect.VertexShader); + binding.SetVertexLayout(layout..ReleaseRefNoDelete()); + } + + return binding; + } + + /** + * Converts the vector and component type to the corresponding Format. + */ + static Format FormatFromVectorComponent(CGLTF.Type vectorType, CGLTF.ComponentType componentType) + { + switch((vectorType, componentType)) + { + case (.Scalar, .R_8): + return .R8_SInt; + case (.Scalar, .R_8u): + return .R8_UInt; + case (.Scalar, .R_16): + return .R16_SInt; + case (.Scalar, .R_16u): + return .R16_UInt; + case (.Scalar, .R_32u): + return .R32_UInt; + case (.Scalar, .R_32f): + return .R32_Float; + + case (.Vec2, .R_8): + return .R8G8_SInt; + case (.Vec2, .R_8u): + return .R8G8_UInt; + case (.Vec2, .R_16): + return .R16G16_SInt; + case (.Vec2, .R_16u): + return .R16G16_UInt; + case (.Vec2, .R_32u): + return .R32G32_UInt; + case (.Vec2, .R_32f): + return .R32G32_Float; + + //case (.Vec3, .R_8): + //case (.Vec3, .R_8u): + //case (.Vec3, .R_16): + //case (.Vec3, .R_16u): + case (.Vec3, .R_32u): + return .R32G32B32_UInt; + case (.Vec3, .R_32f): + return .R32G32B32_Float; + + case (.Vec4, .R_8): + return .R8G8B8A8_SInt; + case (.Vec4, .R_8u): + return .R8G8B8A8_UInt; + case (.Vec4, .R_16): + return .R16G16B16A16_SInt; + case (.Vec4, .R_16u): + return .R16G16B16A16_UInt; + case (.Vec4, .R_32u): + return .R32G32B32A32_UInt; + case (.Vec4, .R_32f): + return .R32G32B32A32_Float; + + case (.Mat4, .R_32f): + return .R32G32B32A32_Float; + + default: + Log.EngineLogger.Assert(false, scope $"Unhandled vector - componenttype combination. ({vectorType}, {componentType})"); + } + + return .Unknown; + } + } +} diff --git a/Sandbox/content/Models/Box.gltf b/Sandbox/content/Models/Box.gltf new file mode 100644 index 0000000..7f603f0 --- /dev/null +++ b/Sandbox/content/Models/Box.gltf @@ -0,0 +1,142 @@ +{ + "asset": { + "generator": "COLLADA2GLTF", + "version": "2.0" + }, + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "nodes": [ + { + "children": [ + 1 + ], + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + { + "mesh": 0 + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "NORMAL": 1, + "POSITION": 2 + }, + "indices": 0, + "mode": 4, + "material": 0 + } + ], + "name": "Mesh" + } + ], + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5123, + "count": 36, + "max": [ + 23 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "max": [ + 1.0, + 1.0, + 1.0 + ], + "min": [ + -1.0, + -1.0, + -1.0 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 288, + "componentType": 5126, + "count": 24, + "max": [ + 0.5, + 0.5, + 0.5 + ], + "min": [ + -0.5, + -0.5, + -0.5 + ], + "type": "VEC3" + } + ], + "materials": [ + { + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.800000011920929, + 0.0, + 0.0, + 1.0 + ], + "metallicFactor": 0.0 + }, + "name": "Red" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 576, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 576, + "byteStride": 12, + "target": 34962 + } + ], + "buffers": [ + { + "byteLength": 648, + "uri": "Box0.bin" + } + ] +} diff --git a/Sandbox/content/Models/Box0.bin b/Sandbox/content/Models/Box0.bin new file mode 100644 index 0000000..d7798ab Binary files /dev/null and b/Sandbox/content/Models/Box0.bin differ diff --git a/Sandbox/content/Shaders/testShader.hlsl b/Sandbox/content/Shaders/testShader.hlsl new file mode 100644 index 0000000..29ccfb3 --- /dev/null +++ b/Sandbox/content/Shaders/testShader.hlsl @@ -0,0 +1,54 @@ +cbuffer SceneConstants +{ + float4x4 ViewProjection = float4x4(1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +cbuffer ObjectConstants +{ + float4x4 Transform; +} + +cbuffer Constants +{ + float4 BaseColor; + float3 LightDir; +} + +struct VS_IN +{ + float3 Position : POSITION; + float3 Normal : NORMAL; +}; + +struct PS_IN +{ + float4 Position : SV_POSITION; + float3 Normal : NORMAL; +}; + +PS_IN VS(VS_IN input) +{ + PS_IN output; + + float4 worldPosition = mul(Transform, float4(input.Position, 1)); + + output.Position = mul(ViewProjection, worldPosition); + output.Normal = mul((float3x3)Transform, input.Normal); + + return output; +} + +float4 PS(PS_IN input) : SV_TARGET +{ + input.Normal = normalize(input.Normal); + + float shading = dot(LightDir, input.Normal); + shading = clamp(shading, 0.0f, 1.0f); + + return BaseColor * shading; +} + +#effect[VS=VS,PS=PS] diff --git a/Sandbox/src/SandboxApp.bf b/Sandbox/src/SandboxApp.bf index d716702..58554a9 100644 --- a/Sandbox/src/SandboxApp.bf +++ b/Sandbox/src/SandboxApp.bf @@ -8,6 +8,8 @@ using ImGui; using GlitchyEngine.Renderer; using GlitchyEngine.Math; using GlitchyEngine.World; +using System.Collections; +using GlitchyEngine.Content; namespace Sandbox { @@ -55,8 +57,10 @@ namespace Sandbox GeometryBinding _quadGeometryBinding ~ _?.ReleaseRef(); RasterizerState _rasterizerState ~ _?.ReleaseRef(); + RasterizerState _rasterizerStateClockWise ~ _?.ReleaseRef(); GraphicsContext _context ~ _?.ReleaseRef(); + DepthStencilTarget _depthTarget ~ _?.ReleaseRef(); Texture2D _texture ~ _?.ReleaseRef(); Texture2D _ge_logo ~ _?.ReleaseRef(); @@ -84,9 +88,13 @@ namespace Sandbox _effectLibrary = new EffectLibrary(_context); _effectLibrary.LoadNoRefInc("content\\Shaders\\basicShader.hlsl"); + + _effectLibrary.LoadNoRefInc("content\\Shaders\\testShader.hlsl"); var textureEffect = _effectLibrary.Load("content\\Shaders\\textureShader.hlsl"); + _depthTarget = new DepthStencilTarget(_context, _context.SwapChain.Width, _context.SwapChain.Height); + // Create Input Layout VertexLayout vertexLayout = new VertexLayout(_context, VertexColorTexture.VertexElements, false, textureEffect.VertexShader); @@ -163,6 +171,9 @@ namespace Sandbox RasterizerStateDescription rsDesc = .(.Solid, .Back, true); _rasterizerState = new RasterizerState(_context, rsDesc); + rsDesc.FrontCounterClockwise = false; + _rasterizerStateClockWise = new RasterizerState(_context, rsDesc); + _texture = new Texture2D(_context, "content/Textures/Checkerboard.dds"); _ge_logo = new Texture2D(_context, "content/Textures/GE_Logo.dds"); @@ -190,6 +201,53 @@ namespace Sandbox _cameraController = new .(Application.Get().Window.Context.SwapChain.BackbufferViewport.Width / Application.Get().Window.Context.SwapChain.BackbufferViewport.Height); _cameraController.CameraPosition = .(0, 0, -5); + + TestLoadModel(); + } + + //GeometryBinding _modelTest ~ _?.ReleaseRef(); + + List<(Matrix Transform, GeometryBinding Model)> _modelTest = new .() ~ UnloadModelTest!(); + + mixin UnloadModelTest() + { + for(var entry in _modelTest) + { + entry.Model?.ReleaseRef(); + } + + delete _modelTest; + } + + void TestLoadModel() + { + var testEffect = _effectLibrary.Get("testShader"); + + ModelLoader.LoadModel("content\\Models\\box.gltf", _context, testEffect, _modelTest); + + testEffect.ReleaseRef(); + + /* + CGLTF.Options options = .(); + CGLTF.Data* data; + CGLTF.Result result = CGLTF.ParseFile(options, "content\\Models\\box.gltf", out data); + + CGLTF.LoadBuffers(options, data, "content\\Models\\box.gltf"); + + Log.EngineLogger.Assert(result == .Success, "Failed to load model"); + + var mesh = data.Meshes[0]; + var primitive = mesh.Primitives[0]; + + var testEffect = _effectLibrary.Get("testShader"); + + _modelTest = ModelLoader.PrimitiveToGeoBinding(_context, primitive, testEffect); + + testEffect.ReleaseRef(); + + /* TODO make awesome stuff */ + CGLTF.Free(data); + */ } void InitEcs() @@ -217,13 +275,13 @@ namespace Sandbox _cameraController.Update(gameTime); RenderCommand.Clear(null, .(0.2f, 0.2f, 0.2f)); + _depthTarget.Clear(1.0f, 0, .Depth); // Draw test geometry _context.SetRenderTarget(null); + _depthTarget.Bind(); _context.BindRenderTargets(); - _context.SetRasterizerState(_rasterizerState); - _context.SetViewport(_context.SwapChain.BackbufferViewport); Renderer.BeginScene(_cameraController.Camera); @@ -233,6 +291,24 @@ namespace Sandbox var basicEffect = _effectLibrary.Get("basicShader"); var textureEffect = _effectLibrary.Get("textureShader"); + // Model test + { + _context.SetRasterizerState(_rasterizerStateClockWise); + + var testEffect = _effectLibrary.Get("testShader"); + testEffect.Variables["BaseColor"].SetData(Color.White); + testEffect.Variables["LightDir"].SetData(Vector3(-1, 1, -0.5f).Normalized()); + + for(var entry in _modelTest) + { + Renderer.Submit(entry.Model, testEffect, entry.Transform); + } + + testEffect.ReleaseRef(); + } + + _context.SetRasterizerState(_rasterizerState); + for(var entity in _world.Enumerate(typeof(TransformComponent))) { var transform = _world.GetComponent(entity); @@ -281,6 +357,7 @@ namespace Sandbox EventDispatcher dispatcher = EventDispatcher(event); dispatcher.Dispatch(scope (e) => OnImGuiRender(e)); + dispatcher.Dispatch(scope (e) => OnWindowResize(e)); } private bool OnImGuiRender(ImGuiRenderEvent e) @@ -295,6 +372,14 @@ namespace Sandbox return false; } + + private bool OnWindowResize(WindowResizeEvent e) + { + _depthTarget.ReleaseRef(); + _depthTarget = new DepthStencilTarget(_context, _context.SwapChain.Width, _context.SwapChain.Height); + + return false; + } } class SandboxApp : Application