diff --git a/GlitchyEngine/src/Renderer/Renderer2D.bf b/GlitchyEngine/src/Renderer/Renderer2D.bf new file mode 100644 index 0000000..49e396a --- /dev/null +++ b/GlitchyEngine/src/Renderer/Renderer2D.bf @@ -0,0 +1,300 @@ +using GlitchyEngine.Math; +using System.Collections; +using System; +using System.Diagnostics; + +namespace GlitchyEngine.Renderer +{ + using internal GlitchyEngine.Renderer; + + public class Renderer2D + { + struct RenderVertex : IVertexData + { + public Vector2 Position; + + public this(Vector2 position) + { + Position = position; + } + + public this(float x, float y) + { + Position = .(x, y); + } + + public static VertexElement[] VertexElements ~ delete _; + + public static VertexElement[] IVertexData.VertexElements => VertexElements; + + static this() + { + VertexElements = new VertexElement[] + ( + VertexElement(.R32G32_Float, "POSITION") + ); + } + } + + [Ordered] + struct BatchVertex + { + public Matrix Transform; + public Color Color; + + public this(Matrix transform, Color color) + { + Transform = transform; + Color = color; + } + } + + private GraphicsContext _context ~ _?.ReleaseRef(); + + private Vector2 _virtualResolution; + + private Effect quadEffect ~ _?.ReleaseRef(); + private VertexLayout layout ~ delete _; + private GeometryBinding quadBinding ~ _?.ReleaseRef(); + + private Effect instancingEffect ~ _?.ReleaseRef(); + private VertexLayout instancingLayout ~ delete _; + private GeometryBinding instancingBinding ~ _?.ReleaseRef(); + private VertexBuffer instanceBuffer ~ _?.ReleaseRef(); + + private BatchVertex[] _rawInstances = new BatchVertex[1024] ~ delete _; + private uint32 _setInstances = 0; + + public this(GraphicsContext context, EffectLibrary effectLibrary) + { + _context = context..AddRef(); + Init(effectLibrary); + InitInstancing(effectLibrary); + } + + void Init(EffectLibrary effectLibrary) + { + quadEffect = effectLibrary.Load("content\\Shaders\\render2dShader.hlsl", "Renderer2D"); + + layout = new VertexLayout(_context, RenderVertex.VertexElements, quadEffect.VertexShader); + + VertexBuffer quadVertices = new VertexBuffer(_context, typeof(RenderVertex), 4, .Immutable); + + RenderVertex[4] vertices = .( + .(0, 0), + .(0, 1), + .(1, 1), + .(1, 0) + ); + + quadVertices.SetData(vertices); + + IndexBuffer quadIndices = new IndexBuffer(_context, 6, .Immutable); + + uint16[6] indices = .( + 0, 1, 2, + 2, 3, 0 + ); + + quadIndices.SetData(indices); + + quadBinding = new GeometryBinding(_context); + quadBinding.SetVertexLayout(layout); + quadBinding.SetPrimitiveTopology(.TriangleList); + quadBinding.SetVertexBufferSlot(quadVertices, 0); + quadBinding.SetIndexBuffer(quadIndices); + + quadVertices.ReleaseRef(); + quadIndices.ReleaseRef(); + } + + void InitInstancing(EffectLibrary effectLibrary) + { + instancingEffect = effectLibrary.Load("content\\Shaders\\render2dShaderInst.hlsl", "Renderer2DInstancing"); + + instanceBuffer = new VertexBuffer(_context, typeof(BatchVertex), 1024, .Dynamic, .Write); + instanceBuffer.SetData(0); + + VertexElement[] vertexElements = scope .( + VertexElement(.R32G32_Float, "POSITION", 0, 0, 0, .PerVertexData, 0), + + VertexElement(.R32G32B32A32_Float, "TRANSFORM", 0, 1, (.)-1, .PerInstanceData, 1), + VertexElement(.R32G32B32A32_Float, "TRANSFORM", 1, 1, (.)-1, .PerInstanceData, 1), + VertexElement(.R32G32B32A32_Float, "TRANSFORM", 2, 1, (.)-1, .PerInstanceData, 1), + VertexElement(.R32G32B32A32_Float, "TRANSFORM", 3, 1, (.)-1, .PerInstanceData, 1), + VertexElement( .R8G8B8A8_UNorm, "COLOR", 0, 1, (.)-1, .PerInstanceData, 1) + ); + + instancingLayout = new VertexLayout(_context, vertexElements, instancingEffect.VertexShader); + + instancingBinding = new GeometryBinding(_context); + instancingBinding.SetVertexLayout(instancingLayout); + instancingBinding.SetPrimitiveTopology(.TriangleList); + instancingBinding.SetVertexBufferSlot(quadBinding.GetVertexBuffer(0), 0); + instancingBinding.SetVertexBufferSlot(instanceBuffer, 1); + instancingBinding.SetIndexBuffer(quadBinding.GetIndexBuffer(), 0); + } + + Matrix _projection; + + DrawOrder _drawOrder; + + enum DrawOrder + { + /// Immediately draw the sprites. + Immediate, + /** + * Deferres rendering until the call of End(). + * Sorts the sprites by their texture. + */ + SortByTexture, + /** + * Deferres rendering until the call of End(). + * Sorts the sprites so that the backmost will be drawn first and the frontmost last. + */ + BackToFront, + /** + * Deferres rendering until the call of End(). + * Sorts the sprites so that the frontmost will be drawn first and the backmost last. + */ + FrontToBack + } + + /** + * Initializes the renderer. + * @param drawOrder Determines if and how the sprites will be ordered before rendering. + * @param virtualResolution The virtual resolution used for rendering. + * Can be used to draw resolution independent stuff. // TODO: find a better explanation + */ // TODO: RenderMode (Immediate, Deferred) + public void Begin(DrawOrder drawOrder = .SortByTexture, Vector2 virtualResolution = .Zero, float maxDepth = 100) + { + _drawOrder = drawOrder; + + _virtualResolution = virtualResolution; + + if(virtualResolution == .Zero) + { + _virtualResolution = .(_context.SwapChain.BackbufferViewport.Width, _context.SwapChain.BackbufferViewport.Height); + } + + _projection = .(2.0f / _virtualResolution.X, 0, 0, 0, + 0, -2.0f / _virtualResolution.Y, 0, 0, + 0, 0, 1.0f / maxDepth, 0, + -1, 1, 0, 1); + } + + struct QueuedQuad: this(Matrix Transform, Color Color, Texture2D Texture, float Depth) { } + + List _quads = new .(128) ~ delete _; + + public void Draw(Texture2D texture, float x, float y, float width, float height, Color color = .White, float depth = 0.0f) + { + Matrix transform = .(width, 0, 0, 0, + 0, height, 0, 0, + 0, 0, 1, 0, + x, y, depth, 1) * _projection; + + if(_drawOrder == .Immediate) + { + texture.Bind(); + + quadEffect.Variables["World"].SetData(transform); + quadEffect.Variables["Color"].SetData(color); + quadEffect.Variables["HasTexture"].SetData(true); + quadEffect.Bind(_context); + + quadBinding.Bind(_context); + RenderCommand.DrawIndexed(quadBinding); + } + else + { + _quads.Add(.(transform, color, texture, depth)); + } + } + + public void End() + { + if(_drawOrder != .Immediate) + { + DrawDeferred(); + } + } + + void FlushInstances() + { + if(_setInstances == 0) + return; + + instanceBuffer.SetData(_rawInstances.Ptr, _setInstances, 0, .WriteDiscard); + + instancingEffect.Bind(_context); + instancingBinding.InstanceCount = _setInstances; + instancingBinding.Bind(_context); + RenderCommand.DrawIndexedInstanced(instancingBinding); + + _setInstances = 0; + } + + int TextureComparison(QueuedQuad lhs, QueuedQuad rhs) + { + return (int)Internal.UnsafeCastToPtr(lhs.Texture) - (int)Internal.UnsafeCastToPtr(rhs.Texture); + } + int FrontToBackComparison(QueuedQuad lhs, QueuedQuad rhs) + { + return rhs.Depth <=> lhs.Depth; + } + int BackToFrontComparison(QueuedQuad lhs, QueuedQuad rhs) + { + return lhs.Depth <=> rhs.Depth; + } + + private void SortQuads() + { + switch(_drawOrder) + { + case .SortByTexture: + _quads.Sort(scope => TextureComparison); + case .BackToFront: + _quads.Sort(scope => BackToFrontComparison); + case .FrontToBack: + _quads.Sort(scope => FrontToBackComparison); + default: + } + } + + private void DrawDeferred() + { + if(_quads.Count == 0) + return; + + SortQuads(); + + Texture2D texture = _quads[0].Texture; + texture.Bind(); + + _setInstances = 0; + + for(int i < _quads.Count) + { + var quad = ref _quads[i]; + + // flush every time the texture changes + if(quad.Texture != texture) + { + FlushInstances(); + + texture = quad.Texture; + texture.Bind(); + } + + _rawInstances[_setInstances++] = .(quad.Transform, quad.Color); + } + + FlushInstances(); + + _quads.Clear(); + + Debug.Assert(_quads.Count == 0); + } + } +} diff --git a/Sandbox/BeefProj.toml b/Sandbox/BeefProj.toml index bd23de4..44941ce 100644 --- a/Sandbox/BeefProj.toml +++ b/Sandbox/BeefProj.toml @@ -5,6 +5,7 @@ Dependencies = {GlitchyEngine = "*", corlib = "*", LodePng = "*"} Name = "Sandbox" TargetType = "BeefGUIApplication" StartupObject = "GlitchyEngine.Program" +ProcessorMacros = ["SANDBOX_2D"] [Configs.Debug.Win64] CLibType = "DynamicDebug" diff --git a/Sandbox/content/Shaders/render2dShader.hlsl b/Sandbox/content/Shaders/render2dShader.hlsl new file mode 100644 index 0000000..fc2c6d5 --- /dev/null +++ b/Sandbox/content/Shaders/render2dShader.hlsl @@ -0,0 +1,44 @@ +cbuffer Constants : register(b0) +{ + float3x3 World; + float4 Color; + bool HasTexture; +}; + +Texture2D Texture : register(t0); +SamplerState Sampler : register(s0); + +struct VS_Input +{ + float2 Position : POSITION; +}; + +struct PS_Input +{ + float4 Position : SV_Position; + float2 TexCoord : TEXCOORD; +}; + +PS_Input VS(VS_Input input) +{ + PS_Input output; + + output.TexCoord = input.Position; + output.Position = float4(mul(float3(input.Position, 1.0f), World), 1.0f); + + return output; +} + +float4 PS(PS_Input input) : SV_Target0 +{ + float4 color = Color; + + if(HasTexture) + { + color *= Texture.Sample(Sampler, input.TexCoord); + } + + return color; +} + +#effect[VS=VS, PS=PS] \ No newline at end of file diff --git a/Sandbox/content/Shaders/render2dShaderInst.hlsl b/Sandbox/content/Shaders/render2dShaderInst.hlsl new file mode 100644 index 0000000..b52e98c --- /dev/null +++ b/Sandbox/content/Shaders/render2dShaderInst.hlsl @@ -0,0 +1,36 @@ +Texture2D Texture : register(t0); +SamplerState Sampler : register(s0); + +struct VS_Input +{ + float2 Position : POSITION; + float4x4 Tranform : TRANSFORM; + float4 Color : COLOR; +}; + +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(float4(input.Position, 0.0f, 1.0f), input.Tranform); + output.TexCoord = input.Position; + output.Color = input.Color; + + return output; +} + +float4 PS(PS_Input input) : SV_Target0 +{ + float4 color = input.Color * Texture.Sample(Sampler, input.TexCoord); + + return color; +} + +#effect[VS=VS, PS=PS] \ No newline at end of file diff --git a/Sandbox/src/SandboxApp.bf b/Sandbox/src/SandboxApp.bf index f46368e..c8a795c 100644 --- a/Sandbox/src/SandboxApp.bf +++ b/Sandbox/src/SandboxApp.bf @@ -338,7 +338,9 @@ namespace Sandbox PushLayer(new ExampleLayer()); } +#if !SANDBOX_2D [Export, LinkName("CreateApplication")] +#endif public static Application CreateApplication() { return new SandboxApp(); diff --git a/Sandbox/src/SandboxApp2D.bf b/Sandbox/src/SandboxApp2D.bf new file mode 100644 index 0000000..1407cf8 --- /dev/null +++ b/Sandbox/src/SandboxApp2D.bf @@ -0,0 +1,309 @@ +using System; +using GlitchyEngine; +using GlitchyEngine.Events; +using System.Diagnostics; +using GlitchLog; +using GlitchyEngine.ImGui; +using ImGui; +using GlitchyEngine.Renderer; +using GlitchyEngine.Math; +using GlitchyEngine.World; + +namespace Sandbox +{ + class ExampleLayer2D : Layer + { + struct Vertexy : IVertexData + { + public Vector2 Position; + + public this(Vector2 position) + { + Position = position; + } + + public this(float x, float y) + { + Position = .(x, y); + } + + public static VertexElement[] VertexElements ~ delete _; + + public static VertexElement[] IVertexData.VertexElements => VertexElements; + + static this() + { + VertexElements = new VertexElement[] + ( + VertexElement(.R32G32_Float, "POSITION") + ); + } + } + + RasterizerState _rasterizerState ~ _?.ReleaseRef(); + + GraphicsContext _context ~ _?.ReleaseRef(); + + Texture2D _texture ~ _?.ReleaseRef(); + Texture2D _ge_logo ~ _?.ReleaseRef(); + + BlendState _alphaBlendState ~ _?.ReleaseRef(); + BlendState _opaqueBlendState ~ _?.ReleaseRef(); + + EffectLibrary _effectLibrary ~ delete _; + + EcsWorld _world = new EcsWorld() ~ delete _; + + Renderer2D Renderer2D ~ delete _; + + [AllowAppend] + public this() : base("Example") + { + Application.Get().Window.IsVSync = false; + + _context = Application.Get().Window.Context..AddRef(); + + _effectLibrary = new EffectLibrary(_context); + + _effectLibrary.LoadNoRefInc("content\\Shaders\\basicShader.hlsl"); + + // Create rasterizer state + GlitchyEngine.Renderer.RasterizerStateDescription rsDesc = .(.Solid, .Back, true); + _rasterizerState = new RasterizerState(_context, rsDesc); + + _texture = new Texture2D(_context, "content/Textures/Checkerboard.dds"); + _ge_logo = new Texture2D(_context, "content/Textures/GE_Logo.dds"); + + let sampler = SamplerStateManager.GetSampler( + SamplerStateDescription() + { + MagFilter = .Point + }); + + _texture.SamplerState = sampler; + _ge_logo.SamplerState = sampler; + + sampler.ReleaseRef(); + + BlendStateDescription blendDesc = .(); + blendDesc.RenderTarget[0] = .(true, .SourceAlpha, .InvertedSourceAlpha, .Add, .SourceAlpha, .InvertedSourceAlpha, .Add, .All); + _alphaBlendState = new BlendState(_context, blendDesc); + _opaqueBlendState = new BlendState(_context, .Default); + + InitEcs(); + + Renderer2D = new Renderer2D(_context, _effectLibrary); + //Init2D(); + } + + VertexLayout layout ~ delete _; + + GeometryBinding quadBinding ~ _?.ReleaseRef(); + + /* + void Init2D() + { + var effect = _effectLibrary.Load("content\\Shaders\\render2dShader.hlsl", "Renderer2D"); + + layout = new VertexLayout(_context, Vertexy.VertexElements, effect.VertexShader); + + effect.ReleaseRef(); + + VertexBuffer quadVertices = new VertexBuffer(_context, typeof(Vertexy), 4, .Immutable); + + Vertexy[4] vertices = .( + Vertexy(0, 0), + Vertexy(0, 1), + Vertexy(1, 1), + Vertexy(1, 0) + ); + + quadVertices.SetData(vertices); + + IndexBuffer quadIndices = new IndexBuffer(_context, 6, .Immutable); + + uint16[6] indices = .( + 0, 1, 2, + 2, 3, 0 + ); + + quadIndices.SetData(indices); + + quadBinding = new GeometryBinding(_context); + quadBinding.SetPrimitiveTopology(.TriangleList); + quadBinding.SetVertexBufferSlot(quadVertices, 0); + quadBinding.SetVertexLayout(layout); + quadBinding.SetIndexBuffer(quadIndices); + + quadVertices.ReleaseRef(); + quadIndices.ReleaseRef(); + } + */ + + Entity _cameraEntity; + + void InitEcs() + { + _world.Register(); + _world.Register(); + _world.Register(); + + // Create camera entity + _cameraEntity = _world.NewEntity(); + var cameraTransform = _world.AssignComponent(_cameraEntity); + var camera = _world.AssignComponent(_cameraEntity); + *cameraTransform = TransformComponent(); + camera.NearPlane = 0.1f; + camera.FarPlane = 10.0f; + camera.FovY = Math.PI_f / 4; + cameraTransform.Position = .(0, -1, -5); + /* + for(int x < 20) + for(int y < 20) + { + Entity entity = _world.NewEntity(); + + var transform = _world.AssignComponent(entity); + transform.Transform = Matrix.Translation(x * 0.2f, y * 0.2f, 0) * Matrix.Scaling(0.1f); + + var mesh = _world.AssignComponent(entity); + mesh.Mesh = _quadGeometryBinding; + } + */ + } + + public override void Update(GameTime gameTime) + { + var cameraTransform = _world.GetComponent(_cameraEntity); + + if(Application.Get().Window.IsActive) + { + Vector2 movement = .(); + + if(Input.IsKeyPressed(Key.W)) + { + movement.Y += 1; + } + if(Input.IsKeyPressed(Key.S)) + { + movement.Y -= 1; + } + + if(Input.IsKeyPressed(Key.A)) + { + movement.X -= 1; + } + if(Input.IsKeyPressed(Key.D)) + { + movement.X += 1; + } + + if(movement != .Zero) + movement.Normalize(); + + movement *= (float)(gameTime.FrameTime.TotalSeconds); + + cameraTransform.Position += .(movement, 0); + cameraTransform.Update(); + } + + var camera = _world.GetComponent(_cameraEntity); + camera.Aspect = Application.Get().Window.Context.SwapChain.BackbufferViewport.Width / + Application.Get().Window.Context.SwapChain.BackbufferViewport.Height; + + RenderCommand.Clear(null, .(0.2f, 0.2f, 0.2f)); + + // Draw test geometry + _context.SetRenderTarget(null); + _context.BindRenderTargets(); + + _context.SetRasterizerState(_rasterizerState); + + _context.SetViewport(_context.SwapChain.BackbufferViewport); + + Renderer2D.Begin(.SortByTexture, .(80, 80)); + + //_opaqueBlendState.Bind(); + _alphaBlendState.Bind(); + + //Renderer2D.DrawQuad(5, 5, 1, 1, .Red); + //Renderer2D.DrawQuad(5, 5, 1, 1, .Blue); + + Random r = scope Random(1337); + + //_texture.Bind(0); + //_ge_logo.Bind(1); + + for(int x < 40) + for(int y < 40) + { + int i = (x + y) % 2 + 1;//((x + (y * 40)) ^ (x * y)) % 3; + + if(i == 1) + { + Renderer2D.Draw(_texture, 2 * x, 2 * y, 1, 1, .(r.Next(0, 256), r.Next(0, 256), r.Next(0, 256))); + } + else if(i == 2) + { + Renderer2D.Draw(_ge_logo, 2 * x, 2 * y, 1, 1, .(r.Next(0, 256), r.Next(0, 256), r.Next(0, 256))); + } + } + + Renderer2D.Draw(_texture, 0, 0, 20, 10, .White, 20); + + /* + Renderer2D.Draw(_texture, 10, 50, 100, 100, .White); + + Renderer2D.Draw(_texture, 120, 50, 100, 100, .White); + + Renderer2D.Draw(_texture, 10, 160, 100, 100, .White); + + Renderer2D.Draw(_texture, 120, 160, 100, 100, .White); + */ + //_alphaBlendState.Bind(); + + //Renderer2D.Draw(_ge_logo, 80, 50, 100, 100, .White); + + Renderer2D.End(); + } + + ColorRGBA _squareColor0 = ColorRGBA.CornflowerBlue; + ColorRGBA _squareColor1; + + public override void OnEvent(Event event) + { + EventDispatcher dispatcher = scope EventDispatcher(event); + + dispatcher.Dispatch(scope (e) => OnImGuiRender(e)); + } + + private bool OnImGuiRender(ImGuiRenderEvent e) + { + ImGui.Begin("Test"); + + ImGui.ColorEdit3("Square Color", ref _squareColor0); + + _squareColor1 = ColorRGBA.White - _squareColor0; + + ImGui.End(); + + return false; + } + } + + class SandboxApp2D : Application + { + public this() + { + PushLayer(new ExampleLayer2D()); + } + +#if SANDBOX_2D + [Export, LinkName("CreateApplication")] +#endif + public static Application CreateApplication() + { + return new SandboxApp2D(); + } + } +} \ No newline at end of file