From 5954fbc411244bd14c47c65315d256a432ee6f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Wed, 25 Nov 2020 13:03:40 +0100 Subject: [PATCH] Render basic geometry Added GraphicsContext, SwapChain, Buffer, VertexBuffer, IndexBuffer and a basic DX11 implementation. --- GlitchyEngine/src/Application.bf | 184 +++++++++++- GlitchyEngine/src/ImGui/ImGuiLayer.bf | 13 +- GlitchyEngine/src/Math/MathDefs.bf | 14 + GlitchyEngine/src/Math/Point.bf | 2 + GlitchyEngine/src/Platform/DX11/DirectX.bf | 169 ----------- .../src/Platform/DX11/Dx11Cheater.bf | 17 ++ .../src/Platform/DX11/Renderer/Dx11Buffer.bf | 135 +++++++++ .../DX11/Renderer/Dx11GraphicsContext.bf | 159 ++++++++++ .../Platform/DX11/Renderer/Dx11SwapChain.bf | 155 ++++++++++ .../src/Platform/Windows/WindowsWindow.bf | 19 +- GlitchyEngine/src/Renderer/Buffer.bf | 279 ++++++++++++++++++ GlitchyEngine/src/Renderer/GraphicsContext.bf | 80 +++++ GlitchyEngine/src/Renderer/IndexBuffer.bf | 32 ++ GlitchyEngine/src/Renderer/SwapChain.bf | 30 ++ GlitchyEngine/src/Renderer/VertexBuffer.bf | 45 +++ GlitchyEngine/src/Renderer/Viewport.bf | 30 ++ GlitchyEngine/src/Window.bf | 6 + Sandbox/content/basicShader.hlsl | 25 ++ Sandbox/content/directx.ico | Bin 0 -> 118854 bytes Sandbox/src/SandboxApp.bf | 5 +- 20 files changed, 1219 insertions(+), 180 deletions(-) create mode 100644 GlitchyEngine/src/Math/MathDefs.bf delete mode 100644 GlitchyEngine/src/Platform/DX11/DirectX.bf create mode 100644 GlitchyEngine/src/Platform/DX11/Dx11Cheater.bf create mode 100644 GlitchyEngine/src/Platform/DX11/Renderer/Dx11Buffer.bf create mode 100644 GlitchyEngine/src/Platform/DX11/Renderer/Dx11GraphicsContext.bf create mode 100644 GlitchyEngine/src/Platform/DX11/Renderer/Dx11SwapChain.bf create mode 100644 GlitchyEngine/src/Renderer/Buffer.bf create mode 100644 GlitchyEngine/src/Renderer/GraphicsContext.bf create mode 100644 GlitchyEngine/src/Renderer/IndexBuffer.bf create mode 100644 GlitchyEngine/src/Renderer/SwapChain.bf create mode 100644 GlitchyEngine/src/Renderer/VertexBuffer.bf create mode 100644 GlitchyEngine/src/Renderer/Viewport.bf create mode 100644 Sandbox/content/basicShader.hlsl create mode 100644 Sandbox/content/directx.ico diff --git a/GlitchyEngine/src/Application.bf b/GlitchyEngine/src/Application.bf index 999a33a..ca173b2 100644 --- a/GlitchyEngine/src/Application.bf +++ b/GlitchyEngine/src/Application.bf @@ -2,6 +2,12 @@ using System; using GlitchyEngine.Events; using GlitchyEngine.Platform.DX11; using GlitchyEngine.ImGui; +using GlitchyEngine.Math; +using DirectX.D3D11; +using DirectX.Common; +using DirectX.D3DCompiler; +using System.Diagnostics; +using GlitchyEngine.Renderer; namespace GlitchyEngine { @@ -34,6 +40,156 @@ namespace GlitchyEngine _imGuiLayer = new ImGuiLayer(); PushOverlay(_imGuiLayer); + + MakeTestTriangle(); + } + + struct VertexColor : IVertexData + { + public Vector3 Position; + public Color Color; + + public this() => this = default; + + public this(Vector3 pos, Color color) + { + Position = pos; + Color = color; + } + + //public static readonly InputElementDescription[] InputLayout ~ delete _; + + public static readonly VertexLayout VertexLayout ~ delete _; + + public static VertexLayout IVertexData.VertexLayout => VertexLayout; + + static this() + { + //VertexLayout = new VertexLayout(); + } + } + + VertexBuffer _vertexBuffer ~ delete _; + IndexBuffer _indexBuffer ~ delete _; + + ID3D11VertexShader* _vertexShader ~ _?.Release(); + ID3D11PixelShader* _pixelShader ~ _?.Release(); + ID3D11InputLayout* _inputLayout ~ _?.Release(); + + ID3D11RasterizerState* _rasterizerState ~ _?.Release(); + + private Vector3 CircleCoord(float angle) + { + return .(Math.Cos(angle), Math.Sin(angle), 0); + } + + private void MakeTestTriangle() + { + // Compile vertex shader + ID3DBlob* vsCode = null; + ID3DBlob* errorBlob = null; + var result = D3DCompiler.D3DCompileFromFile("content\\basicShader.hlsl".ToScopedNativeWChar!(), null, .StandardInclude, "VS", "vs_5_0", .Debug, .None, &vsCode, &errorBlob); + + if(result.Failed || errorBlob != null) + { + Debug.Write("ERROR: Failed to compile Vertex Shader: {}", result); + //ErrorPrinter.PrintErrorBlob(errorBlob); + Runtime.FatalError("Failed to compile Vertex Shader"); + } + + result = Dx11Cheater.Device.CreateVertexShader(vsCode.GetBufferPointer(), vsCode.GetBufferSize(), null, &_vertexShader); + + if(result.Failed) + { + Debug.Write("ERROR: Failed to create Vertex Shader: {}", result); + Runtime.FatalError("Failed to create Vertex Shader"); + } + + // Create Input Layout + + InputElementDescription[2] elementDescs = .( + InputElementDescription("POSITION", 0, .R32G32B32_Float, 0), + InputElementDescription("COLOR", 0, .R8G8B8A8_UNorm, 0) + ); + + result = Dx11Cheater.Device.CreateInputLayout(&elementDescs, (.)elementDescs.Count, vsCode.GetBufferPointer(), vsCode.GetBufferSize(), &_inputLayout); + + vsCode.Release(); + + if(result.Failed) + { + Debug.Write("ERROR: Failed to create input layout: {}", result); + Runtime.FatalError("Failed to create input layout"); + } + + // + // Load pixel shader + // + + ID3DBlob* psCode = null; + + result = D3DCompiler.D3DCompileFromFile("content\\basicShader.hlsl".ToScopedNativeWChar!(), null, .StandardInclude, "PS", "ps_5_0", .Debug, .None, &psCode, &errorBlob); + + if(result.Failed || errorBlob != null) + { + Debug.Write("ERROR: Failed to compile Pixel Shader: {}", result); + //ErrorPrinter.PrintErrorBlob(errorBlob); + Runtime.FatalError("Failed to compile Pixel Shader"); + } + + result = Dx11Cheater.Device.CreatePixelShader(psCode.GetBufferPointer(), psCode.GetBufferSize(), null, &_pixelShader); + + psCode.Release(); + + if(result.Failed) + { + Debug.Write("ERROR: Failed to create Pixel Shader: {}", result); + Runtime.FatalError("Failed to create Pixel Shader"); + } + + float pO3 = Math.PI_f / 3.0f; + VertexColor[?] vertices = .( + VertexColor(.Zero, Color(255,255,255)), + VertexColor(CircleCoord(0), Color(255, 0, 0)), + VertexColor(CircleCoord(pO3), Color(255,255, 0)), + VertexColor(CircleCoord(pO3*2), Color( 0,255, 0)), + VertexColor(CircleCoord(Math.PI_f), Color( 0,255,255)), + VertexColor(CircleCoord(-pO3*2), Color( 0, 0,255)), + VertexColor(CircleCoord(-pO3), Color(255, 0,255)), + ); + + _vertexBuffer = new VertexBuffer(Window.Context, (.)vertices.Count, .Immutable); + _vertexBuffer.SetData(vertices); + + uint16[?] indices = .( + 0, 1, 2, + 0, 2, 3, + 0, 3, 4, + 0, 4, 5, + 0, 5, 6, + 0, 6, 1); + + _indexBuffer = new IndexBuffer(Window.Context, (.)indices.Count, .Immutable); + _indexBuffer.SetData(indices); + + if(result.Failed) + { + Debug.Write("ERROR: Failed to create Vertex Buffer: {}", result); + Runtime.FatalError(); + } + + // Create rasterizer state + RasterizerStateDescription rsDesc = .(); + rsDesc.CullMode = .Back; + rsDesc.FillMode = .Solid; + rsDesc.FrontCounterClockwise = true; + + result = Dx11Cheater.Device.CreateRasterizerState(ref rsDesc, &_rasterizerState); + if(result.Failed) + { + Debug.Write("ERROR: Failed to create Rasterizer State: {}", result); + Runtime.FatalError("Failed to create Rasterizer State"); + } } public void OnEvent(Event e) @@ -57,16 +213,38 @@ namespace GlitchyEngine Input.NewFrame(); - DirectX.ImmediateContext.ClearRenderTargetView(DirectX.BackBufferTarget, .(1, 0, 1)); + _window.Context.ClearRenderTarget(null, .(0.2f, 0.2f, 0.2f)); for(Layer layer in _layerStack) layer.Update(_gameTime); - + _window.Update(); + + _window.Context.SetRenderTarget(null); + _window.Context.BindRenderTargets(); + + Window.Context.SetVertexBuffer(0, _vertexBuffer); + Window.Context.SetIndexBuffer(_indexBuffer); + + var _immediateContext = Dx11Cheater.ImmediateContext; + + _immediateContext.InputAssembler.SetInputLayout(_inputLayout); + _immediateContext.InputAssembler.SetPrimitiveTopology(.TriangleList); + + _immediateContext.VertexShader.SetShader(_vertexShader, null, 0); + + _immediateContext.Rasterizer.SetState(_rasterizerState); + + + _immediateContext.Rasterizer.SetViewports(1, &Dx11Cheater.BackbufferViewport); + + _immediateContext.PixelShader.SetShader(_pixelShader, null, 0); + + Window.Context.DrawIndexed(3 * 6); _imGuiLayer.ImGuiRender(); - DirectX.Present(); + Window.Context.SwapChain.Present(); } } diff --git a/GlitchyEngine/src/ImGui/ImGuiLayer.bf b/GlitchyEngine/src/ImGui/ImGuiLayer.bf index 89e1e5c..e3609f7 100644 --- a/GlitchyEngine/src/ImGui/ImGuiLayer.bf +++ b/GlitchyEngine/src/ImGui/ImGuiLayer.bf @@ -4,6 +4,7 @@ using GlitchyEngine.Events; // Temporary using GlitchyEngine.Platform.DX11; +//using GlitchyEngine.Platform.DX11.Renderer; namespace GlitchyEngine.ImGui { @@ -40,8 +41,12 @@ namespace GlitchyEngine.ImGui #if GE_WINDOWS // Todo: temporary, needs to be platform independent ImGuiImplWin32.Init((Windows.HWnd)(int)Application.Get().Window.NativeWindow); - ImGuiImplDX11.Init(Platform.DX11.DirectX.Device, Platform.DX11.DirectX.ImmediateContext); + + var context = Application.Get().Window.Context; + + ImGuiImplDX11.Init(context.[Friend]nativeDevice, context.[Friend]nativeContext); #endif + } public override void OnDetach() @@ -174,8 +179,10 @@ namespace GlitchyEngine.ImGui public void Begin() { - var v = DirectX.ImmediateContext; - v.OutputMerger.SetRenderTargets(1, &DirectX.BackBufferTarget, null); + // Todo: + //var v = DirectX.ImmediateContext; + //v.OutputMerger.SetRenderTargets(1, &DirectX.BackBufferTarget, null); + Application.Get().Window.Context.SetRenderTarget(null); ImGuiImplDX11.NewFrame(); ImGuiImplWin32.NewFrame(); diff --git a/GlitchyEngine/src/Math/MathDefs.bf b/GlitchyEngine/src/Math/MathDefs.bf new file mode 100644 index 0000000..604ccc4 --- /dev/null +++ b/GlitchyEngine/src/Math/MathDefs.bf @@ -0,0 +1,14 @@ +namespace GlitchyEngine.Math +{ + typealias Color = DirectX.Color; + typealias ColorRGB = DirectX.ColorRGB; + typealias ColorRGBA = DirectX.ColorRGBA; + + typealias Vector2 = DirectX.Math.Vector2; + typealias Vector3 = DirectX.Math.Vector3; + typealias Vector4 = DirectX.Math.Vector4; + + typealias Matrix3x3 = DirectX.Math.Matrix3x3; + typealias Matrix4x3 = DirectX.Math.Matrix4x3; + typealias Matrix = DirectX.Math.Matrix; +} diff --git a/GlitchyEngine/src/Math/Point.bf b/GlitchyEngine/src/Math/Point.bf index 535f52c..91ae8fb 100644 --- a/GlitchyEngine/src/Math/Point.bf +++ b/GlitchyEngine/src/Math/Point.bf @@ -2,6 +2,7 @@ using System; namespace GlitchyEngine.Math { + /** * A 2D point represented by two 32bit integers. */ @@ -126,4 +127,5 @@ namespace GlitchyEngine.Math // public override void ToString(String strBuffer) => strBuffer.AppendF("X={0} Y={1}", X, Y); } + } diff --git a/GlitchyEngine/src/Platform/DX11/DirectX.bf b/GlitchyEngine/src/Platform/DX11/DirectX.bf deleted file mode 100644 index 8a6e47e..0000000 --- a/GlitchyEngine/src/Platform/DX11/DirectX.bf +++ /dev/null @@ -1,169 +0,0 @@ -using System; -using DirectX; -using DirectX.D3D11; -using DirectX.Common; -using System.Diagnostics; -using DirectX.DXGI; -using DirectX.DXGI.DXGI1_2; -using DirectX.D3D11.SDKLayers; -using static System.Windows; - -namespace GlitchyEngine.Platform.DX11 -{ - public static class DirectX - { - public static ID3D11Device* Device; - public static ID3D11DeviceContext* ImmediateContext; - public static ID3D11Debug* DebugDevice; - - public static IDXGIDevice* DxgiDevice; - public static IDXGISwapChain1* SwapChain; - - public static ID3D11RenderTargetView* BackBufferTarget; - public static Viewport BackBufferViewport; - - static HWnd _windowHandle; - - static ~this() - { - Shutdown(); - } - - public static void Init(HWnd windowHandle, uint32 width, uint32 height) - { - _windowHandle = windowHandle; - - InitDevice(); - UpdateSwapchain(width, height); - } - - /** - * Initializes the Device and ImmediateContext. - */ - static void InitDevice() - { - Device?.Release(); - Device = null; - - ImmediateContext?.Release(); - ImmediateContext = null; - - Log.EngineLogger.Trace("Creating D3D11 Device and Context..."); - - DeviceCreationFlags deviceFlags = .None; -#if DEBUG - deviceFlags |= .Debug; -#endif - - FeatureLevel[] levels = scope .(.Level_11_0); - - var deviceResult = D3D11.CreateDevice(null, .Hardware, 0, deviceFlags, levels, &Device, let deviceLevel, &ImmediateContext); - Debug.Assert(deviceResult.Succeeded, scope $"Failed to create D3D11 Device. Message(0x{(int32)deviceResult}): {deviceResult}"); - -#if DEBUG - if(Device.QueryInterface(out DebugDevice).Succeeded) - { - ID3D11InfoQueue* infoQueue; - if(Device.QueryInterface(out infoQueue).Succeeded) - { - infoQueue.SetBreakOnSeverity(.Corruption, true); - infoQueue.SetBreakOnSeverity(.Error, true); - - infoQueue.Release(); - } - } -#endif - - Log.EngineLogger.Trace("D3D11 Device and Context created (Feature level: {})", deviceLevel); - } - - static uint32 _width, _height; - - /** - * Initializes the swapchain. - */ - public static void UpdateSwapchain(uint32 width, uint32 height) - { - if(_width == width && _height == height) - return; - - _width = width; - _height = height; - - Log.EngineLogger.Trace("Updating swap chain ({}, {})", width, height); - - uint32 backBufferCount = 2; - Format backBufferFormat = .R8G8B8A8_UNorm; - Format backBufferViewFormat = .R8G8B8A8_UNorm; // _SRGB - - if(SwapChain != null) - { - BackBufferTarget.Release(); - - var resizeResult = SwapChain.ResizeBuffers(backBufferCount, width, height, backBufferFormat, .None); - Debug.Assert(resizeResult.Succeeded, scope $"Failed to resize swap chain. Message({(int32)resizeResult}):{resizeResult}"); - } - else - { - Device.QueryInterface(out DxgiDevice); - - DxgiDevice.GetAdapter(let adapter); - adapter.GetParent(let factory); - adapter.Release(); - - SwapChainDescription1 swDesc = .(); - swDesc.Width = width; - swDesc.Height = height; - swDesc.Format = backBufferFormat; - swDesc.SampleDescription = .(1, 0); - swDesc.BufferUsage = .RenderTargetOutput; - swDesc.BufferCount = backBufferCount; - swDesc.SwapEffect = .FlipDiscard; - - SwapChainFullscreenDescription fsSwapChainDesc = .(); - fsSwapChainDesc.Windowed = true; - - var createResult = factory.CreateSwapChainForHwnd((.)Device, _windowHandle, ref swDesc, &fsSwapChainDesc, null, &SwapChain); - Debug.Assert(createResult.Succeeded, scope $"Failed to create swap chain. Message({(int32)createResult}):{createResult}"); - - factory.Release(); - } - - SwapChain.GetBuffer(0, let backBuffer); - - RenderTargetViewDescription rtvDesc = .(backBuffer, .Texture2D, backBufferViewFormat); - Device.CreateRenderTargetView(backBuffer, &rtvDesc, &BackBufferTarget); - - BackBufferViewport = Viewport(0, 0, width, height, 0.0f, 1.0f); - - backBuffer.Release(); - } - - public static void Shutdown() - { - Device?.Release(); - Device = null; - - ImmediateContext?.Release(); - ImmediateContext = null; - - DxgiDevice?.Release(); - DxgiDevice = null; - - SwapChain?.Release(); - SwapChain = null; - - BackBufferTarget?.Release(); - BackBufferTarget = null; - - DebugDevice.ReportLiveDeviceObjects(.Detail); - DebugDevice?.Release(); - DebugDevice = null; - } - - public static void Present() - { - SwapChain.Present(Application.Get().Window.IsVSync ? 1 : 0, .None); - } - } -} diff --git a/GlitchyEngine/src/Platform/DX11/Dx11Cheater.bf b/GlitchyEngine/src/Platform/DX11/Dx11Cheater.bf new file mode 100644 index 0000000..d6c76dd --- /dev/null +++ b/GlitchyEngine/src/Platform/DX11/Dx11Cheater.bf @@ -0,0 +1,17 @@ +using DirectX.D3D11; +using DirectX.DXGI; + +namespace GlitchyEngine.Platform.DX11 +{ + public static class Dx11Cheater + { + public static ID3D11Device* Device; + public static ID3D11DeviceContext* ImmediateContext; + + public static ID3D11DeviceContext* Context => ImmediateContext; + + public static IDXGISwapChain* SwapChain; + public static ID3D11RenderTargetView* BackbufferTarget; + public static Viewport BackbufferViewport; + } +} diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Buffer.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Buffer.bf new file mode 100644 index 0000000..4c4898e --- /dev/null +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Buffer.bf @@ -0,0 +1,135 @@ +using System.Diagnostics; +using System; +using DirectX.D3D11; + +using internal GlitchyEngine.Renderer; + +namespace GlitchyEngine.Renderer +{ + extension CPUAccessFlags + { + public static explicit operator DirectX.D3D11.CpuAccessFlags(Self cpuAccessFlags) + { + DirectX.D3D11.CpuAccessFlags flags = .None; + + if(cpuAccessFlags.HasFlag(.Read)) + flags |= .Read; + + if(cpuAccessFlags.HasFlag(.Write)) + flags |= .Write; + + return flags; + } + } + + extension BufferDescription + { + public static operator DirectX.D3D11.BufferDescription(Self desc) + { + DirectX.D3D11.BufferDescription result; + + result.ByteWidth = desc.Size; + result.Usage = (.)desc.Usage; + + result.CpuAccessFlags = (.)desc.CPUAccess; + + result.BindFlags = .None; + if(desc.BindFlags.HasFlag(.Constant)) + { + result.BindFlags |= .ConstantBuffer; + } + if(desc.BindFlags.HasFlag(.Index)) + { + result.BindFlags |= .IndexBuffer; + } + if(desc.BindFlags.HasFlag(.Vertex)) + { + result.BindFlags |= .VertexBuffer; + } + + result.MiscFlags = .None; + result.StructureByteStride = 0; + + return result; + } + } + + public extension MapType + { + public static explicit operator DirectX.D3D11.MapType(Self mapType) + { + return (.)(uint32)mapType; + } + } + + public extension Buffer + { + // Todo: nativeBuffer contains exactly that! + internal DirectX.D3D11.BufferDescription nativeDescription; + + internal ID3D11Buffer* nativeBuffer ~ _?.Release(); + + private Result InternalCreateBuffer(void* data, uint32 byteLength, uint32 dstByteOffset) + { + nativeDescription = (.)_description; + + uint8* byteData = (.)data; + + if(dstByteOffset == 0) + { + byteData = new uint8[nativeDescription.ByteWidth]*; + defer:: delete byteData; + Internal.MemCpy(byteData + dstByteOffset, data, byteLength); + } + + SubresourceData srData = .(byteData, byteLength, 0); + var result = _context.nativeDevice.CreateBuffer(ref nativeDescription, &srData, &nativeBuffer); + if(result.Failed) + { + Log.EngineLogger.Error("Failed to create buffer. Message({}):{}", (int)result, result); + return .Err; + } + + return .Ok; + } + + protected override Result PlatformSetData(void* data, uint32 byteLength, uint32 dstByteOffset, GlitchyEngine.Renderer.MapType mapType) + { + if(nativeBuffer == null) + { + // We can pass the data while creating the buffer, so we can return here. + return InternalCreateBuffer(data, byteLength, dstByteOffset); + } + + Debug.Assert(dstByteOffset + byteLength <= _description.Size, "The destination offset and byte length are too long for the target buffer."); + + switch(nativeDescription.Usage) + { + case .Default: + Box dataBox = .(dstByteOffset, 0, 0, dstByteOffset + byteLength, 1, 1); + _context.nativeContext.UpdateSubresource(nativeBuffer, 0, &dataBox, data, byteLength, byteLength); + case .Dynamic: + Debug.Assert(mapType.CanWrite, "The map type has to have write access."); + // Todo: DoNotWaitFlag + MappedSubresource map = ?; + _context.nativeContext.Map(nativeBuffer, 0, (.)mapType, .None, &map); + Internal.MemCpy(((uint8*)map.Data) + dstByteOffset, data, byteLength); + _context.nativeContext.Unmap(nativeBuffer, 0); + Runtime.NotImplemented(); + case .Immutable: + Log.EngineLogger.Error("Can't set the data of an immutable resource."); + return .Err; + default: + Log.EngineLogger.Error("Unknown resource usage: engine={}, native={}", _description.Usage, nativeDescription.Usage); + return .Err; + } + + return .Ok; + } + } + + public extension VertexBuffer + { + + } +} diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11GraphicsContext.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11GraphicsContext.bf new file mode 100644 index 0000000..b92cc5a --- /dev/null +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11GraphicsContext.bf @@ -0,0 +1,159 @@ +using System; +using System.Diagnostics; +using DirectX.Common; +using DirectX.D3D11; +using DirectX.D3D11.SDKLayers; +using DirectX.DXGI.DXGI1_2; +using GlitchyEngine.Renderer; +using GlitchyEngine.Math; + +using GlitchyEngine.Platform.DX11; + +using internal GlitchyEngine.Renderer; +//using internal GlitchyEngine.Platform.DX11.Renderer; + +namespace GlitchyEngine.Renderer +//namespace GlitchyEngine.Platform.DX11.Renderer +{ + /// DirectX 11 specific implementation of the GraphicsContext + extension GraphicsContext + //public class Dx11Context : GraphicsContext + { + //private Dx11SwapChain _swapChain; + private SwapChain _swapChain; + + internal Windows.HWnd nativeWindowHandle; + + internal ID3D11Device* nativeDevice; + internal ID3D11DeviceContext* nativeContext; + + private ID3D11Debug* _debugDevice; + + public override SwapChain SwapChain => _swapChain; + + private const uint32 MaxRTVCount = DirectX.D3D11.D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT; + + //public static override uint32 MaxRenderTargetCount() => MaxRTVCount; + + public this(Windows.HWnd windowHandle) + { + nativeWindowHandle = windowHandle; + + _swapChain = new SwapChain(this); + //_swapChain = new Dx11SwapChain(this); + } + + public ~this() + { + delete _swapChain; + + nativeDevice?.Release(); + nativeContext?.Release(); + _debugDevice.ReportLiveDeviceObjects(.Detail); + _debugDevice?.Release(); + } + + public override void Init() + { + InitDevice(); + + SwapChain.Init(); + } + + /** + * Initializes the Device and ImmediateContext. + */ + void InitDevice() + { + nativeDevice?.Release(); + nativeContext?.Release(); + + Log.EngineLogger.Trace("Creating D3D11 Device and Context..."); + + DeviceCreationFlags deviceFlags = .None; +#if DEBUG + deviceFlags |= .Debug; +#endif + + FeatureLevel[] levels = scope .(.Level_11_0); + + var deviceResult = D3D11.CreateDevice(null, .Hardware, 0, deviceFlags, levels, &nativeDevice, let deviceLevel, &nativeContext); + Debug.Assert(deviceResult.Succeeded, scope $"Failed to create D3D11 Device. Message(0x{(int32)deviceResult}): {deviceResult}"); + +#if DEBUG + if(nativeDevice.QueryInterface(out _debugDevice).Succeeded) + { + ID3D11InfoQueue* infoQueue; + if(nativeDevice.QueryInterface(out infoQueue).Succeeded) + { + infoQueue.SetBreakOnSeverity(.Corruption, true); + infoQueue.SetBreakOnSeverity(.Error, true); + + infoQueue.Release(); + } + } +#endif + + Dx11Cheater.Device = nativeDevice; + Dx11Cheater.ImmediateContext = nativeContext; + + Log.EngineLogger.Trace("D3D11 Device and Context created (Feature level: {})", deviceLevel); + } + + + + private ID3D11RenderTargetView*[MaxRTVCount] _renderTargets; + + public override void SetRenderTarget(RenderTarget renderTarget, int slot = 0) + { + if(renderTarget == null) + { + _renderTargets[slot] = _swapChain.nativeBackBufferTarget; + //_immediateContext.OutputMerger.SetRenderTargets(1, &_swapChain._backBufferTarget, null); + } + else + { + Runtime.NotImplemented(); + } + } + + public override void BindRenderTargets() + { + nativeContext.OutputMerger.SetRenderTargets(MaxRTVCount, &_renderTargets, null); + } + + public override void ClearRenderTarget(RenderTarget renderTarget, ColorRGBA color) + { + if(renderTarget == null) + { + nativeContext.ClearRenderTargetView(_swapChain.nativeBackBufferTarget, color); + } + else + { + Runtime.NotImplemented(); + } + } + + public override void SetVertexBuffer(uint32 slot, Buffer buffer, uint32 stride, uint32 offset = 0) + { + // make stride and offset mutable so that we can take their pointers. + var stride, offset; + nativeContext.InputAssembler.SetVertexBuffers(slot, 1, &buffer.nativeBuffer, &stride, &offset); + } + + public override void Draw(uint32 vertexCount, uint32 startVertexIndex = 0) + { + nativeContext.Draw(vertexCount, startVertexIndex); + } + + public override void DrawIndexed(uint32 indexCount, uint32 startIndexLocation = 0, int32 vertexOffset = 0) + { + nativeContext.DrawIndexed(indexCount, startIndexLocation, vertexOffset); + } + + public override void SetIndexBuffer(Buffer buffer, IndexFormat indexFormat = .Index16Bit, uint32 byteOffset = 0) + { + nativeContext.InputAssembler.SetIndexBuffer(buffer.nativeBuffer, indexFormat == .Index32Bit ? .R32_UInt : .R16_UInt, byteOffset); + } + } +} diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11SwapChain.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11SwapChain.bf new file mode 100644 index 0000000..8c3435d --- /dev/null +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11SwapChain.bf @@ -0,0 +1,155 @@ +using DirectX.D3D11; +using DirectX.DXGI.DXGI1_2; +using GlitchyEngine.Platform.DX11; +using System.Diagnostics; + +using internal GlitchyEngine.Renderer; + +namespace GlitchyEngine.Renderer +{ + public extension SwapChain + { + private GraphicsContext _context; + + private bool _changed; + + private uint32 _width, _height; + + internal DirectX.DXGI.IDXGIDevice* nativeDxgiDevice; + internal IDXGISwapChain1* nativeSwapChain; + + internal ID3D11RenderTargetView* nativeBackBufferTarget; + private DirectX.D3D11.Viewport _backBufferViewport; + + public override uint32 Width + { + get => _width; + + set + { + if(_width == value) + return; + + _width = value; + _changed = true; + } + } + + public override uint32 Height + { + get => _height; + + set + { + if(_height == value) + return; + + _height = value; + _changed = true; + } + } + + public override GraphicsContext Context => _context; + + //public this(Dx11Context context) + public this(GraphicsContext context) + { + _context = context; + + SetResolutionFromWindow(); + } + + public ~this() + { + nativeDxgiDevice?.Release(); + nativeSwapChain?.Release(); + nativeBackBufferTarget?.Release(); + } + + void SetResolutionFromWindow() + { + DirectX.Windows.Winuser.GetClientRect(_context.nativeWindowHandle, let rect); + + Width = (.)(rect.Right - rect.Left); + Height = (.)(rect.Bottom - rect.Top); + } + + public override void Init() + { + ApplyChanges(); + } + + public override void ApplyChanges() + { + if(!_changed) + return; + + UpdateSwapchain(); + } + + /** + * Initializes the swapchain. + */ + public void UpdateSwapchain() + { + Log.EngineLogger.Trace("Updating swap chain ({}, {})", _width, _height); + + uint32 backBufferCount = 2; + Format backBufferFormat = .R8G8B8A8_UNorm; + Format backBufferViewFormat = .R8G8B8A8_UNorm; // _SRGB + + if(nativeSwapChain != null) + { + nativeBackBufferTarget.Release(); + + var resizeResult = nativeSwapChain.ResizeBuffers(backBufferCount, _width, _height, backBufferFormat, .None); + Debug.Assert(resizeResult.Succeeded, scope $"Failed to resize swap chain. Message({(int32)resizeResult}):{resizeResult}"); + } + else + { + _context.nativeDevice.QueryInterface(out nativeDxgiDevice); + + nativeDxgiDevice.GetAdapter(let adapter); + adapter.GetParent(let factory); + adapter.Release(); + + SwapChainDescription1 swDesc = .(); + swDesc.Width = _width; + swDesc.Height = _height; + swDesc.Format = backBufferFormat; + swDesc.SampleDescription = .(1, 0); + swDesc.BufferUsage = .RenderTargetOutput; + swDesc.BufferCount = backBufferCount; + swDesc.SwapEffect = .FlipDiscard; + + SwapChainFullscreenDescription fsSwapChainDesc = .(); + fsSwapChainDesc.Windowed = true; + + var createResult = factory.CreateSwapChainForHwnd((.)_context.nativeDevice, _context.nativeWindowHandle, ref swDesc, &fsSwapChainDesc, null, &nativeSwapChain); + Debug.Assert(createResult.Succeeded, scope $"Failed to create swap chain. Message({(int32)createResult}):{createResult}"); + + factory.Release(); + + Dx11Cheater.SwapChain = nativeSwapChain; + } + + nativeSwapChain.GetBuffer(0, let backBuffer); + + RenderTargetViewDescription rtvDesc = .(backBuffer, .Texture2D, backBufferViewFormat); + _context.nativeDevice.CreateRenderTargetView(backBuffer, &rtvDesc, &nativeBackBufferTarget); + + Dx11Cheater.BackbufferTarget = nativeBackBufferTarget; + + _backBufferViewport = DirectX.D3D11.Viewport(0, 0, _width, _height, 0.0f, 1.0f); + + Dx11Cheater.BackbufferViewport = _backBufferViewport; + + backBuffer.Release(); + } + + public override void Present() + { + nativeSwapChain.Present(Application.Get().Window.IsVSync ? 1 : 0, .None); + } + } +} diff --git a/GlitchyEngine/src/Platform/Windows/WindowsWindow.bf b/GlitchyEngine/src/Platform/Windows/WindowsWindow.bf index 5e2827e..fdaf54e 100644 --- a/GlitchyEngine/src/Platform/Windows/WindowsWindow.bf +++ b/GlitchyEngine/src/Platform/Windows/WindowsWindow.bf @@ -9,6 +9,8 @@ using GlitchyEngine.Events; using System.Diagnostics; using GlitchyEngine.Platform.DX11; using GlitchyEngine.Math; +//using GlitchyEngine.Platform.DX11.Renderer; +using GlitchyEngine.Renderer; using static System.Windows; namespace GlitchyEngine @@ -31,6 +33,10 @@ namespace GlitchyEngine private bool _isVSync = true; + private GraphicsContext _graphicsContext ~ delete _; + + public override GraphicsContext Context => _graphicsContext; + public override int32 MinWidth { get => _minMaxInfo.MinimumTrackingSize.x; @@ -191,16 +197,19 @@ namespace GlitchyEngine _windowHandle = CreateWindowExW(.None, _windowClass.ClassName, desc.Title.ToScopedNativeWChar!(), .WS_OVERLAPPEDWINDOW | .WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, desc.Width, desc.Height, 0, 0, (.)_instanceHandle, null); + + //_graphicsContext = new Dx11Context(_windowHandle); + _graphicsContext = new GraphicsContext(_windowHandle); + _graphicsContext.Init(); void* myPtr = Internal.UnsafeCastToPtr(this); - SetWindowLongPtrW(_windowHandle, GWL_USERDATA, (int)myPtr); LoadWindowRectangle(); Log.EngineLogger.Trace("Created window \"{}\" ({}, {})", Title, Width, Height); - DirectX.Init(_windowHandle, (.)Width, (.)Height); + //DirectX.Init(_windowHandle, (.)Width, (.)Height); } private bool _isResizingOrMoving; @@ -249,7 +258,11 @@ namespace GlitchyEngine { SplitHighAndLowOrder!(lParam, out window._clientRect.Width, out window._clientRect.Height); - DirectX.UpdateSwapchain((.)window._clientRect.Width, (.)window._clientRect.Height); + window._graphicsContext.SwapChain.Width = (.)window._clientRect.Width; + window._graphicsContext.SwapChain.Height = (.)window._clientRect.Height; + + window._graphicsContext.SwapChain.ApplyChanges(); + WindowResizeEvent event = scope WindowResizeEvent(window._clientRect.Width, window._clientRect.Height, window._isResizingOrMoving); window._eventCallback(event); } diff --git a/GlitchyEngine/src/Renderer/Buffer.bf b/GlitchyEngine/src/Renderer/Buffer.bf new file mode 100644 index 0000000..bd9ae56 --- /dev/null +++ b/GlitchyEngine/src/Renderer/Buffer.bf @@ -0,0 +1,279 @@ +using System; + +namespace GlitchyEngine.Renderer +{ + /** + * Identifies expected resource use during rendering. + * The usage directly reflects whether a resource is accessible by the CPU and/or the graphics processing unit (GPU). + */ + public enum Usage + { + /** + * A resource that requires read and write access by the GPU. + * This is likely to be the most common usage choice. + */ + Default = 0, + /** + * A resource that can only be read by the GPU. It cannot be written by the GPU, and cannot be accessed at all by the CPU. + */ + Immutable = 1, + /** + * A resource that is accessible by both the GPU (read only) and the CPU (write only). + * A dynamic resource is a good choice for a resource that will be updated by the CPU at least once per frame. + * To update a dynamic resource, use a Map method. + */ + Dynamic = 2, + /** + * A resource that supports data transfer (copy) from the GPU to the CPU. + */ + Staging = 3 + } + + /** + * Defines how the CPU can access a resource. + */ + public enum CPUAccessFlags + { + /// The CPU has no access to the resource. + None = 0, + /// The CPU has read access to the resource. + Read = 1, + /// The CPU has write access to the resource. + Write = 2 + } + + /** + * Defines how to bind a buffer to the pipeline. + */ + public enum BufferBindFlags + { + /// No binding flags specified + None = 0, + /// The Buffer contains vertex data + Vertex = 1, + /// The Buffer contains index data + Index = 2, + // The Buffer contains constant data + Constant = 4, + // ShaderResource? + // UnorderedAccess? + } + + public enum BufferMiscFlags + { + None = 0, + //AllowRawView = 1, + //Structured = 2, + } + + typealias Format = DirectX.DXGI.Format; + + public struct BufferDescription + { + /** + * The size of the buffer in bytes. + */ + public uint32 Size; + + /** + * Identify how the buffer is expected to be read from and written to. Frequency of update is a key factor. + * The most common value is typically Default. + */ + public Usage Usage; + + public CPUAccessFlags CPUAccess; + + public BufferBindFlags BindFlags; + + public BufferMiscFlags MiscFlags; + + // Strucutred Byte stride. + + public this() => this = default; + + public this(uint32 size, BufferBindFlags bindFlags, Usage usage = .Default, CPUAccessFlags cpuAccess = .None, BufferMiscFlags miscFlags = .None) + { + Size = size; + BindFlags = bindFlags; + Usage = usage; + CPUAccess = cpuAccess; + MiscFlags = miscFlags; + } + } + + public enum MapType + { + case None; + case Read; + case Write; + case ReadWrite; + case WriteDiscard; + case WriteNoOverwrite; + + public bool CanWrite => this == Write || + this == ReadWrite || + this == WriteDiscard || + this == WriteNoOverwrite; + + public bool CanRead => this == Read || + this == ReadWrite; + } + + /// Represents a buffer containing binary data on the GPU. + public class Buffer + { + internal GraphicsContext _context; + + protected BufferDescription _description; + + public GraphicsContext Context => _context; + + public BufferDescription Description => _description; + + protected this(GraphicsContext context) + { + _context = context; + } + + /** + * Creates a new instance of a Buffer. + * @param description The buffer description. + */ + public this(GraphicsContext context, BufferDescription description) : this(context) + { + _description = description; + } + + /** + * @param data The span containing the data that will be copied into the buffer. + * @param destinationByteOffset The offset in bytes form the start of the destination buffer. + * @param mapType Only relevant for dynamic buffers... + */ + public Result SetData(Span data, uint32 destinationByteOffset = 0, MapType mapType = .Write) where T : struct + { + return PlatformSetData(data.Ptr, (uint32)(data.Length * sizeof(T)), destinationByteOffset, mapType); + } + + /** + * @param data The span containing the data that will be copied into the buffer. + * @param destinationByteOffset The offset in bytes form the start of the destination buffer. + * @param mapType Only relevant for dynamic buffers... + */ + public Result SetData(T* data, uint32 elementCount, uint32 destinationByteOffset = 0, MapType mapType = .Write) where T : struct + { + return PlatformSetData(data, elementCount * (uint32)sizeof(T), destinationByteOffset, mapType); + } + + public Result SetData(T[CLength] data, uint32 destinationByteOffset = 0, MapType mapType = .Write) where T : struct where CLength : const int + { + var data; + return PlatformSetData(&data, (uint32)sizeof(T[CLength]), destinationByteOffset, mapType); + } + + /** + * // Todo: as soon as Beef supports generic method override, change to generic? + * Platform specific implementation of SetData. + * @param data The pointer to the source data that will be copied to the buffer. + * @param data The number of bytes that will be copied. + * @param dstByteOffset The offset from the start of the target buffer. + */ + protected extern Result PlatformSetData(void* data, uint32 byteLength, uint32 dstByteOffset, MapType mapType); + } + + /** + * Type of data contained in an input slot. + */ + public enum InputClassification + { + /** + * Input data is per-vertex data. + */ + PerVertexData = 0, + /** + * Input data is per-instance data. + */ + PerInstanceData = 1 + } + + public struct VertexElement + { + /** + * The semantic associated with this element in a shader input-signature. + */ + public String SemanticName; + /** + * The semantic index for the element. + * A semantic index modifies a semantic, with an integer index number. + * A semantic index is only needed in a case where there is more than one element with the same semantic. + * For example, a 4x4 matrix would have four components each with the semantic name "matrix", + * however each of the four component would have different semantic indices (0, 1, 2, and 3). + */ + public uint32 SemanticIndex; + /** + * The data type of the element data. + */ + public Format Format; + /** + * An integer value that identifies the input-assembler (see input slot). Valid values are between 0 and 15. + */ + public uint32 InputSlot; + /** + * Optional. Offset (in bytes) from the start of the vertex. Use AppendAligned for convenience to define the current element directly after the previous one, including any packing if necessary. + */ + public uint32 AlignedByteOffset; + /** + * Identifies the input data class for a single input slot. + */ + public InputClassification InputSlotClass; + /** + * The number of instances to draw using the same per-instance data before advancing in the buffer by one element. + * This value must be 0 for an element that contains per-vertex data (the slot class is set to PerVertexData). + */ + public uint32 InstanceDataStepRate; + + public this() => this = default; + + public this(String semanticName, uint32 semanticIndex, Format format, uint32 inputSlot, uint32 offset = (.)-1, InputClassification slotClass = .PerVertexData, uint32 instanceStepRate = 0) + { + SemanticName = semanticName; + SemanticIndex = semanticIndex; + Format = format; + InputSlot = inputSlot; + AlignedByteOffset = offset; + InputSlotClass = slotClass; + InstanceDataStepRate = instanceStepRate; + } + + /** + * Use AppendAligned for convenience to define the current element directly after the previous one, including any packing if necessary. + */ + public static readonly uint32 AppendAligned = 0xffffffff; + } + + public abstract class VertexLayout + { + private GraphicsContext _context; + + private VertexElement[] _elements ~ delete _; + + public GraphicsContext Context => _context; + + public VertexElement[] Elements => _elements; + + /// Takes ownership of ownElements! + public this(GraphicsContext context, VertexElement[] ownElements) + { + _context = context; + _elements = ownElements; + + //CreateNativeLayout(); + } + + private extern void CreateNativeLayout(); + } + + public interface IVertexData + { + static VertexLayout VertexLayout {get;} + } +} diff --git a/GlitchyEngine/src/Renderer/GraphicsContext.bf b/GlitchyEngine/src/Renderer/GraphicsContext.bf new file mode 100644 index 0000000..3edbcb5 --- /dev/null +++ b/GlitchyEngine/src/Renderer/GraphicsContext.bf @@ -0,0 +1,80 @@ +using GlitchyEngine.Math; + +namespace GlitchyEngine.Renderer +{ + public class RenderTarget; + + //public abstract class GraphicsContext + public class GraphicsContext + { + //public abstract SwapChain SwapChain {get;} + public extern SwapChain SwapChain {get;} + + /** + * The maximum number of simultaneous rendertargets supported. + */ + //public static extern uint32 MaxRenderTargetCount(); + + //public abstract void Init(); + public extern void Init(); + + /** + * Sets a rendertarget. + * @param renderTarget The render target. + * @param slot The slot to which the rendertarget will be bound. + * @remarks When @RenderTarget is null the backbuffer will be bound. + * @BindRenderTargets has to be called in order to bind the rendertargets. + */ + //public abstract void SetRenderTarget(RenderTarget renderTarget, int slot = 0); + public extern void SetRenderTarget(RenderTarget renderTarget, int slot = 0); + + /** + * Binds all. + * @param renderTarget The render target. + * @param slot The slot to which the rendertarget will be bound. + * @remarks When @RenderTarget is null the backbuffer will be bound. + */ + //public abstract void BindRenderTargets(); // Todo: maybe do this automatically when a drawcall is issued + public extern void BindRenderTargets(); // Todo: maybe do this automatically when a drawcall is issued + + /** + * Sets all elements in the given render target to one value. + * @param renderTarget The render target. + * @param color The color to clear the render target with. + * @remarks When @renderTarget is null the backbuffer will be cleared. + */ + //public abstract void ClearRenderTarget(RenderTarget renderTarget, ColorRGBA color); + public extern void ClearRenderTarget(RenderTarget renderTarget, ColorRGBA color); + + //public abstract void SetViewport(Viewport viewport); + + /** + * Binds a VertexBuffer to the pipeline. + * @param buffer The vertex buffer to bind. + * Note: the buffer has to have BindFlags.Vertex! + * @param slot The slot the vertex buffer will be bound to. + * @param offset The offset in bytes from the start of the buffer. + * @param stride The stride of the + */ + public extern void SetVertexBuffer(uint32 slot, Buffer buffer, uint32 stride, uint32 offset = 0); + + /** + * Binds a VertexBuffer to the pipeline. + */ + public void SetVertexBuffer(uint32 slot, VertexBufferBinding binding) + { + SetVertexBuffer(slot, binding.Buffer, binding.Stride, binding.Offset); + } + + public extern void Draw(uint32 vertexCount, uint32 startVertexIndex = 0); + + public extern void DrawIndexed(uint32 indexCount, uint32 startIndexLocation = 0, int32 vertexOffset = 0); + + public void SetIndexBuffer(IndexBuffer indexBuffer, uint32 byteOffset = 0) + { + SetIndexBuffer(indexBuffer, indexBuffer.Format, byteOffset); + } + + public extern void SetIndexBuffer(Buffer buffer, IndexFormat indexFormat = .Index16Bit, uint32 byteOffset = 0); + } +} diff --git a/GlitchyEngine/src/Renderer/IndexBuffer.bf b/GlitchyEngine/src/Renderer/IndexBuffer.bf new file mode 100644 index 0000000..3d0a911 --- /dev/null +++ b/GlitchyEngine/src/Renderer/IndexBuffer.bf @@ -0,0 +1,32 @@ +namespace GlitchyEngine.Renderer +{ + public enum IndexFormat + { + Index16Bit, + Index32Bit, + } + + public class IndexBuffer : Buffer + { + private uint32 _indexCount; + private IndexFormat _format; + + public uint32 IndexCount => _indexCount; + public IndexFormat Format => _format; + + public this(GraphicsContext context, uint32 indexCount, Usage usage = .Default, CPUAccessFlags cpuAccess = .None, IndexFormat indexFormat = .Index16Bit) : base(context) + { + _indexCount = indexCount; + _format = indexFormat; + + _description = .() + { + Size = (_format == .Index32Bit ? 4 : 2) * _indexCount, + Usage = usage, + CPUAccess = cpuAccess, + BindFlags = .Index, + MiscFlags = .None + }; + } + } +} diff --git a/GlitchyEngine/src/Renderer/SwapChain.bf b/GlitchyEngine/src/Renderer/SwapChain.bf new file mode 100644 index 0000000..5790c5f --- /dev/null +++ b/GlitchyEngine/src/Renderer/SwapChain.bf @@ -0,0 +1,30 @@ +namespace GlitchyEngine.Renderer +{ + public class SwapChain + { + public extern GraphicsContext Context {get;} + + /** + * Gets or Sets the backbuffers width. + * @remarks @ApplyChanges() needs to be called in order to apply the ganges. + */ + public extern uint32 Width {get; set;} + /** + * Gets or Sets the backbuffers height. + * @remarks @ApplyChanges() needs to be called in order to apply the ganges. + */ + public extern uint32 Height {get; set;} + + public extern void Init(); + + /** + * Applies all changes to the swapchain. + */ + public extern void ApplyChanges(); + + /** + * Swaps the front- and backbuffer. + */ + public extern void Present(); + } +} diff --git a/GlitchyEngine/src/Renderer/VertexBuffer.bf b/GlitchyEngine/src/Renderer/VertexBuffer.bf new file mode 100644 index 0000000..1e85687 --- /dev/null +++ b/GlitchyEngine/src/Renderer/VertexBuffer.bf @@ -0,0 +1,45 @@ +using System; +namespace GlitchyEngine.Renderer +{ + public struct VertexBufferBinding + { + public Buffer Buffer; + public uint32 Stride; + public uint32 Offset; + + public this(Buffer buffer, uint32 stride, uint32 offset = 0) + { + Buffer = buffer; + Stride = stride; + Offset = offset; + } + } + + public class VertexBuffer : Buffer where T: struct, IVertexData + { + private VertexBufferBinding _defaultBinding; + + public VertexBufferBinding Binding => _defaultBinding; + + public this(GraphicsContext context, uint32 vertexCount, Usage usage = .Default, CPUAccessFlags cpuAccess = .None) : base(context) + { + _description = .(){ + Size = ((uint32)sizeof(T) * vertexCount), + Usage = usage, + CPUAccess = cpuAccess, + BindFlags = .Vertex, + MiscFlags = .None + }; + + _defaultBinding = .(this, (.)sizeof(T), 0); + } + + public Result SetData(Span data, uint32 destinationVertexOffset = 0, MapType mapType = .Write) + { + return SetData(data, destinationVertexOffset * (uint32)sizeof(T)); + } + + [Inline] + public static implicit operator VertexBufferBinding(Self buffer) => buffer._defaultBinding; + } +} diff --git a/GlitchyEngine/src/Renderer/Viewport.bf b/GlitchyEngine/src/Renderer/Viewport.bf new file mode 100644 index 0000000..ae1b7b3 --- /dev/null +++ b/GlitchyEngine/src/Renderer/Viewport.bf @@ -0,0 +1,30 @@ +namespace GlitchyEngine.Renderer +{ + public struct Viewport + { + /// X position of the left hand side of the viewport. + public float Left; + /// Y position of the top of the viewport. Ranges between BoundsMin and BoundsMax. + public float Top; + /// Width of the viewport. + public float Width; + /// Height of the viewport. + public float Height; + /// Minimum depth of the viewport. Ranges between 0 and 1. + public float MinDepth; + /// Maximum depth of the viewport. Ranges between 0 and 1. + public float MaxDepth; + + public this() => this = default; + + public this(float left, float top, float width, float height, float minDepth = 0.0f, float maxDepth = 1.0f) + { + Left = left; + Top = top; + Width = width; + Height = height; + MinDepth = minDepth; + MaxDepth = maxDepth; + } + } +} diff --git a/GlitchyEngine/src/Window.bf b/GlitchyEngine/src/Window.bf index 0a4c003..b8b24de 100644 --- a/GlitchyEngine/src/Window.bf +++ b/GlitchyEngine/src/Window.bf @@ -1,6 +1,7 @@ using System; using GlitchyEngine.Events; using GlitchyEngine.Math; +using GlitchyEngine.Renderer; namespace GlitchyEngine { @@ -86,6 +87,11 @@ namespace GlitchyEngine * Gets or Sets whether or not the application uses VSync */ public extern bool IsVSync {get; set;} + + /** + * Gets the windows graphics context. + */ + public extern GraphicsContext Context {get;} /** * Gets a pointer to the platform specific window representation. diff --git a/Sandbox/content/basicShader.hlsl b/Sandbox/content/basicShader.hlsl new file mode 100644 index 0000000..72c8200 --- /dev/null +++ b/Sandbox/content/basicShader.hlsl @@ -0,0 +1,25 @@ +struct VS_IN +{ + float3 Position : POSITION; + float4 Color : COLOR; +}; + +struct PS_IN +{ + float4 Position : SV_POSITION; + float4 Color : COLOR; +}; + +PS_IN VS(VS_IN input) +{ + PS_IN output; + output.Position = float4(input.Position, 1); + output.Color = input.Color; + + return output; +} + +float4 PS(PS_IN input) : SV_TARGET +{ + return input.Color; +} \ No newline at end of file diff --git a/Sandbox/content/directx.ico b/Sandbox/content/directx.ico new file mode 100644 index 0000000000000000000000000000000000000000..ae04226090f111858bc4be1dcc062e6b582c0eeb GIT binary patch literal 118854 zcmeFa2Xs}{*2f!?0HH%Dfk5aWO#uNB0RI_ z@R$c4^T1;sc+3NjdEhY*Jm!JNJn)zY9`nFs9(c?Hk9puR4?O09$2{M%Jcv0?UeNQZU4LWp}Fs0JyXhe4@ebgfhpl1$QVxT;Lmm{wgvwb z2nY478Icl1^G$H$_fa(S(ux#-zhn#B zDfjI%-uH)_w@mONZ!^n zaWCCJ6@1|DMJi;1_T7DWmsF1gzaz&|_E9S44?pG)KY#k!i&V79pzl*b8-cW*N5e-u z{qEh?*iSkBC6mOLfjoCe{!j}omfu2)=C?ewdDK1(9e)V=o4*|^&b|*%uq_lX=)n`T zB?>(nrHWV{p=4nT6-pOlUnpvMN*A@<97~%stECCf3yqCKNzhwp49zt*yOPf6l@5OVMD$G0Zo~3wCA4;-aQt!vR{1ZIy{I`C2g7ej9 z#R~j|qHO!{x8o1>zmFcGT@epN)6hcsEhH?|0)ixm7=!Rrx-3~NTb>Zh0bPbJpKZgo zEwh0Ui>=@0h1Pe|0-*LGZ00!Vw?$Z(3>|wUIIpj+Cu~{tPw2z3-dv~8)z?+p5FrB8XMzO;SxuJlDR*zcPXn>k0=wAgxYq;I_FOY1{F?{+_E(}#XfzE+Cs zy8h<8eq2ZWIC$%F8?tPs1sBa{nX+fIH0je>nhfbIeXgt)R3x|cSUbnQT{_l2m^akw z%o<>|g_#5Y4tKmizk!+k?adkeJeb+fb4czT zwJC91t=@v4?fv;f?Y#xO!~8)GZ_oF<*V}UjdHQD-{o_S#+S>GiYw3fxT)%tzxAqKL zFW#c{q0YWZuWNqA^}qC@Uibw2 zeDyeo&(@5yw-yYwTGM*l8}L_c@z{j!Hu~@;%T>LcY03KUvg5tCq8gha&9bdBdTn#!o+c zeO6z4fB8uJG@N%^Gv2-oSD#I=uhxxE#@Fj6nBX}!!MmQ;cMwCElggRI${G4}1M@z!G7T!-cnv#r_I zS^o@pM0_IrxOrwWe8>LN_DBBy2Oqf4_gt&d`YHAeeN-F1tuU~eRp`^yD)s%|p6lPt zdT(1~8OlJLVquo4Kpx9nFpp&{i|h*{PWdNzOu>dfqG&87`EzYN%5qB%N%bof;0nW@qSSi>np?ftn! zttr>*uxpWZ0zd6sXdQPf_!~NGUts@R=*YE%pSVXS?$s`0u6@5@ntic!v^_tfomJ@d zoju*XkyYvUqqPg4W@(B-&jO*AF)X)b$QNSieaM^3GKA)`OzdaQ8)BIRwljhZc{%sL zf&S(pr006+xQFhSvQ5jepnM>Ihy@hQV_jFyv={oevP#{X*fYJqx0(~W*=H-p!rL>f zgZdJ5;9b>!9k;Xp5IV5`pMh&^TVU-$%JVz!5WPIt?W8{4z0lgy)>}E&UY*#@zMnMM z{L(@10FRH}XBYSdrm}A)4zf3<_p>io{%T$Kpu==%&|&>v*Vj|}i4Gi~NDWI_H%L%%&5{GI+5=z2oNFwZ?QCf^G_;C>Dq z3n~<1sfy;dc8e$5O9R^3v%Q;J^&xHT{RP92X;ZDs9&}myvVYV{>+AY$l?~kM!GOK= zgBShii~k6m-w*WNU?CU9m?A57#?44Nyt=+bHHt6758+>TJ4T@W5gJRd(z}Rpb5EJg&fo|V> z?7$l95A>T4Zr>gA93QB^bH97f<+p(cR@=bnzc7engE^;n^h#^FeV)}@J>Fgz)6t&o z`-43{u!a4wbev^>p{(VuR@QP=eTuf6<*f3g-hK$# z*?%O^EwXbRAsA#!fviuW1C+ytN(^s?IMP))`SFu(Mk@h`tm`b-%ub4U&gDwfac&lzbijqhevhqboP z7mu<|@J`Po>#h6YaO-w>jdhA$Wu0PHT9<>XtV`T#*Zvf<+}iJ7V(s=VwhmEClhJPf zVs}i(+wNWB&}PqKYqNW?wcfSRq1Da>)^f)@2anD%$LSs4qYFqktg~>qJ^xE5E8F=S zE7zsIeYx;w>xwPa6?>=i=K0nMbOC*^S6}_S|s0ox6I*y!1AfZiwF?>pxH2Au!(x{3Dgk`eae zq#o#iO{{d6Z|%KVgRI%cnbu;%3~Rm~-DblqYXt`EU2V0xHnk!jzib8Gd%+64Q{4)@ z{k#>d^Sl+R^MVzwQzIGL|Ce}+^9A>J)veIm)vfS5&nKe@*Dm^TEvw$XzIEBUz*=lX z|K)kU-!Pr$p6Z_az1ah;e2<3q+@R+6`s7|#7a8`ge%m!8i4xjKN|YY^ZPga?N^-l<)*1tZ_70Mnzk7LyHD`dmIK8LU z#*Y1T#TaMLeYbhKH9@awDs1tf3GF|D-*ukvTS1VfquBA2%i@a?pZVBDgww%$71 zTJlV?mFl6lzQD7-HnE3&&h~eDZ^nO9;Om3rcGE15J>t0j=6`@}bRWJ&R(`x}jJ=NE zV&^6~-TXeHFl69aRXphY6;Dz>mXv;C7 zoxtA+?RWgO?K_s->e?yLj&s|f12jc;DPZv+adP{pOC*zx0C-t}nfP>gy{VQ$M!`qDiZWdDavC@0a7-t=_z$$f0J~A>V-} zR%>WGYdXHa`DG+7hwZ1BlfR4I5n~ASvu`I2v|8i4I=WXG(%h$z*0L+{4W9gOYI&{>)Bpmy}bLq_ww7{u#Ej>{I1sf8{55hb4~T< zt|iukR=;(NTx=~F>ur#;lh5z9pJO-JXfH+`*kI#gHrd=`yX?)MnjrJ5Ti(|yL!)Ob zu-jV)N>p)-ifq(vj_qgx(zqP*e zeCK%fdvlKZSr9FHc9VVgAGAP^+CF-^wxns zJvM5M4T=hPzYj*Im5w`DdOa9&faB=*gVln;(K_zgcKhD*+%c{@JZ3$=ueBbsiMZ~p z3(+={{+$Eno}xXu%NCs6ZR-*b+NbE;CF{On1wN=@Va(HE?+DLZ*jv>s?9HmkhN_nL z^(vO{_2-;Y`AaO}LbHk7^^N?#3s7`)doj`=W@e)>CzAw=`L z0AD$1W-K^Wn!|jz!prLmTYFsr~GgU%FVWQ9s!$qdMCslLuIb zsXsgVr-J^n{iq4x_(1&7W%?LvK5vXQm^ReDnLNn8oif-O%^YsO9*wYhm*Z^8#RE1e zDcZ&-McFto_ToMpd2W}DI2&oh&+PQz^bQ+-db0hyYvvhC#M zU_K0BdjPg(--FnH*o8tLY{Q=TAb#4n%)0GgVKXkr+JuvlHVI5V5oy!U?6Dc}z}l$V9 zUs&ai-`KY^hgh?vzgi2f*Bak>&-HU{{!!gC-i`HXXLsA=(~&miRHV&13r}2%#nxYL zUG}fC?%4aiu=V=D*Frz+>3-PK{eiF5I}bj(*@hn5`cD|fv0=v}9EQ`EBTsF&QS`F| zeLevhQb)u!f@Jul=mSghAGF)>!L3WsDo^zVR>H zUpWe?(-Q~Cmfq6j%xGzIWwNv(nJsN-W_(*&?3GRptm)dx&R&tP@dtFY??Dsvzowg~ zS!2*}Gde4Jr$>K9mq-6^j857J-Lx@~Uhja8-)zO& z`RN<03@Sm>N*%uSwA$7&-|_bP4O_G0*H)>=clN@dmez9P4Cni7vvrPjU~cWZbE!>@ z+iVj+$4zsr6~F(ueyaVjZVEQZG;6dHUp)49BlP{o-gj<7D>Ox4Z%Q9Ep>2#EpuR~4 z?+7`Z}ufIdbZ%W_)fUf;L`%So4lZ~Rq3}-idK6jYC(xHK+DF6-g zWU`~-mYt%3Bj&bt;{6W;xsHHtw%C!Xbde9+Q`eSYj#=*O+_L#IT` ztuw8q^yYs7#U6Y}jx)e>ExC3pbZwz6e(jE1=GdsX4L0V`MyHFl#l~*Uy*uM)?SuYM zvww4|ggyRryLwikZ9Oa3`b)<4SN3%4diHdidRD$oT`LF5YrhrX_b2r`a-$mW-FD+_ z>x?eehWp4j-3Ps7>X9ur5gFS7UTeYg>pf+c$R96Tuo>Gwa=aPPaoGk!3v7TEJe%Nq zw-(rGZd=Dv9M`cHpru;=?g!ag@Qud8H|XTe=;v0G)T|Vq$Wet!Ly?YrSEnv&*E9cHBwa03Wpa z&4KMM_J{wpi{ z)3^3C{+lWTTUsY<%zoGy-L@~b?&$eL(5>ekkF=T0JzcrKeC-~aNBaFDo{zrSxx|ab zyzgH~`JU<{AM`Hm9|89}{iENVt%ZKy78%rb(`@UH{x=pKzYEvtg72u~uKD&u#5DVG z@i6-kTj}NQ-&>le3Rxy-n5B3D%UU$dvKGoqj3lQ8krR_TT@W&WoJHh7>L7CLGG?&U z4D%y#eDk@xJiW2sGN9-c_w4@U+| zhZ!vWZy!EguWviK#qU4RivhIq^AC9VoRs@6CXnK9IKMx-w9ntIwja9g&|NES;iTiJGAK4AQplT2t^*UEN) zC%SxNRr)r?PuAYvp4iL2m@~|NC7wSiW{ZuAT5mstsfV^&__+hNI3b$%AvS`J-`DBu z(#sXkK=<2=j~fgG>Wh>Zybqlh4CHu!FZ%J`{q#P9wsl=FK>mBsm+e0AowkRdPme(F zn;f^rrl9+Dg6BFirdl)pzaqx`CVrZ4mi%I`V-Kau2Mu!vTe_UtEFCmW4{d|+^~mR^ zyjkU!1!@jZ?jgPae|RDlzX_=`q_;qL!ar*|tK9fw`;xfurz^(Umun_558?B|w*4t$ zflWTX!zLfwVUyyw*_7kk;DreKY_t7(a0}RqeMIZ_H`zGmXdixM``6gbHZFFHjpw{% z#6`Fm`sBk~ZSs)_n|%217!N%r9Ny;sPJcTY+N?fvz}6%jL7TK@@2}((jA45c$0r@#K_70h>6|z5 zSR{V1xo+PHtRC|f9;Sh zyn4vl3UiYV+nh^rHY+jKW?hW2If*eg7c4+Op2z;|geaSRPKf>|%;nfzes|!Uxn9gu zdzO2h-fxq!M<+t(>Dd0^7Y|!_(otJ_;h-%#e}Fc|HeWhw*X)5M{o|g+Ke%C$*Ar~n z*@HH0|5~fRWUSR1*VUdM)xoOat9yBDXZw1=C^xniosF@boEvxls-5`rrX_NX9T!j7 z%7i#u29K@bUYoBRxA4pH&|;^JhaQvRxmnnObI$Cyc|7}ko?(812aC?f@Jt>oy~s7Z zSbFJ@E%9R6B;rPqL@y+9F{u2DOVUB(N(+|XX(D9Uis(f4NdtSJFQkOqU?9Uqm^b z;tSv$#{e&mSwWlUo)H?2#&g*Y>Le66^4%T05w)#iEj6TU0iw3Ixp#Urf2>PhzVfsMBM{2|+ybcXXUS^U+Dc7(q>_s1QZAG6a& z@8#L|gxjcn>+F~P%mw?`*#Pd{Z_jFHmyhLr$8%j_0&>fT$;^$?O;SR~CdY4gax=L_ zhE6`dlQF%+Ch*(Oyt{lXL$T+FqT3Eo91vTvd(3KdJ@g-Jo!-nN9q~c8MjvR7{w1_U z9t_&H%)aj0k~|M+N?g}3h&C-qEgH+$1C$R8?SqgF8JYJp=gwilg~Kf86XdRzE^fKY z6t_HOOIf~3Pgz(E{9yP;3f8G^`Q=yn;6*F=>8n=ktGDdaUwgw3=)0R{lFKmPx}zV< zAEmjk_4?WN;cR>f*w1Z|719$mhig6@ynl@igH9tfUkm6CKQph5!nYy6-e~9{zwTIM ztL6zG#^Hw?C*Hum9mX~Ka<5)oS8bojW!4?ts|PmP&wJNe%-uxWditPkJRNJB)Sim9 z9Z4r`UD7e;gT+>N)mZzSyolED^71nWZOesYw(;yC+kED*Z8^<2KXb?q-oI?)_N=#l z+nL`ZiK}alrCg%#u=pXB;bd}0IH9>AC!%^2Nt^PH_eb-=>G+S4)c z!9k0?e!-6Ye$&P45JRhw z8$r%$2syDi%N4gAJ4hWVFpnR5G=!2Qi@1TDG^MiapvH{cO3bypffehp{WhTE$ zd30H^2eTH*XW7Up4=zPM1UVMLPZhD86`pW87@rOL2^(ywHOCJkyG}Z>#^^%cYh_{_ zbl&K67ma7>Zd2h4(Q_7kHamWo&GxqQj_tOE$M@J`e5i|$?*sd7$%!aiax&VMojhR6 zf!ZY}b&TIm?YG6~do$pXiP8h(7*k-Pbk68aHaU8W?YVZ=js5kE?Tu&RZ2Q^6cH-t` zyK2AN8m`-8Gx7>7Ji5nj*dKP`&JEj@c-(aUTA+5{%>5^j@Cz1zC6Jqgo@3qm)qkWJA zEs?WLn7cY7|34o1ljVH2jH5@klKII`g+}C}W`$On$wyS4l*?HpPbnib%?NGNV+RC5 zYmNO>%m;4G2|AzFrvzp~CMYgUZh&mWbl3~db`ZZ{3#JdvPF`G2avzZ)%EN;n>h^7K zL-&wlv~{lYj}65>nTcMsmiOCu;jqo)eP%+VX^d(0*Zkwq_k?J>A6@!@t>oR8LsQp( zY_E1uAH|{9d$8uzLB{YwjzO1maklPkoUI3HSEJMET1&Zx2hld?&@P*Qc(?7nde$P& z9kH#9>FpPeTU6pnOSpZ-?xNG3zkA(QFlJXCjU4=gzqZL80r6Y{R&l?^=4MzHDJA=~&b;C@RuY#Yvd zu#s(@yPo?kqd#UJ*=;ipA>SAybC7-O>BCj%oth&CtDiT|VUC|;19z>mFZy)w_-RDv ze2iP@lO8&WW4ZgHJ7b%% zmDZy(ucDuppN&P2K7{SWvBY?Oi?^+6FC4WkwA(Hov+ZD8B7k1o5>Gg6r}e>i&JLY> zAs$;S-ZsD!8|cGL^xe!!w`CfcscXB_rG*SKrv?aFVrq5hwCpYeW^e{X~P z$b;WGcKN{_i@SLNzB!YO!*?&)dgRVJT|enDy<#@RDXreFc6=m!ByR~G{ zUojx9FB1;{twVF`88tV^Kgc!w$qf(4gx?sM5C~7DMjoWiL(UjEkB$0wvPH<_2~O|X zY_ph`*JB&U-??PRf4gFl*G}2it0!z7_ShO|v*sf7x_rX6Tsv*s;g4O{&)VJ_=NzJM zp0{Y^ZS;-vcHqW^WE{MaXtB`p0Ilcvd5htk80@s@Yv*jw<yzTV%@it`mM#g;v z?Iy%(;j^9kOBzWNRy&HD*?hU*C+bz3#|CU|*?H+RAy1R~KOt@Fhh#xmW z>#gE9=(huUZ-q{~pj|Zc%K`51a9z)HAsNy$VtI#H-YFKII*1&J1Bcj-yLr)KZ(Xtj zw=Y?=a3{%ja-DVX&$^38U0-d$o{hj(If@+`bNd40C&p%A8;oUcl};>KRKIs8Q~q(< z+}SKJJ2^aA)0;nZlK;jZ+s!{cw3eJFx7+Fd$!jv9vy1)ukpmXUI-nr*LFRwfuw~4Z z-LjG^omDxrxqUXUqkTA}qkTHM z8}Y&s)@|)#>alf=z*)} zEaocqw-<-5o_BWGVcBpgAloed`XxJh{h}SBuXjLO(Rb&0#-QZ=`FLA;1pSJ+U``zR zW6U;Na4-^?ehfKq!H!%>u%qA@bc;tW9Oqj5l1|%Vq!HunmkC$=B9PC)gL`$ju$x!RqvBV;}ZzYtMcCk>y4&4|%$z z<$yNIWz8lXUwR%oTb9E4JnJ6Nk{<`t#&C+!jpeIV#+J$W%OJ5#g zY#d{3oV@uLPT#y@KAgF6)qFU6>#7N-Z(p?om(N+`g%h^>;z`?szh)2ov{t$SdZe4P z@dGY~mf~E?A}MK`D6)vtFbi?EIPK&JzHdw=eoNtokBl6We4yP z9J_JRP5`w>K|HpD4@cQP0*@UAaq!n6id6^vuG+a)v3 z#oHcuNA%sm?`yqSPbg8mDUg-g(hw_iJHOBoBxuxlhQCt?#%mi~V_+CCoK(Mo(> z#|n|#oWBk^EyQF)$xq8e9(-=rDCQD9p2WsOAJ1Mw_Io}HE-ssmSe|IEH4CD92yvb4 z#DSgd&w6#u2iO4qY0w8851=ow7BDTcAU*SZI&?kh{rcMstZ{McsaQLjnf2UR%OGpY z!wZ$*v#R(RUVPfZUL#M69IT?Bzi#jLYGcdJVY|T3bI|`+pesv9+kEc4)2E# zG>=4~`$-?$25;>W9l>5`9Lc!fc>(@7kDewOz?hD{bk?Gg+dIx5x1BtPTK%>!>8$PN zwuXM6 z`(Q7wM7InF2bkkGBCpn=cPv8oNajz+o>jlq?$p!@;&%&WtZB_du4l@y-l4b!@uOFX9$rU;I#qJ-_vA9hg04sx`Aq{Xx*3mfyq38FF^6& zG(Nqbze$Vj5Tv*fHbn;Jhm7!n*1TnA4PF-bP3s}DS74r~!W!Hf<>7&6tth$kZ+B^i zzIWL1_B`gX)zEtf{lA~SJ$5_EwlTIg!w<5TwwyiU=ItoP*ipvGY3B3<`s_To054pE z_JZSs+t=;lqrmTHu1f5!b2QC#P(ru1RQ5PO9myvgNbUrI1k;=-+~Ws-{6_Ad%%9eEw*o8 zbG&l;8rR3CEIyOWS&x0Yk@wz2KSbbXJaRh`8FRuS&@GoSXNmUm3$4dKe7$u8@-WD0 ze2(=d<)5(ZWr|o9#$cu*#B&P}%g@XBL%UBLXuKTC==z^lH z{5{J3P|VNee<=@8xnN#C2tXF3;T+kB0n8E7{{y*>a>UarhDbco`2pkuC?4k4#^fkp z%yKawXkCtYw8+~v?cL7JEaK`(+k!s33Oz@1_aHv_bM(i>yEp9^`kVBpX!N0I(FR(@ zLz^?uKx6;Pz1w!xi)*z16RzLCQ9rd`EmxW+Yvt3c0s z<=$Pk@6qxc4tHI9_1-O7ctiIFng{g0k|_t^se_l%J?WRE`!|q#Nz4&v@KqgjJRtog z5<8=ID{|XkddBiR%bKII#gOyla4`NP+cPlcWsj#rwg<`nBab^ZF`LxsWxuHo2{Amy z^c33>WaDA)`D6FFIxwO?@cufK3p9-5-!O1GWXVF3<~t82@g)TOrm{ zLi^~Z02!%esUz;6HH z!439r{H}8z*mbrq|8dU}pxGJjeMWuEJB;98j&8ON z86ls~sw4Yt)2Txi&H2Zf3(hcBPoYmqc4+Rs05ry(Y}GMvfpMDX#wqk=?!CaafKj!c)2LNN&Wd{^PmTnn~iOUA?RH~+-{{QF&7cRtR>5Lf84mHhFI zassVNZVZj_c!M@4RS5YqKV)7J5H<>|x~Tg;v4mUzzb&ff^UoM)25z`026P zvqD3~buyFlr5I28VEjb#4Ke?_{6E^%LHPMHWMJMv_s>BtAnT}uvHgP!i~a>HgxWiy zWuZU$WBEjXY{2}Y|EtxkaP1eY_1gYW&jI7l7ts>0#=3LC4f4Fy6!ujvmneG%+JU(b339#Wh^I*8)Q>v^BG@IVwk9=-Dl{Kt!U z@5Mm5B{7_H;rDyC3VGj+SY*9fL+s6wKiSLuTG|U;np)*nU)fXGePzCQleLPhYsBs@ z_S*ASTJG|t@Y4|UDOtp_Gj>J8tn%Z?{wKbZ4&7cgUtDc6 z=MVDc|2m&9^JDjvyzk1suTU`FYb%=KB&HG-cgzlU@*WX-7kr{ykaP0;i2m|TDE1?pFbdyb+wd7yXF^YEUHnMj zePvJLPbvM~M^^mH+E$1$R`{(KEI(`1!nB@AV;*|v!Z+i5a@5Vr#(2-dc+W^3uMEg= z)kDZY%qJalcRG!G^m+M%Q!@|v^gro<{_ugTQn?yJBC?7~QF^5e{plKZl|cd##C%h`L#c4&8=`BSp?vgiYy zE@Ag2N&gZ(q-S~C%jkZ|+Z=c2L!Ybgjp(a+Lb{yhiA3at&b^ABq2I65UeiA332s~a zx^_zFy4)uTTxRLw^hm|WuQ$bvl2zytV#!fLTz@-5cS;-@HF?{zEL z=v^z<;uCwC^~BFm!{OyIKiQY_M%Y4fC*z2#L_u5G+1nHF#hu6hhs~w=_84+nw%}fk z3+%K**!1$Vo#gmQ<^jp}%aXUqgA2%pq&v_c88_?_x_T0}SQ2#h;R-4-V&XGqc``~^)JFdf_O=Oh~vyDg8YK<^7qKEfDa*7 zItKdwIdo9jqr2dRoeB6L&UyIa5c9t&UCHzh}1UhhpSOkWmwi*;?R zMa!dl2Ng?Mj;FBwk?+~450@GLyvBO^+~jm1--EJK*NYggayLD3pugyz3fs<4aX-a= zM0cPZAm;~A{6~C&t>ALTuoY8b2c*h`?noYCKsNjbx$pzP1FCnDz7X|pByXN%p26R! zdJK79L@vF-S}WxTLI2{7-a&>T1K{V1&|mp>pOI6$ko>&k5Ac`JA7`Pr;4zumM`$nKNaTPGjfv~wIw1k$VTpfBlM*#x(}xbx@lZu<`V_xSxTtq16w z{B}=3|CFwMKN)}8Z~yoQQ0v3*cAvi$eskx0{#Mt}J#-!2L-!YM0NDxWHLvh|3AAT< z?z708b3FS+WXhEX_uMmU&XNv$1|B_$FZ(!p!b#-Yf%{ijpVyVV)P`2J)n`_!@w-;6 z-Wyhknga#M@yPQMYgwzJ|Kr!qMy;t#)c?x>4T7jkk_I20^0ots^`<6vFP%SCI_QQC zulkdIX+&EuRQJV8e@A=S0>lB5lruk%$xA(Y?8m3=iYvncoDKdy!a>o|G}I7 z&b!`%2T$?$dx(Foym-Xkr}kU5VXdrk_wTGCYmv$~|H8^N{Lo6&eaniX_sOqP=(TE= zADvG+cnEWEj&k^eph;HhZDuCsla(Ak)kabtPkQNl@P%@|74u1x9zPE8|J2O$zWHBv zfOJ9G0O`>EG6rX}Y$3TUNB+E)6WuYF*ZvDFSIY8NE^h^@QXjGAvsUC)?7p|qL*J*q z#^-NZnXlir^4NZrntfqa+J9?Rso7MuAN7w$wzXHsb+%Wg^|H4X4Yhi#UFl4o*fest z4iNvn^AGjOU;o`_Y{er(w?lu?d=34$8i)rJ(^X!8rRdek*a0)#u*)IoOG#`sr zmzXC!bA#kQd~p|g-oKZO8~35@L%6PaAjN#|t|gl5dS1ODf$N^Z=dZcy6yx^*bCvj2 zF@;Uow0iFK&|Y!P_1Jh@cz)TLk{cc__xzg#|E)V4{&yu(;~4H~>m9$s~H@Mnq5Rm47j zy0qmkP0S9xAeZ8J1@l^V*7XGE#($q3e#neYCtV-9yS#sNKOYYS;RDd`8POdw!8@54 z`&-c`OW>-f5`mSq~bTJnb|_r^E=A0UHIVn08M{+k&4k>~__nNy>{ zNyc4*<`7!>4X-jzHEx}*2E8<9C8uxxhW`k<-}&Qr#y!5FM?n4~9l!YinZV!OMi#hn zFPXq~osNh+knJHG|D1G3WRB+gz0zOc$sIt?C|;F6Lvg2d#5OjweUNyu<{nXAa_#~D z?&KW?kyqG=HI47H?%-9%$n&bd!1{z|s2^6beFH1|V_hqa40z&;+QiphwW9C7WCdSG z2A~V(dA_{!3FoeeEru?h6FK14cR{aAqFX*{p~+{@`o4^k=b2gGqw%lt?zR85{=-4B zU?&Gy2g*6h5eo{YrmbuT?7(zk=okeHDm!`P?dXt55x=@2$p&cGMQ?YOl?p9^&HR)Rh=VF4Jss9wY4velJM@%>xgR ziKiX?Ju$L1^r3R)0t1@!>Y}PBNhREqK63|Lecqbz@)ifqehRFDJV3e_DCsJcFJk+QlAE zx-lQlGwZq4t~-4I`X6*QzHj{h4(-pP+x$WfM8j1R?d`dPsT0?Qanixnx~jl>+p?|d z+LKK`A@29C700($5FcBC+EraWf;{Njxhj`-Ihe}D%;IFf>Ku{_1f9|$`xUQIJlENA z`0vx86DZ~@+3)lHNdHS-{{bJkTIJ#a&XJEu@dN$6{Qg1c7|QvP4VaCXa1P?ZIawQ+ zgFMjO&mnhSu7v%EKk$Q^R{V?CtVI2{u>U@^a_9ihbYm?g^}Aj`|9g3IcYAa0K>KL< z&*b1wg8x?#7e84$s5y}_wH>*r80qR1d6L?fjE#o=NAVv@?u+(n<+qVvP(GlG zw6gOg2NV~$$=FW@vOx0SCiy-$q3fd{n=c7E>+hXyC%F$28S@E1euGoQb5274liXi% zix}vyoD+@r)zDiI53FZB$rfUIvHb3%|8LM=^H|3Xvt4}+&5bYo(vf;`t>~-9tX0L| z^W!Hj)?W@OoZy%~rBDK?W1zfEfF_5j6xu-_EF z3Bcx8Jjd0&_sV^r?l1bgSOD~w9_Y?-asZ#Od;!D&Jn>-U2Rx98^@G{qf$ZoS*{O+| zy=)Qc@ZuXJ_Lq+uIfdSSp1FXz=_~xfP2OX@<>ywpb3?1%uQ_#b+FQ+u-R#xb{q4P_ zBUrCC-nx;;x{KV4yNv!n>6ZtL*}u^rny&<&JlR<17ZUxIBcQqe46SlSE+C69L35w} zcU`(0xFvZGy&r{J510!c!cF!i>*dqA!Tc;fauQ5WhMtW3x1%i z#2~Ykq2BJ(*aO5SLy7-L1{B8rD^{0z0Q;{Tc0lEh-&&PkP0?*zyLy;!%pGK(P-C(Y z>&XU^tFPGKZ(jP}hyEwX^;7=sI&xxGAwO4=1F?pjp>>R>9gLee>}BO%oRzPZd0#eU z0{%hK#?c2_`E+;1W$t1N`r<>{zK^{p8&GpYN{BwUpt<{7=KSkGGC=#X@g)Ofqso6I z9q_cC0ozs2pjfl~SMu|&ay)?iNB7(4=zj!1b|SWY|c7@gwv z=26>7jSW;?AQ*uv$|NL z_$?E;-&u%1WhqzG#r|?)`-f0#DKEBvL7uk=KHw+n*Rj&ye`sY{TPu5}Dr>20;Qx7R zdOy~V&7oG(3L6u>+4a>;pX{e!M1vFfLUx=#>}2O^Xup~qsBq-yM&#%o@+YG46UALT z=i+LT`=Y^F{O)e7idN1BM7RG_aXF9fr*SSFP_qBF>_7H>^S$i8ThRKZY(4&7@?T?K zbXUAjdVuBu#}6EH@g?{{HivlNP!c(y__CBgEE;X zGv<;PHIQ{nU0EB|V);aSWn>4d+^dQ6nU?zDLo1FRRfu&~>eH~-t6*zm^Evrnik$zV z_+VMzO$;yt^L{$~a%q*9n}b|X{67KA_YTN#)p~aMztCCqmcKVy1~A_JgP0%C{nMcP z%kIxaJxHzpQ0-)&-#-MOQ(nb?v<`@x0)<{e*0av2=({!TiBG7fSg*E~Y4Dzv`~Fj_ z)CS$ZGxlF^>UIupWiO8DWUox_MP2tH))@cq=DWn&9$d%2gWn9F`+X$8#>8**i+nl9 z$P3uX{J#;Jho6pu_6Nv=U=0^}*V3^g6UZYuf66oGGxjwf%O|LO0r~knzCI8AZZOYZ zCpLc#+fVd&JiwgbbO3Zb(cMS;>(E^Kg4!Fj!ga2B6Pn9kC>!vqm;TcK9sS|I1Mp-d zxyH&B-iWN1e|L@Ie8_m&f1+NjgDcdx97)}{?)Jja z)>gUachn`JZbeJhr#Jb;6aOQ|R)YDUIDUYl#9a%MgIVB3;y+cX?@*}>u^-m^lu@o9 zbx`roX2TB1LM%XbTzc|)gRtvV_uZ}i_ss*Mzhs5%0_Y!bsAvLJM?0$^0HoT;0~1KX@)O7!Tv_?IaVvk3Y&MPIyOQ0podHS+klaHN$r3CbU;-go{ z`>g57`@{weBM&f)I9wiVz+B4BBrYSra8_)8t=-C;58Y6{VdO$kHgdtBaq4u$e|_{v z9%$W%WPo@;{1HUlK)QtH6|E`BjJ}t(F!5m46{+sM?vqFS_S{o$UQ%r|`KSvMrzkEy zsQ<1#^#gw2HuYTXmFHBy7eCM&`2XrJ8fELQ9e26$doQs5fw8=g@e_;uJVNf(IcOVy zg}fYmfKlYz?M1fk=J!4LPxd=Gdk+85C6`xm19^QJKfK1fbUn##L9`J+X!{DjKaGE% z9FXmI7dm+K0eA*}^XUL?KH%>}--mIVd)*PR8EzpL6cnsAWUb1zyJnq)uIKa($HtsBa-iA8@e%+5NlkMo3ays2u2T8JR(Hv zOjRopd7w3+#h9~7euMqj1p5y=wnArQK|k^VmE zfH`R=&%Ps(^(eFv=UBsZmUSZ_^4tlRpSpv&Ksg}m&SzAUD>v zmQVVi@;yX%&GYDazOnCUFZ!bw`r?017HB>oR_N@;OxS?rfN6b^M;?%08iqW;{>z39 zk{vxHCou_+EU0MtStni?eWf_DixR|upKA7rmB$xYi9CQ8v2nhF{;RH%^GN-KRjhYZ z4z$+AE+gkvxe#%;FS@wDazZ!aM^}!v@-?;2;t2T^%9Rn_MZ0tKy`VfFm+u4p70-7$ zA<)O=Zs<24x=BC0sJI=Fk4QPXn#bL7XsB~s?m!A!Uxd~^$iL*v7tpmud+~t$z_LLO zF}9WC7>7L?%UUJHH8-8ndw39suXDr6Sof~;-Fk>fTf!QHt@sSLT{%V0M<;6UHnOJ} zGo^^3NdGU2Ppt^PwS3IC%C`uq^d#}WCp_!^i1laSI~bWlsZS5x)8pSwOZ>;x@?ort z?kWg35Am0H)eXQ;WO?>D^0_-{OSq^-R|jO5Z0 zx7K$S>wji~c}J)BN6TK6{vjRC#dJBYxRG>zLFXk&esDeCwRL@Mi(lkt zR4du#X&#ss%9j$S&vC+>&(6 z>Wu1aReCmc^JbYw?^!AOusHb?`K9~gU&&s%3_15D(EE$B_A<=Uq4%eCdOz`gylmJ&-j8 zj$k3)>;tqp~!chzIT(&KCsPJks}ku`YElYQ7)Bo!Ib~u zqr1z)W=yKR03V3v=NZRZ58$J}ay=7>uXxinHn(IvcyCEu=L zrKz!B`m@*VDfIvH=>N}>ulC)v;kNy^MBBjH&lS|US)O#nHn0|S?=AY~w`;6vWvvbQ zkcxk;Vm(Ya+Z*r$?nF0=z*Z6t%D1b$FQ9x%<^Ekn@4DmzIU1J)_7kDM8;7n%7Pzr_ z%aiNNcvSv~)@LbZq#QuSkCmf$n(?o^46P+fKu=fxmdi6lC%=HqNCcj7FS+5lu8;rJ zIzFQnU%I$H`pXHP`!st039cK%J-6~+o5_EUP;NJC&DOJ~Z!K#h)-VRvv3_|6>y?hG zPso>Ed+nIFMi4Q?%g1c*{Y3kEbT2E9U0sIwTB(omtrG((UK>4&{x9;_ z8494f9@t?h1G3FuGd*F)*!!K39JN}C>R~6?Q4CNYrU|;^+!?u#=w+b76 z9&_CS);P%@z6*Ws41F5KSdJvGX9w60B3ZYw4;sZX9^;rxTk(;Cx<=q_zL>> z!??<^BxH6H`ndz!vMDcPn_VOqL%CSWDN@Wp>q<}JGuC<`t@k~}ea>Nvp2z-Dj(9w| z=x!YXf0Ial!X@%JlrMPscdm)ALf1@Uo=Coz+t$A4+>7LU$ksa#l=~q&HVzv$3jIC` zp4-E7MDp&t$$#C&TEJbj(Y*U{o*@FhSV4bj{ioLdFGW9$ym8t-8PJ}%8ooeu>-?%` zuR7+~{8|qXfBe^KxPP;3q`TW&|1v> z%mYuL|9{lCt*uTx*sbKdvX&NoSA86T&ZBt#IuK5Ld?Ps$+pq<-zAg?K zde}RL&O;N)T({N>Ih=Cr`L;DKHTHe-U;3Dn)6hWUP~%>6$8p+Y;1uKk7(5V-y{UDh z;)@H=Uvfa_YK?~YMR}ac>AL`2{wU9(drH>$?(O7`o=@lS_nKRjlj3p!X?3qdTz@Ze zZ8!JYicYBf0Ih}FfW5PU_g7!U-nwX72RRR2cpSBHRKI2_eWtbaTK^SRr#kvSHO=wA z<$Ssn-tcfP|6&Kq224%-z~zAwOHlquI{eGZ3C_TLk|`{gWyAj;Ox!Um-`WXA|5F@LzR5h~ zffj!I1*_fV2V2DY&tIcAQa5oeHGeix+hQkqzEP}wIM2JX{!eQF!D4jam8^%`O0MKy zY^NCPe9`0>dY|Z^eEJhWc>$-bEE4o@QT6^#4j(<=7S&ebYYUyhk75_nor^SckYJEtjlkFD- z%|%z}n_A;tJ|C|h=#Ty%p!Gk3Vm|2oYPBXf&}Rb@|MBF3a^0NR7v6ke7ketfw?y@Q zBw_)%&`)#AA4Qz{rMC5L7WFAdP>*Ra-)B*sCezxJoT)(C2+j;BV5YBZ@%V&Cn{sH@96f?uig zHV3^}^?(#V+m4Q~iyXh*U=P@T5#OH|8k>r>ctC9h#`OW9F(x`~XFPAk-j>fuwO|y3 zRgEFlYf){tEgav3%vGMA0)?{$yLl%T9dMwxk-6Fx~|rws6K`A zJ+$6j&$FNV3X&PRjt|OH)0)=^{Cu0?>($6Ot?iV*fcXY#U_w#}A>~cV9-CAGe0DH&3V!T1*hi71($c7vVW)8_wFrVd=?q39Z&((q` z%o-rpfR!z2dC4!+cW+f|pa4FJ@}ItG)f#DKtT##%RM2{%O^-lVFr}D|+pY4qG^~|xV;oxeBG4CkfUhx>6CtsrIrS-^BqA6or z^a^LK&XQx%)Rbb42J4d6p__`=)xoy@(Btq+aG{W4c?-j^xs{ z_|hsjC7+_vr&hK8r&i&k+E(J_>U`gt8VuxS7OVtK%9D#z7Tf$u^fK1%g;2*sIh;Ar z5wbzsOymY-K(|XvzNgCpMc$`@{>l^8Z&}dsvZ3>5=ltyOKrr;rR*brD-*^C)C!VasFgOSbXm zDO*h+ti5r{!s(M$SK@8uwRl^6<*?1X7-Q3^^E!{3xvB@MZ-uCT`%-^$AaY~!!R_|r zm_b(T<*Jsy3N_xLeP{*Ne?fcIZ46;;NG|B5bzs@h5mm=06Fvde^-h-+eK5$A1FX70 zfyjFK`c(r)^#YVP?DB`<2j%|BhLAr&IpE3#RcuM?z_L{=X>|v6;`>{xUF|o;_4VDG z-hA6;D!e?OoQ*Nm_?b+cd&cqIHWN%|zMoIcyyet03Wq<|vleI*xzF2JBfRs{F=}n7 z-UhOtaio|}GBn1R&!e!T575R4tR0WVKNQ8bYPPBV(@xcQ;2hNn+mVFb&6rzHE`jQ- zY5k?v9N!}TdXJd1YJe&4MEQ7%L7&5h*oj|aBe^FVsP(lOdu$)M+3wn|z8CQbWWW|( z%h~P78{IP{c>W#9;C$Xo@3uV!bZn#6#S_zBN^E->P%Xn*)I*td9KDD-uoI{et8YxG z&bVsHcBjT^8*0BdBUeE6cZX2F^6Noeoc$MEO8MW=7#m&nT2!aQeVZ7X>ASU}pJG9( z2N$R{VCeR~96-1Bn>k-RAfLbV2gw8Fe2E{l1}u$q3+a~dj{HgKm2afnlagWfK5J9E zQU|;N-yqX>?ey&f)nJ{$_+OcL*wsay2F+bvY~;Uc{y6%R)3FlWb`^DuS0nq^kn5uy zkF~U_?Y9xxy$O9O8T7gIIQ6{`s>2n5Tvh!y)e}+NeJykR8uA1dqhsx+j@xGZOY6|N zl!vjCIb69STKn+_c;INSh`jRg|8zFv4%H0f-r@oAP89V&mr+YlYn0b;9r@~3GMC7H z30M3IS^hV0%v0ylQ_FA_W5CrXr&SJej>CGmdUg#w>K) z+0@Zi?W1{&f%)iyKFC+@!$P*TR#-8Ug@SY`;zrY`FCsl!wIx+MX9_yz#Hh`-2c1B1 z#g)jLaQb^2vR(O`%D)j0BzpZyTK6Mf&^n||@XreVW(Bb*)qvf{`ul17H`)Z|YSpq* zz0v7$)U%A``Qxb3b!4~G+dcKoRLkiv$cN*Dt9k1EP1`Boe+K(u>~eW-q!New|y%`nx7jihcEwZovjs}T#`=R@>!Drob~J$s-#> zy&TnHR{iK9=$s>Vg}ZpmTITZQ)Sl9s>rLqMsvUNRv3>}@!c=O+PJvINupM+vI>AQt zJ;kAxg5~IdyQ$0b%Wia7zRfU;&}M{|f8MjHQCq07%{Ri9{c0naOV8qyKE?Q#FJO(=C$kzGa38rxBO>{Z7uW2;9Nd$8 z^ul)SwQafeLk}AsMg4keo{o%$FaBe!cd~FKV_Y&(->(v+LrdpxLtlMIpM6Jua5wgQ zP+z|}-=F%5Z~wfve3ZSpaEN`prNPMo|3i1{8~NK1ZT2K2$q_z;4L^#x=5xjOLZc)7?UQoc`}c1^AM zoL{Wrbk=Q5A7)?77-k={R{7)EL#!U(7=0hwzrFNl`!ak2^Z!h~fx|a|;DJtj8>%he z`Em8?`F2hh-laGB{`%Ir#?xT**&*nT!chD~Bd`sOMPEs z6yvZrwCK8Zk@eWJ$a<>fJ0HDhNAFr=r^zvlBX=mqYvVsI-R#7V?bn&VVGk%`Cp5tPopRX0 z*t8*!t?RWr_6Kyu|2@_%v{ZjNZOlddwPUoue}+@O%M1?sd@7RHLey z1G=&o(3!D9^*rfWU!(gE8ZdjlY`niqd0qc`Mv7N{M&5wO8-~8|Ub|PxJE6;sUG6T} zgL;d-%iagE#dYF4@kxY+u?4t3Gfuw69^LvY$7Q4K`kRbnJd2YJmruxMz}N#}eiO*` zHX8(8j+gBj++Sk8sJ20D5QrZH9}$E-jcwT7Q5zuJ;fVwPIEoDb`g>?2kpFg~Ydo0$ zgZXdgg*Y>(xW!1XXUlTn?`{EC{>o(^;WhZINEsBiPTp9Lovd$X%g3Q#qHphaIi7P> z(g|}NULcG2be?bnKMLM|H*uP{vB~k{RrW5hrnx+x@j3xp71+G-JB?iyVD0Hh@lQK! zAVB?dha}(}HUfc7mttjeIyNj7z{!@&#|`MUa5T(vxv`6n5x(ESe}KkrTPoj!V&|%|1}(0){_i z3@@g9cJaW64kGVLr_{bM_HGVE-Uo zq3%sry{$hpU;Pf6`++r$pU`W#1Dg8+djo32Vi|qbmy{t79Li}gukjtitUQUG6FWFO z*Ko=s8QY+gPsfO5r1O>X2hjR_Y#NTEFHoCv1H(7UYk`ZUEq1xvZ<;0T*3XbvH_w%U zVXNg`-{JBkJem`_Ad2alukQV2ZNR$z2QR1mzBv=_!1KW`JPEI-a(`U|Qe7a*_q83- zTiy*?ES&2joJ`x*Oix~$MmI7LKwGp!1*uCN!e<|`Yjbj$}zvg4} zL+8w))O*lr<^aGBZ1L=4uZ3!LKa-Ow2dL9+$QWl?LZsJ*CDLa8D5!t!==~yg^VG=%)`;Um!4>JQD1s+i}e6>vm>((IeiGb1W{%_v7LLhH)4QsP0On= zpL}UwzQ_jleqH-^{ZGMMoAUGQC^o+oXWZLyqjrxKUuq9|E}*u9bvE!Lyt#G0yc)bnUJF?)T|<`0o1sfN!?#>U9oi=zXF8@?VTVjXR-iCVZD_+^O~bag9j0?V z6Ht3Hv+-v`OEZ9}q@gy?reIrdHt%kxUepHfdin+JSC-Lk)LymfS^JiC)Y+EpGHB;& z>H76FbH1wX@DHT6_glc*@;p#?Ao~<~)`)$IT~BAmlCw!MtjlhNzh6lk zUxh86z|@1R+Z~my(3{%)R{Lveuh4F%P|vojT|l*4ZHH-GTLD{rlwGf<+HnAnq&76w zmZAOGWE-yRIJ4qS;{{iMwtvO*_~@PT4r6fF9gF4F?epc8Eps_vHv>Dr^Q8-C#?}6h z{VbPR1JZgm_0IZ_?gdj&+t2p3Ahid89dHFhA1Wh&KWIIy7iTvX!FMc{H^P?52S2Qm zUO%pe-}94!zi*Q6d;O#v^V0sm`Wt(sLoM1TU>DT@c8S!6rGY^BSZo#90a?*V>}uL! z6my*XFq&s$2&48l$6{Y|6kxS0x>d%~4yMD4ZKC}LVgGL#{e{k(Mk9xu2A`(uOug64 zmClRDOEb>Zy)d-9RPWbCs`lngUC&p{ImMcsRjj3Yi+gpFnjd#Dd#)Qz@s{=r$H}{^ zW+8_=D0ATTChQL)&S2Is_9GvFM|Zk!K^+A&fn&u`$Z?A&j$X=S^|nZ|^rU;_!B4E~}*HPhZPByS|WDw=H07o+odu zohh9;H~vI%TV1MdPX+4Nfa?3St^eq{u-bsqvA%iFH>7^AF5=a_vv_gNK;Z?>oOk(R zg1o8q5A?uhQommUU{d<$6Z-Kx#o zfxkEcHgb^med*1Q+%*tHH|T0+&*O6`?g?b><88eum^}tVG6R?G2}yS5)b1n z<52Q9f_x6n|gTd+qv@A(uwG1rye;!T(1Xv^Vm1` zQqR|=6?N=X2AQs&4`n}q6KjOds@sS4+#%b(mTu^1drNt{^>gHP|C!PS-+MoD-9y-3 zJ;prc?C-Md!XX38@xQus#5|jwvR~$={wniQ_sRUz`;9+01)IFdzsRgaAYre8+4y#t zdE#f8j$Lep8Nf_z94V;X>}k=vWJ(Nxja#21^!fCctFS5lWg<4RusN&t(N-aIQJK(T z__O2LS513g0RN!<@-+J8@jq{rm(fMiaQKJjTvAuY-cKGxisrLzhug_l8 z|IuUOHLM5c*glpq-}xg`VB7(eH`&hoBN+fTiJxTF$vt`am;-L+oMODn2Xhj^tJ+Kluk+7X zVM*F=viu@8*Dqji{o+AcdND#~r2HZyj_s0xkwL~zaF_LSIdd~txi*;qeyp-U4jE&nK?=;A;sy zQcybfAJ4((G4^g_y<{h6D1Kv2=M?+uS7D#;gKri{EA(bG9^VgJ&gh-fvvcg7c#-;i zfjx}!9kRDPW+G#YF^9#s1n=vl*_-dxC zi~3dKSa;lyO_SZom4ldn1Y&PANP#)%BKVI5z(UHlK+9o+g?7AvrzPOc4vWE!9TwSf zrg*ah?=8_V_V@9%9+m)#^Tj8vFfZ;08Gk4Q`{0|TKmBCe)l;M;XD(W1?TI!AXkaGv@lkDD6ssXBpPIc!Qcn_Uq zK(+VKqx5cm=*ao1PMo#CH~;YtXINC1VYTSuq0!Nxy80S(2CeD55z=|xZ0w7}uamD)*j68{_SKDzb;dq;lKe21XO3Y1 zU+tLZ2m62OYnqVV_wtsf&UA<5QDs5?c+uo*a!^-#Qkpe(Y-rs47L$k zNd@*Fd9=qi0{fC52Oc~(fZkFM)uRbi0Lt5-LK}TX7ze1pwTTNId};?%b=!xsW@ok4 zp=+Beqt^AH14d3Y9zIaZpticlAh&&lCxq9tmd}by#it{mXXgVQeT`!*V7Gg3|Id!6 zQ3ps5JMv>AI6rVOP=@}#MJ65!me*EKlZKole#zHc8qXRo?U4x$_gl)kfUW~5{||qt z>j1hQ;8aLH@|!O$(LvJKmvhI{`pe6UM{{m>7Iwu0r4_nq8mt^I9XHRH?qOe{zvmkn zs5}jPjKWZbeUArwnEbx373RIOe-m+7L7xxddIV$62$gfd8x8*{K-OP4VwH=q#`6Vx zia%#falwII#ui`Op?&4)rtFJVT@($`e~X@c1Kg{AipK(HY4rSLX$5TP*gl2yq4)$ZhSl|vz~Sx%n5W*uM?^2@>Kw;Bp=#?Wut%Vd1;GemJj*@lF} z>pH+7%B=WN9zQ<}g~uL>oJHSt1jU`c^U0oXq#OEFIw1$BziOP+Uol#SLKCkucE3bB z_L}&Kyu?~z>sg~@@X7_u^Qm`R-J7|e&I5}UlAbGPNn_gni|A~qH?^O%S}{(#FrWAo zKE8kWdg&V;ARq1j7TzCT6h8GcU;x(&-v0u0U%=xa|9iStypYCL(Mad6I(J?|x6s4CAhN7D6!3Fz+`j~(Dr9S`Gh5R4;y|SJ(&1p2Vtc905HQt^YT`7FQMg zO060H(Y;yYtq#(5WIt(3+HH}uwjob#d04>{`2=Z;E|-40zn0mt$atB@ z%%)6QHudK)mzjO^JDFvJnLL}xe+siS46@)yfwgUY$NXoDC-3?@vpzga_lqJQo{h|7 z4(p6sSIW2dV}17ZZ{|pIY4Z|#km@lGdr6t5=s(9V$2q8HID=$#;&C3S zENkG;*vIgfXydfo$3a=1KNdJsrFxdEKz#PN%ro>a<3FQmbKaqJQ*^L42iTuqwO0oj z{{1>>yJQ@B9w~3Gok_nCZ02@Lj)OzWx)8h>pRy|stoQ+sJXbKd#D=okQC3)}K5_>u z=(`SJ3$uW=<^}K~3xP$fp(`w4-9+~dXn*={@G@zQ?7PF~Q)HO`7xKh2>=|K=N85iP z_&&7&-gHC zg-l>v(7g{TyPv|mcna(J#@A~HT5tm8BLe~!L2F2*CBr#S-i&Ed=i zyRlb9^)Gc?;Uk0BEaW`L!}*``{(3IxN%jnV%DLm$S=W1mb>2_d%Q5;lbDrE^Lf zjK&{hgVBsN3L^pg|Bv(S;s43kI7h+nkB!&{&2Esl*3Xkh@FFiv`bg?d>>~}BlMD%2 zB~De?!(uzvY~Q17Y?iYxwWRJ-wu9=aH^6>X)s2`B)TW&aVIK!Zmtwx~58r1mZQg%+ z&G}01i&lJE%C`2BF{~Z6pxzt81Js|?&&;7d+OEwVb+EFvh?`Tq{Zh2Y?k98f$ z%>7vhGB%*;2WISsPyNB)=;t$Qn)E}CzV+i0rXz`vAlO2 z-q-MU(06pbcr@#KpR(7a2YRC4WE@hR$Zu?zBi)%#tzdnxP#N^uAiFkpj@h$P$OeU3 z=T=*H`9bYgYuNmt&+~`<-?>+Pt7?yBCF{)}Y-K;rde+O4yT2W{*vz4nAJ=u=$@K9P z=;J0J=b8-oaP5PPOJRzRp*rT;@M6cA{r;c)sjP!O4t$AcI%D@F(iy-!`s2{AWo~GI zwe8!_exbcapZhS@ z$rN@wgmZjnH*qNJ0>X$hl(3uG)9H^+5I@%3zhXb=mz)j!3R#O^?0)2YvDiG|Y=fR_ zr@t@8UNxihkaNk#PAT^#^Mme*e+(F1M)||q?)ATa3wY0&{c<(hHsd_QNzOC=&Y5TO z&felx$A4pAcLaOR&~L(e#ZJYG4HQQPIP;+Pe-*+U!1@f_IEDvLS~rfM_m}3BgrcE;bVBg6yWV(+G^L+?Ixwpa;88Tz43|5#qRff!-CWGd% z|6=wO@t!qR2G~HaKbzqry~Yhg#{qV(&7L-NUUBB#4sM)>bw>}@-+<@(Uw&}^r|=rb zW5VZu-|^nW#kr`8=-}%yZioz+F-Zne7VlY<2N*Vcx(uDo*b5A$EJLSLZr_J67~Bl9 z!9eik?K{c9!0F%-T$yL!^E2@NI2bTxygUxPr%aH~e8y3a_>;!Tr(;J;rw_Vuj)VRC zoc~gN&rcMk{WBL(eQ(9w+5e5+05^2NxUpBblnq?*^ME~sx-VSyL*@bJj9fWe==!XO zfl}CxP;jfvUg9d~4FSqj;XG+oE0o568Yqo@33qf=j$dyi{yZm|Y-3~I5j z`8jl2QZK5LL~W9|a8|V>ZSEn!|0xfcAN0KABjFt0$sg8!|4-rn)pPQrx_EW3d}-B@ z&zVaD6tThmFS+u;!mqiYxq6( zE*4ci7QFKih>Pb|aD4vgwc4}E4-eybG_3Xc-^1sBU++y>G=1`=`(}%>kHVa}()@GA zRzY=gj!08-7 z*MJ?s*aM<(FuGy#`DWbEePjkWD@Ole>nXy%?=hPkts>_7%`N95<(RIyxkNA%XpZ|TmH+po` zClB`XlX`~I?4Q-Vs9c5oKNcME@DJCjr_xAmK}KJxo!-CNhe9l*r%n6UZZ{o8&1 zbnW;55SP|hUcLUqwW(9^@bA_^p?^`Q|DAgOFT?z;zp(Wo4(UEpXAaLFiQ6HbLfWU; z=odyse z7q|ip6kwnL0|gi;z(4^8{{Mu5+7x!W7MM*Bg-{r0+;pp#@7LIqD&oXmNA~&_*S)LQ z7f>4!MbSZCydt)NIYZ!#9_b?32i9|VMLAQhdz9VFVK0t7+wSZSSDR7AtCo`@)zB%9 ztqQdt^%Qy*)J|muJ&TF%1(&+)nXXYqDxiP*>AKa>UCbVEwL1em)exH|*q>FM*LptJ zgFW|UU&j7;b1(5|X|;hXB-#+Ld z&%J>iO?3M->Ve)n&h@L_x7z*Qkh}(E43W0;MoO1iqtW3q zR$f~?Ryr)1WOTfBpFcrf`N~J$UCtSaS)A{jF-7_>oGPu?&o=sgURgI?x^R}R=hx_Q znLS;+*DR1x{!7d`VAW&v3g@d{=e)P-^X&?B+PXlxqUY~@&R@Q_aiR3uxLDrZwnTad zelBn9K<{VBGNb?dJZ2Zx?wz4goVDcaqUz3> z%=z1~oLkm2%s%KdP#tMLao@|FZ>{H{^xWIhU7Kb4FI(jE-CJe$uiIt*KJ?9T9!k&4 z%(eu}{BY}8s(I*~{Q}(|suO&<+BL&Q-|8dip+TRQ>VR2|KA82zV4#{U4>0|6>XNkCx*9(#Q;QA7c37 zm1BNwPaXc$ONU#>!2j;GLo{4IsUPdt>f3LMXnK0X58e~#Kd|2D^Z(S@U$y%tE^as= zV-nHldH-3{v)!A2Gz02PMr_yS#_=r^;;}#0*iCUvsq|HIS41O(pQngLo+!-ekHY33 zuFN%$6%mCCJ;7lh>;VyvL5w{I4^t-&tFz|Qe)rJV0Q^^2leT`IEW!?YML^*~q(c%< znm-Fa$zxJ6Shr*ET75>&puo6S$>SG{H(K6Q75P3=Huj4qJ(n^ zz}fsd8RzWeTtwm7%9Se36PwvX?;>9Jm3 zHEY(aZTr-$TBmA_s&%T=;aSb+YS(_gwqD@sG>BO-n;3m&@}`f^1pOhJsl;S{e0_X< zC;3jA*4 zaA0s?AP~&gdc%ECpr4;l&+a|@?DH(Eu5@Yl@@{3zyO!nS3E=duG#_=YWj!r@dUo$U zb4g%eNWi9m4I31K0s;eqxC_#|V0AmTPWqhWq4H(319w_osJfjy%x_3Y=uW-cxifs{ z+WFqyyY<>whQwTOu5P8>_{BNvk3KA|&v7M7y1JBE|6%9u!xsgGg@uIu&27>r8tsl% zBR<#UpLk;Qge8&2St2cwkr0l$aGvPBg;I{g2Rma6v|3Llm57_EAZ+!RmzJbuv2FpiH>(#5*s8Q1< zxQ0!d;5Te!bxk|%+Yo@8+Kux2Z!@u3qtw{^{kLe;Tek4s?l-(`+ioLEI?(`2j_lK| z+latOpW!nX`a%9%gM#>SVRzaZ{C$V_T|}D*361Jh&nwS0^lD(P>o=_5(95f7E6N|d zAz)Si;q%FKkUt+Hp#a=$4GPrT0RP34hx$aW?%T2BN27}tp#lm|^X}ENuiqIK{ zNJv=dPU9Qw@7S?(Cp8cn5)v?fdYB~)0y@;y%j-q27reackTOt5KXvNVf3d!oS34yz zg3X*C0Mdzu53wr2tJ8R?&XADE*f08Y@8&&52NUSuyKkSqzGq_csPk_Z;lvuU=}gq2 zLs5s*x;Dxye*TNDhYrQ?P1&Rw{TD9!=#;*FyfuAwU(X&f+y5kw+plx+wh~jXZkv%%LgX-rm!`HpxHn_1DWrFI^FP>8!dlr_b;`efsDTbq^bl zWp~(xW5*KHl5QmZv~JzHrCr;#QrE6kyLK(xwQSRI*_w6h_ME(tn072NmyRC&^>>;+hklEea4zZ6H6VmeQprULX6sh2A?39J5D$*W z0d%7Q8#f$?3f~#BGxC!j9qq1DhxVQOYzW;HYB?SgstsU`Kg4fdgWlCw3N-PV=mY(y zcP&<|Xfdb4@Gga%%U7uZ{dj#mFK83=5w_OPFCd7P2Cat#1LlIO;DF$OO?1jfW5Yv( zcUnH_*0F=Sc5T|V>(Hk2hnvGlKR$L-(54{z`Jhc9oAe!R{|q2OL5weC!!N)>_a5vw z* z(-Qef_x2szx9rf85lT;j?fUxF`@~a=lfB$a^=dG zcX5J;H7!S=^Os}nkh%;UH0ekNqk(Lw_6>;eNS0g|Buw(o7 z?K`$<+pUN9h*dE$5fL%zR}Il?So3m3-gsh-DI(@r*v=3?pVeJUmV|Sn0$>fFgs(%p zK&wTImIMTa9Xl4A78@HK9pm6)V`5`t;^X6DVq;?B(lb+%lG1b2W5QOC=+n7#`|iEH z=Y&Nk-pNc!NzF=)Immkn#}eWb^IYt)xOiTSjY&+5Nj$bGz<=KSHE$VK(hR@KS1PO7 z1+w$^4~e~S>C&}}moEKr>B9Mo7tjB3{t-u{nK!SVzk2XY#tn~@)SwKQ`c4|guX3pJ%Yu8xr=47R&X67D@h~g6+ zmzZ)2N`OZ4T;efueI_>2LLV~FtKo~!*RNfxM)gXS%DcK10Sj2xb#*I`t5gXFVnFzY zeI$RGu0AGSqex6jPD{b1rlzK(rl%*wBqYQfJf4%8nwoX*uhczzQvZSyQd6^!E9J!{ zoF^~bW)L(zBP0DPSE)(-lb&|SvLV1Spq8>P<;#~X>*C_(t?*H{5Ir;uyf8}InW#=ZR+Hz?^ zrDkSkWM0Y4%)Fj?Jv}il{^XVPcuVLe%YceyU33P6D_Vrvk#@iwlZOo`N;gan2E|z{ zF&SC88Chih>hN2x4v#l;m%L=}8xXt;?>K^3gK!Ic}Gc)z3Eu4B}(YWgwm#cS+dFnBMl(>N>jqo=MAXed#%#8ah*oQsQ(&&bWabNl-B>l)jg?A$xK zxw%U3=0{V`%FN2l%*wj=;K2hL;@vDzZgL1t_z*rvUr~1__fB>uF=gfErYA;6rwnp& z!8)QLek=JuS=h}*ZzM#^oql zsf84>mX&?$)-9?7^33KkhhPBMvY{#~CE-+hTvB4pq4afq+O>p5ZBV~%ZDwj!A%Axl zWCWnX$%*MqSsG#W8nx-~$~N(ViFWB~YWz9m^;9aAde*vra9@A!-J_sXf}(++TR9-)mO%vR5>C-%@<6iyO{sKM zNpbP%U7L8-sac~Eb2yznIh8Ex1lc2>pc`fkR3^k^fJp?kFyKmZ5*3=8MK=fjiBeN0 zef6!88=AOvD?1yM>$N(~rZo&c z2e}#P8QEPM)M2XR>RO_N(!WhGV%i_lqe9AqIhTYHsaBl|Xgc7|?b}%o{`!jy+|A9^ zL~Lz6&(kd2SE?}S=lff;K=JS9-p#qob#B)6>^oh(>eQ&>>B(eObD+hyHh+|_lyY+~ zkFezVx^?O_Y0$K3tCsBs?y*?nvi|xjS8I?yCnG24K2!iA_-ZAaLf^WV11Z>1ug?IJ zFYrQ_OxK%wE9b8VnFlRA#3f!qfH7A31nUV7#BlHXJk6&%qGA{#F zs#IhMa4Y4c?H}?d2QDt8AJ zB>(!0+A)=`zxuiNAm<*GYmk?keP_eSp6%PTY}uFwK;bJ^@+^j)ncDn3JxvGHqGjuLJ-vNz&;&IF;=K!tr&s^A(^oVfgv6))cz2`cZCnrf zr}xqJR|09Kjy`4tLcmD}$RwaFToCo|g&=`RRkvP!$Ip+=wI)ezreQ@1&%1mwTuPT%1 z21qknkopI`yoXPivBVN*9pmrm2&)Y-2O_6?)q<(ZOO}p8pJ~ra&C~Ca1@r2I%!q(_ z6TCnC@RbfNTQzIiv|$58msP7E2X!x1$|jL# zrf8JVd%5*sQc1NtN`T&or)oa6u(*d;W)~D}y zpLqcmjpjb(hY6(kbo&f2pg<$HWd$HNIs|m@(|1ycB_>mgsPsnzpchgKCo#pj{$X`_5437b{p;+&)PIsC zbe(sPw&35|0af&L)dd6kA1%Ei013FWvwK;pzUK3F>%)CDZ_%zpryfwB?^>N#Y7$zd zFj?7Ix3mJR{r#gE&`_X&zq7M%5*Yj&`lm0xJAVSg3PXX`0g&ORqPx4RGd(axf1)rW zKcZX)z}mIzH=y@v*|J^79+1E9nmf7LpAz$3xPV-`b%#Q0ijQ-U2YqGkU26w;KPUI@ z9Phr}J9cQL<6i@>I?vZ3d1V81{tp7QFLu(&4;*+^TMt0*OZ%I!>IU7v>BvDU$fx^J z)SFB^J|{{WCaew^T)`LSXIe7K*wQ*Y?K@&|3fP zx^?%Se#4M*cJ3`^7tDYRuVn^7O~+)!>glyQ>i@nDR?3)?GGlN?M)sY7X8dVN>NQE- zoxzudB$A{Du%>R6fLJ>KTLxOTYuC$r`nB7sDaf7E1fHu7^d}l&A-uX@0DQ*Blv_d;fl}5^Q#AQexbxjI4n@+O?wZ^{T=6 z>-rSSW|}%{i#Dh1k24%#c@IW_7YzlrdUfQ|&(~~@xN|j$`F2X`@#CpxK)0u0O9pJ` zYMvg+7xy~xOHVj{H6022TgN=E?x;eDWfca{+#zWNPb zQu=SzVcDHOuU*T&b2t0uRfIa(H?!emZK$^AfsT&RCdWK}lx&i_oVA7MIUG%|EL6P7&HGWs!Xs)x#~b_c44_WHN-_kLtHva_ zYf_9QA|XkoFelS8t&LC1O3UI$D}@Hm1T=#wu=Y24t)WZ+ZK*g~fAT5BN7+g7NIy@u zt5(C)^C?#=`I9(l+mbhm-Dg=tVyI&)Cd`!UxgHPg1U z7!NfInM_cb>VRm?4_RSCr1A-M$;p)BfF&&kfte+yl~*-w{?@Ta8=#T@J^2K~zPoz` zX8)CR@wR@mF0Eea`tj&k739SpJ9_A7+T|-0O&zin%6Eq=ik6fPZ*O{Mq#P+JNr;=W zvhENBdCl!RS$A$TgS>t%F%~IW>>3|S`AcL|_|6R-W+vm46cwhXLI3&06Y3I%{g5ZeGR^8BaW@dUy zHbcuT<*=;jXJ+3?hD9=jpyEPa!OJ+8aSo?);P}h2JGL%aVi{1*rFgNTC7Es+9#H!q zn`0ElUeMEBgO@DX8Xlh(g)}ub_9XOw(ke(zPD^4so05`%Xf@$@c2+Xo6J-bB4pY-} zk0W1Cz@1WY*QvyW#8dIwdiWqpjXHQGHpGA7!nIvVy)et}a9jvqtPQ}7K0GSqRHEe5 zT>}=*3k*LL8;ro148*G-`J{^Cj7v#6hAbK>_nqYA)Qo$7<;EP(`Rl&UP3|1DkY(h* z2w@Q#$Hm6S#>K?P#m14kB|Kz9Y-EVv!nsSj6f35KAGB}AURW@y-^tCz&8=)Xb`;dA z_R*qw3j#tS!~Ft7Alj%X73%8R10Qmbm|_!v>)bl??t|lNR!1OzWNvULVxL*`Q&D+z zOhj~qk((cgia2l}%EIDLm?db-+!^yfdf~Zh)vH&oRGA5oyUPEHIF(}Pt5DwKjjxw2 zoi){W)x5b|bUVO~9lKeTYZL(A3Ax>Tg90FIV1j2#s`N$x}DGD z$>S%_2-Nf~ku0%USHBdS^q-Ns3KFFqX<9O$tOxhAlJ-sO#rjvfJ|itj*K;3aol8qT zryaI&x@*G3puLWD2@RjczZpX(5B2_{D>E)lS6Lv<0xW2kNs5|z+jp_#ZQPEX))haq zey7XY;kvkINjx7<`Euj-tr^j=O$%J>j^1lwmEs~|PNt`^vx9|h4M`Jmv?;Efvs zLNBqmulg@jt#wQStDaYLYS<9dx7w`>`*Z`0<8)FiY1%Nj66(2WOyW*dbqHopZk z=56!ax&RD#lVHdq=6qSaD6-$8D`tE5?K?a$*xFjH;T;zK?GZ$#oj;#K@&`xu=*XhG)wOEbs(CAN z(5h934xL9HzI#1ADJ?BsvuuAYFQ&YU$Q^AE4^Ohdi5GNY0|h!mg5ftRzyLazlU+emBp+ZahcJJ1Ecu3gxz#V4IoK;Y$P07go z*vTi9tp$lGF{%CPvH!#Fth+z#cW<3fN}#Vk&o&kNTdcopq{eA{kmQc-TX%SOZ~tNc zv8D|a8rip7w?4zS_f>(DAspQ@UKA)AJx1ZYxm_~R`B)56Sl6L-gtU8wQSlD zym4b_FJFH)+OUaYD;eNt?bfw{#H?E@w)$_uEgAn|`}WrKixeH%y?yuLbI2u`C9r1F z>NNA*tPKd<5a`>j;~L$cu-Ve8X7#Eys@JSqv$B@FDnFIEsamya^%~7Bn?pB-hpuhk zXEm>GUAs2WFVHW5y>^rel&<9y;J+<^eL+))cW(dc$fB%0IzKhbyYIwBI@m#N`V*-{ zr$lJLJctaP{YjrSonGni$}3%(devtiWPNq;l6lwMdNt|NwNuwl-s`;kt~PzHcI3pS zQJa2Pqqn-SMI(CmA6rs6k&;XMPMmGqfM(vJ=^QYv$Fw0HMMYWyyxD)o1cpy7mhh-? z^U?apK@jR{=tP)L!f7}9#cdeTy?f6!TGNORJxIJ43Yznk#GMA+txY|4m1M!pkQ4_-SKf+oQdm*sqMw=Br8F+qdcX zWf_>(zd3j(o|V=eh`I@}q)SP!uV3xlq0b`i`5%eL;f9@&3&)SJ?*A*ieD*@!+Ot9T zMUsaAe%OR*bv)yG$%vlayZ6~plo?V9T`X6pEUf+7-0Kskq6jx@Z2Y8i_ddP>Y{Ur( zHk&PVPp3K}3kcqzE--NQ%t_;{W8e>Kb;k@FWx%^`;L{QGp#xpPtLM!eK51%%3NCO= zR~a|dsT9XJePA9cFHt9A_JoPPY=8`9=Ni%3oL+?Pv~5KTwS@Ss{K2&UqQ4~QW`V2v z&EUc~D}rjH2DT%6pBJ3I5WImB7kkD18S&CuY;O_-t)k3Gsq ze4eKq|5e%lqE4^B^WOXKzV|Nfz4vj0%}1}^A8g#H!GlH(;^)nZ6y=_^G#kGrEc&ju1riCCut1QblxMG_i*pLRz&$f>a3Kzdnvb4Zh6jKXM`2_ z*qjFt9%*&X%+Cw!$55X>;V(mnCXC)#gIWU=c6KVkyiUW}+>I4o+iIj#qc<>kKF_b?Y$X@cZy3%^5*$lO=sexmJw_vAO=Gjumvh(I1`E zsWWF1c=XU|2;0XYrT^$sv!TLs72oPLcmGpS|3~>Lc&Pvb1sEv6Kmi5{Fi?Pj0t^&j zpa25}7%0F%0R{>%@P9i7R8Lr2U^-Cfi7Nc!rkmgVKX!dV1^(Z4trp;<04D`BP(T9( zGyn}C|24n>@>qP>|5qOY@&6;>n1=oOpYrqHdaiav|DimN&+Ta;A;;b9s)>t*fJf3ac_2?n%k%2T;9u+Nzfms7 zGXDKq?M^?L2YF@HJOB^vmt&*e*gVIUdQt1O-m?YC3fPm^`+p0H78CYxkp@uAY9C!~ zsy~S>bxoJuMb`AesliEMtNm=&Fbp18u6S5}yRBFUeAsbQtfY(7YxAOQW&Q&kbG!<$jcaD zw70x9ihBjFwM=hO1_N5QQ3K@N(F4V+bu%f7t$U}ZoTc61-ty@WYo+_nFXe+BE3IIz zS4g)VR``H>z5W2`Zh)}*40tzmxxA0xHE@x&cYxZSt1JzkMK4E}Y!bXqq<8qOXhA8uSEC7-J(F3*;ilIYYeS+ShBa;>^`U7oEVCI1OD zEa3cXIRho}O8~{6^OSz;KbJ;xhDhg)b7e^QCX;Ud{QT|yH}b4`#~(*N$Ne5C<07_6 zx8TpENw-%`*_j+jgLgYoZ;NGS^e$O={3ls(><3v8`=cy8_9OleGXJRFKlIHt_X}db zmxcVd2$&PSTR#19oitlCLb`o3S4!1?PTXI7PTXFoCayIqiYsMMC{_JgaeK}NdhH0{ ztQ26v@{SvNglRAur4yw89)Fpuc>xw4{gJe-urT%~QCM*7A((snbHXh? z{1g`>5^k>2px?XFbn!^(5xiW+NAHl~hqudc^wEwy6ePnV17-NZ zKpBBQ3|+OuBG6TPAW(+>j{n;h8EV;V>SHi<(&M{T(rhU@f4`h06ZQwn_Yz3#8c%hX%TC8K5H;T_Wd2S51~JP)&Mkx}U8RTv5Fj8xwS_UCqg zw1xD+MGy20_X=4qlOoXF8xbN4qP`cees4>Kj*X@KD@~+AhbH3Lp{Z1K08ey1D^%>z zNS=MAv3LS?dUutX2Y1R8;+gb&kn|2-As_DeihOJ%KY>=TI9%JGfeU*cN09&F;O>*3 z{pDl&w~3UY>D(dGV$LvW`2MR>xM&eGZZ>?kvvfx9a@#K_5++F2#O;@bF+a-u80ZX` zr+)PJw)^j8!BH#Bk5OO4d{3C~%=Pp`Ve-YveezAx0r5}#U4l{%NsFO9rQ#beONG~) zi)ZI%;@Qy#o$vtQtmZ)r5PuQVJ3sBfzDrjp00 zM|X)Yd7g3n2bp=|C(7}&OpDnq@68z{jeB*G<{!Q#)$7%Pk1AxwzfzSeO6eCEdtQ28 zYW40SeScbG$}*Akd`Wi*x~xY+U)qOG04CChPN1CQfe8nK42;Jg$GyTB+K35zV7n{^ z2P=;6L8o<)%sH}4TFxCNFHQJJ>QR2xPhNj)Z_188{$pt}d#FtPop!@}tK#;`Qu-_H z)5p+%kNGW#acMhgZI|)Ts7WtEpDE~=Wxdw)wM`EFb%V^NPo7Ns9}}@fJ_!Co>U{Ku zlzFMPlzrhjd8*hOri25s{q$k^CVs!{ z{^J(~mYeSh{VPCzF2JEIZE}l>qc@e|Pq%Y)FojFJefC zcvc?YXJ8fWU={6i^@(u#>coEXMc$78YC!dQe*r85zB;i_0+J$R9Ju>r?>F=vE2UQF z7E;vBg*q>3`aL$92wP2ry*cs(JoXdtnN?n{FQd>?K6vjY8NGj-e31|?U!OTBYtKfB z-uC31634g1z2WpB*?8u#Y&m;G zwgQ3Z7T>=?>J0zbc|0Z9=hs&saTf}?Mw=#io#H&Md z%BW=p3M+5_gp`2)bA>-CQ>&VkdjVdniI+UvzOht%t+`Zvzk_&h|H|+XbB>@#ojM(f z9{1UYxIX%`sq?SVpT3$p@FSmVPgumCx(hf7B>XOZ)cGR%tcAcF-U&M!E5DwN15U`1 z8|O`)G`>j6zVBRu>^&PV->1dN!b3mGk|RIKBJ{PdCe95B5wh{rK?9rcH!CDYqJKTo z`1=2HXk;vLErc#-&>sx?B|!Ra`9dl&rh3w^R-{ZG&0mC{e9rLYt{!E?$(cC@^NFI+ zez7v{QnGw$aq)aco~m44O2L1)*Q+k01HY09wEeFr=MR6xnX-LD`2v#;$@Y{Z5|SD% zJ5R^RZuGzJK64zpjg#-s0BOe!Y(qc1hTWKOP<~1~A^Xprl!)_5l5rPBDijGL6G^o-2i@hxq9j|8P0lK7h!#BwEBeznxn2{=$OG)w@V7Ztgzo!#Eh z{8FME$Vf5q9=Vz((Zm~*c}|XAdkE3jFUXPW=OvtXf4m6p(oab&dhu09zxEq5g9D@l z^<4C67b)ygoPLh_F0OM&>-?1+Zp?w4q{P#uq*TT7@^tk|%*(J{QNNayYy5&#Y}Z6u z4e2GX_zsp2*UmTfq3v=DbrYC;#L)Zh(?`i$nw-3GQIc+4l9U_hn0Bz9`u~RZ^7b+xX*u>&Y4S-osfdk@ z@-Nkqvhd=ipRXov%te(JN|Y`ooX(-%3sdLCp$S)PfRtvAQVt#*`yS6TM{R@+k2VdY z@*6FrM&H+E_K#a+3vKVav&X2j0= zqOY6-&O+m-d&bBU_u#T9yX zu29;P)#<4c$QO#x4G7~Uwrum__CHVZ`USlJRCLl z7*cOuCf{dF*^g7Uv)pG;<}0^v$kptda`pC~d62>VnLn?{**~vJV&)}DxOPcWv#!dC zs~4#M3zEirr><+cE*Y9hyLnYIvTvBUE)!qI?JVN{6JOzmq(N7Tqk|VuOEh$n@@KkO z=sQB`Pv%1NX6{XW>paT2Vi_q>)>Vp?Dj`lTO8+X8f$yd58yN{>ALj$5gxgc%{LIth z!dT!&J9zqq>frAMTm84D{=3TLumIUiKkz%azx~&JNutgVUOppH(D9M(9 z*_8dxEy)C~sGozMoh4WB&r_~c=t1$Pb$RM~y1~n7+R6FbH|ZCx?`Z#!2RT`i&ij{$ z_ayD$FnEt5-Gk6d(zOh^j?Ig`wEu4zYsYU}BURsOBOYxU(uV7rIjCzbY}8b#04~Z( zamrYXvK9dkDx=gnfX;(T(kHrlmJ|2to>I1Mbt%{IdGWw5O_kSLNX-vA$@8BvNA&fU zSC&r{?;We;w~S~zqtYunfQxr8kat@m^-6OeA|@upm){O73q z3)$D@3}Mew*Oz#95gO32mw89ubA(IWD}GYI+kx}RrtCkTJx+Z?ix(2*C&rYX8y3pT zGX_b6;U7w^?wzIDYb~X6>&8-{31eRU+TzamUaAVVdnltbc7L3pb(L2dSs^&kd0-J& zXK|vhEKvb_K$R;Kt@_G3y#MdqXZ$rmNK374-r2GpWzZ`d^mP_@8p^&TY94ZCoSVHU7J*pzmlH z-~xTf8OokW-7CGH!jGi?*iYI=NONiA_tJ>DL!ANdn0$J6XiUEA(%-@7R;?(`*fuIw z+MTxTV#YT!20;H>cI5#JQU1auwCz*=GOkj>v#hvQr_VrcRrX~s@q86ts7FVsHRL^M zIPFtu`Q-$eesG7J$;p&!j9q^+ek+|H*Zjf19R&wTwCAh2&~k1T<9oIN4R?dG-ptLx zPRm`n4P-w6@NeDAm8^UE{ElSZy~Eg;LmRp+mx<%ljdT<kdQ%z>?SE>A-XZrHVY>S=e%Y^8rT%o`jODOOC189vZWii0neL#a&i z8PoSO{(ab4UKsj5boQCFSus&w-?BhH`+1%G3NLh59zfH7%6`W8AF%23Gvo3R`ou)W z?z6OuKN!cf{cHJ@2L7b%S$DO*?@;f0td*^G7W| zc7wG1_bGn}kay&mwPp#`JqS_c8n( za$nRU82(>rKxF_*4~5~4iaZ4`P^J|9L>b1-XNluEsnGT%srpVEsr6}BX*m8PX^EYy zS2oO%-ao97ZSbgC{;N4RP5G7YRetp_{b3sYz*)vU?Y}Nl|5|=6_buS|JuA&C-7DPC z^3zW2eS`LyXX!`NpdTG)m3RJuc|-Ug35K5TQT|m&f0Zs<=1a>j#!3V7Rp-+;u@lzT zjDKa|;asUhXX>&9yp7KJ3TvOw*k{&hD6`Ig&=bP*;^IW#P`oTWuV-m^q-UiJ^Mvxu z7bK-}+0gRy<2IrH~d{Cs8dZ`f;=J_g(Umk#mDFIG%hS!;aGlIeh7? zBwoE_t$XTIY5E%Fzd@V5dB;ivf6~si&EGW7Z<+enYaK7O-&Fcff+skXaT@;rr2I(v z_opQozDmoVm~|PxCq^dk*(5C%jzFf+8(U~^Nj2n*m0oEk73jOlf(N%6jDN_SOO%86 zf!8hS;%xc=t#{@9b^fo>m?aTQxPl=5aAs4k_dG4>&UFM+)~r&8F!qtDav-_XCIeda#&jZVycoS7pj zAMDP$OxcFD#RHj$(pBwG-y)5^(h520!o#~{KRk!>pL-aKW0~{(#`v$iyWw9m)6JNE z0si$8^?xbrx|I*$+VDd3g?8TH3Vlc3m$B zFVI5R>1g`Hld>>8R2pF0t~T_qGG^s{pK0_0w)v`xOJxr!S&s3~oqAWfhco@2uGKO2 zTj`&Ho$_6z4^W=y673`%-sdv?h2i=Byk_`39Y2(B zOb3+DOIIFDUn47cbJ=9+Fxgw0eD=On zK*n0O8TJHOJ9^6We-+I5=k&Cz;qMDmRsKaR@^%TB4?r;AkY(poArf34W8o^||#7jna9qw@D9 z%9ds>Kz|4gm^Eqm0c{UXjDN+E0h)P0rE>DLu2(i-?T)de@*AyW=C1YPha7Grct4b$ zN}q(hhI-$3E|IzHACjzPqQ6$Uy#U=>;W};gy5aH9FhA6J>?vqOdD$4|Fh?0D6$kO~ zP5R$i-dFxu`@Rdb8THTWGsY2}L!DyIaftcWFYp;ZG1h9o7|vW&WinsI>=obOwNk!) zBgU=T;>J4aQUyZe*AY{dS4Czx=$h$yvOLhZLp1E6lk6Bz;?<5{&tj zlLLgT{aNLIMVyO?3t`+DA4)?XPwQHB-5OG%0enBQgT9M=r2jVyWzd!{jqSu$tVwJ+ zr!vQ5vKjf~u8Sum@>-glfTmAE{|U@fP60{_$$w@@a#jXo!g*81|JB}kz-4i6>)xm| z6+saYD~cg@Nz|w)_TD9SW9$W$ph>K;qhbSFY$%F|($t6rP=hEIKqV2xSRx=;h>@re z6=U`}>;BgOZgY-j-}Afo+Y^m3n%)6DApJTjdX}oKxc@HJ!8GUPfP|45kNy4vpIrsI#vSeSC zpu3l4^R0OCWn40W9G0WEuar@%7Kl?j@`*O*9l+mTnd_?bK`pVSPAp?%4ORhP5dIGn zd|qbcMK!1HGt)i*oL((rx6#nd!Tz?$xz`2L` zt0?sf3zM)*_`Z@b^?B$ss(!804{p*=xXC;ILVjlq{Re#~_5Z0qC64VE_+A|Q zs2?znXXPw;A=S?C%oyvbd*ZOuSTy~NBUxARMd4q>=e6rbyo6?6BK8m=lZdy>2;C~y zyu;=2_h^1si^`0Bwf*B-4PThHf9n6xu@F8GV_d{DQX1cgNkzuU)hfsc|79|i9HCzk zOA90?rgvhbtiqqWow&}Po9FRWpO<~NE=njqa4koK8g=uxjEfK4`JOR(f&}6-Qvb02 z_A&TLzoY*CmAX*dZhgjeoTL4_W0@&(jCW}({`?)+;$>&*ISKmlq6Bfz)pUISC*$0K zH_qXQi{rhFl4ZmTcl~rhJkIPB_hVoawl0>^_Lfqzyan$y&wm+HDN*eIb=14WokveZ)0WT0{(z@ z<5S|;J;T>ZuLxi9Al|)%IN-L-3*vt_LAIoym+izp0rO2Jskl zZi(W(3MB5aDebguy>(8uVcU8Bw*N$2;m!r)`~jJiLydCm%pgvceu3>5WLMT@S#m8# zH2&QqVl6pxedMzvo2AhwU3d@he=ru-wyw0se6$|q+1L7C$Hk7VU8Gm|D(N1vO8Or3 zC8o~rO`LJb`CW-?EF;J4vMa}B1*Y%D8e*S5#NHMsMG`lN zl`vvAfjp~;_{xtEGnjKSSlpI>E)BefNTcaPr0ue)GCL+%LWpVTK6{CK9wyeK@h$Z; zEarUPoNqbjTZZYf+EP6~v5uuIFUA(KpT;Yf5`&(fKzx*#Y)|kqokNyO-#yF4xqVZ4 zPk(djJfr@{_pk9kYhoVujCbsb3pT=c)ce31(Y(N;$m{#*shu*2cWGkmF7Y@U1nwZ1 zyuIKKc*dqtFHR%Qpyqid1S96a@^tV&pA&1=*wZ(R&jW}LeR(F-h)cJ{m;S*dcd0S7 zv(y;aL26^QhJc5dG(cJ`m@F$2j>sJ1ZXrB38h4m}dY^pG@w_K3E@|OXT>!nA~3UOgvpx0R$-)IHmpNh>hq0Rqzw7c{n z4%cPJLKzB{X;#u9@%@RoH93L(nETu$Uof`v#&_aFEO|xRN%6r}kVAMmG1?WxoK}D{ z@&19BGq&l*Y2twivWFO;#x(q|o{`V?u9d;tz41?skOquV8jk-&8jo|A#$(;33CoS> zw>AMMFnFuCd=|7;c9C~Dj{aSc#uk4{kd4&0OTZv31!v$*Onf=9?G@Kf80A=5R30sp zmTxuXTy^U^@x60K{L;_J%G=+GC-1_b1M8)e|03`&KGJH;0Aswb&%sAvA>8okweazj z&my;i-8dlgz(UQ_Ic9t&zN|59U4CQ4qUU18`^;tgY`*smG2p#C%lnCa9iz_cJa@8G z@9SpZ8miHsuGZ64YT#F`fseA#+@wZ#a1T9Pr8?WI_h};?zZfUSiMNFkYtfj)Cf*}G z*Bs8FrsvXf%pnGDSkZdjW|WgQ(Ab~t^Af@FfD8E)T#90-+}3cdgL)eMAZ-I0_3b3h zmQI$B!G*EWOw!vpk7)#a6stpSdZlA4Tk3F$_y-Czo7| zl(pbr>hx|04yu+Bi_`fVBQ|HvSi_3v(OTmajJMRR@x9mN-mFSICQGS9mh(_#?$v)>(XcwiQ)(Ng z@z{#|=I=Cq_c#0LGIf=8V<)le=pusyR*2iG88VADUfUX@4KV8eqB2tFFT=cbUerz$ zf7Uk28(WOeViee}j$0N=bFN=859$MII=H9wKj24Qx>u%ym-3=sosP}WInS7v8q39| z>)$qAuitHtU$im)@lSRv6*G;q(brV7&_1=!?f6Vwx!81l8P=;==z9Aidutyvmdn+v zA;WgAz{fd3y6;>fp5R-&Xlt~r#pRezyQJoIMz5i8+4!vMiVdvXING#cU`;;$q$|FE z`o6@!x{Vr2JmZ9HrTR-!t!gZ9DOy(SiP|>SZDsxq^A+6RnY3|UC-;b7 z(lIf^hg6z=cIgV>O_=NWLd}$Zwf3#m-djecsKq zt6P~DT45QroQ1}M|D=6>JExk`vK#)LUfspD$XfU6 zCT)6mH>`D^?$WwXPifP)r?lougv(2-wZ!-X?&$7W!3N_FxS|5p|K91im9dg*x6QW>T?5QV^VCaSKo`8u76{1 ztuNlL$20sDdJY|v>HbB>Ev4tDEdS0dw49V#%Wafh%U@J>ww2cH#5&L!GW>sUWBwHr zuXw0;8on~5i2h_`y8wItma*ONsWLY(Rf?;^j94?{EybH4W@O;?i!u8K7D@ZxZ>`sD zf3l+QBJlsG_3FPus_TuRqy#U6J}qr}v7w7p80TiaasvwOKgaoa``-M|mW%lW@7#|v zXg@>k?R%{FlXtGyxI~e?^LT9gYyDsM;IC)kuV>(|XW*}A;D7TO;Ol=)|6T9EpLHV4 z3vYwtJMV_k_cQu(B?~#2rA-t&LBE-PusMBG9nY9+jI}aj8^(G@ti1|YGUCY^d$*?V zZ%v%Z5{zA0jjO@^s!$g^Djcs$#OiDq^Hzq3;XtgWjEj@O2dwM@A5gJ)%^HBkYFOw) zaQjH=G^r17rM}bx-&3buOE{b@#lDjZ+#>ixU0ND`B&QC|!2q?E2A#ogf#0l)AI7CU zz9+a?HHLMT#{D`O|JuYUnvsnvEMMZN_~f%{&H++oWOA)N{DFO&cMN zzZfnbfg$;54*U?IlG{;khVa>s1wF3Xu9N(7f@@dinEJ$no5UstZ!)u!%t=E1b z?bgjPe0*-3=SW}Q*)nj&3}Sl=7_-lk4q&Hxe=}dY;EV48wq?LZmKkUF*tt{&?pOx) zc?Gsg#_YgH3ht-pp5@Xxbh-2iUM1b|mG=%`BmEgu58Ss}KE==3Kin6N;d&WGR)`S@lQzyn%* zC_t7U+9S)3!io6~PTqOqEBNMCpT%#3uSEF?%BT8<*!ed}@OAK6Z%aNU%anTr&u}>$ zqSeYP#0R_y+{-rNQ=4v{mfb&`6~8-YWiw-Hf5zIIZl9AKw=c*}<$(~74!U_kLT@F& zfk`wl-+S=~D<2@zz)fG36U4%HWGT1glI$gZ8J0ynmAG;U@yu}ICC9*5DCRAem{BZv z)3e~q&Jib0B0hW$Y9pbNxFuVCMUKe$HBSh105U}A4Q`5BJI10!yF3tV~{ar2*u z&t?;Yzx#^VI#>e5nr4AFP^@kq_^aQ*VQD;)xBp%C&Pw60mH+HJKyCq@RN`25{`qK# z@*+rJOw8E&CJUFb@7sUa-=u^I0?i;`OiB>~H)g6sY{CcN&?Y9R4Fd}^j>0hX-%gD1 zOktfQ#1YRoxG@vP zkDuV+;9y%M2hCy?)rPJ*ar~GG{A2eqW5$dcKYkS7_{iL1~+hT4OuA`|=HD>(=!)Ezv$8G&H<8|3o;8!Sg*{XD`QhQd?M+Iy^NBs9)9Ev(WA)GJ*np{vDYT6p`BiOf zHS@ELt&M}d{o?8VadBs2VvN1@Pke-TyAtNTKKDJae}DMC{rn5w7tG{0V|G~3(f&rgQCMGs2F6y*S(NSj(#$QUl)xTlG zhV>gXZphbNTzaQmN=b-~i9Su~jSGl9eI`C+)|?I{OV{b#`Kybhs5!)RFe3aQ89+i0 zM#r2x85lUseUPe*Pu>}LD)~&z!B93uL?AqW@L()oIQUhUF7KBxvvH`|c0*Xi!Ce$Q zLbC`IPVw;((Nl)FyAN<5~^{->#^zaNoA%)G>R{%)-KHeQ+2_ zf{Lbu_{4;S_$#T2=|@*j88T$b%Dw47q@~2Dxc|9x35fHbIUk>p66(Ls#lpgjd{m`O zor89TCf&Gs`}bJXwU4l0@87-qB=uCJm4%s^X$h0}Ov_ZP?BH0te*dwHQd3h>6Ekx1 za&z-@^Ye1VUp#;Q;(4AH;bHbY{^mT&&Ckz&o}2s2y|ioh)2~Fj*01ScYhz_q z#*8Y*#Jr59m7PQFrjfU^^K+weevQ|!K+uVC_^rX$k%Tdyn0daJU1ujVczd|uWWN|P}APd z+RCzw*?T7D#@C%(TKAYVYx{%jYgswjxyD!cR{jgUn4**C-?!otehzE=G<-9DKS{UrNd-h)TQUm+)&{sKmFwCDM`Il1>TbB<5y?%KeS z3$QdJH&uxeCT2znYBhB2G3kq_-wJ+z@#yg*E`elbugHa_NzIsEyvTk0=<$o+U*$zl z>gm?Nsk*JTr6sj2SyU}6*g877xOR1)Jlos<%B$BevePr}{i1h~?=$J0c#;3hz0B)b z`LAD`S~q)`dt2AWPE{Q$R@5Vqi`c@-u14*KZF)?a;p3nE`c+m&Mow;CKAG5xzW(Y( zK85E2eE#~{n%N#b+!{Jnr~Kv2`1wkdFfD0ZKz&N!wRB_Z>w>KGjGTPVU-Y^=(e(L(6&XU_9eoy+g)K5cGT#!Jet=hs_USbQ#npE5Tm zBP09y>%6o*UIVy++a%t0J$TaWxC;WDKPoBXVAT^x+*Yt2Kyei0h z_W1FO=g)pQ=k-YsH`fn%0Im7`O-h)SHnXs@v8_(|Kk))NmGLP10ry`YVdF{E(rfv1 zA3ey<$x6#OKBOy;uf4VYex*%lE%gFy9BMacGx*EZ$>}Mn8EKF8fz_1M&kOSM3s~X+ zFZ1)Bre~xkr++tRY_A5j_3Yd~(-P!@tY}-svA&CIuaoI_pXA(q`13POa{hvUzvbmU z&wrV3d@3l&fBEd;!}~c;($i10b!p&KwTi8^mC??Y(f6l%y{3_g$q%R^9_4ZK`Ik>7 z%lw>2`HziPfESQ=nkwXe@~Lm@)zqio!rasZDSuN_GmCOom8v(`8l9LPpLp9yLHEm} zJo0yGHo2Va%zO8Z+!POT?%%$2?oNF4H?=C+SZYFXtv@Kft%IY}%x|`wx^(Bxt@K~X z;>VeJ5g(?f+ZlZYG`Fv}9(Z_Z_P^*jk#Hm|0kojm>^* zaKtU@-rINYXWxIol#_iwJ1eW;_3KyJcc}rh`AFfjGH$0PC#Qx7Y_+5Muq3al_ByEl z$}d_Unj95&P>#|0T_=~^czQ~y#iCnrWn{TS@Gu&gQf&cvjI zSs7bf$KL(i*Z74*1xLih$0yKEBwtC1K6W&NcX@AMUUqi+v8bpsr($AGorymaAAKzN zV6dN$yZcC&Dix{!NXgNBqmR%2*dKMeLG!_MEAdi#dfw}oR~F1md`a%OjFfm1lwG=f z`Q{CFx^yG%fX}jZ?X0cJn3m-JJNc|wvp*s>;$Re0bW}9c!GzT02iNv^4sf5cCnYEO zT7t1fw=+dVMTGk-Tj^|SN)M_+l`5@$Hg4SLXY>w&_wAyO5V9-iRAOk%ux`!hM06W^ zd~Zs8NFcq7T_M_o2oBz{F(ANiX-9`D6-~@KFQ4yw@M6;WvuDoIJvkj4cRDuqd`imw z3#%8pckkY-d-p#57OuWFrCFXg+D`I-hm^yLJTc;$QHN zT>-({cSc2>bfFK$WN)v3U5-aZ1#S=CrMm?1H+VfuDI&|vPse>)~4Xv#zT3c7N z(!Wf$wjH{#rBjDa9XhmX*@3SxeQaxjmKN=hCN^PKG?g)AwYoeEbJ|1I8~ z;X?7mhy&a-eR`sVIttNYgo#N>9&i&jP*Ikot!Hea6aD^o3?Pb+^w<2aJMh;X`2XY% zsGryoYmdEWEV1B-{rl-%zN5d^z1yeuPq{DD#D!d!x8{n|Q2fMy#th6xF}6c76#w8_ z6i-%8xrAz9%FDsCC@4S7X!2y_bdJo41TUm^=n39Fl{GbDAVVV7 z$%-FO%CfW=S$q}T-BoT=`P;$CjYcw=^||bAUbm@s#x1?JXs(${2bct=4+U zmYZi~K0Jkm*aDq{3ttF-LGkPJ(a)L~wbRhf`!Zy+R7Q8rp*?XE_+n~Rz=>BQjxurg zT3HL`cw+Q!<2Z}JwEo>VE*SH#G1WV3xylnf;d|&vls95J1Be}{4%%yCC~B&|At(mWV8PPjH_?@zd|Qn=FB7 zyNy2;C=>iw8XBdlC14KMN_lp+aPKR_zg4_u0Gtp%_*uJ&3GBUju}C|JN$w6WTk#M~XgFka59j+IeTid{cD=yx?MSZEH_~F`B zjq5LkhLr^zJ_~(kczzcWJBdUWTYKpEJ?W$dVMeb2`8F z_<_Nny`K*U5W5SfOf{k|{gN(Wx00k8_;tm{TGp_Wvgn&AK2FbX0=}&RIKs+kSygM> zOg zCo|!UO);>8r7M(?GH7VnwM08+SQlx! zc!HdJl4)q9C=c=uT3^b6dP>}-P!mEm5#UI}jsJ~xst2I!l^b>U1u+}A^gqG_PJ{E^ z&=XysZY|L0sSVDwqESa0oDS+{Jnv!wzgc;|_RZ=`O>p-vo^VVy&P1X{3&QVJmgvU)rdc- zF2IYw{bA@|C{JFsV;;i)ya`7v8eOpOc|IP(A0ByNgEablh@o*%fq1b6IAi6nE3e;Z z=S)m^{>p(ntt6G;vuHfH-q?Q9%4eFKdzuN)3a;uSv~$p>P<1EV z?g-pS<@zO|`?MlPh6Xnvx zKD-HU`g=)5Q{y)ALVe~lhzVYUgQxGh#u2Z=HC62njU^gdR_tf!uCVQ}YTdwd^hIN` z9$Xr2FBQA3y!nzQswGpx;8ht|Uhu!RjhxZ8i;*pGhBc;n?B^SBm$T6kdxRD8L$zK> zM$ak+F7G3Lcm3^k`3m*a6*!)$`S3pBe14yIUy|~&adu7W8k|+t zv@(I$U0Sc5I@k;>xdk|b5y!WK%{~HuF`TyMv;U-{&be#1uZ?Mya80t8#EAL>KY^?Z_!K+ftrBl&%_*DL*at!3~**&SO3F9h`=D7w2K!Uz$E7ozT#5dz_C7u z128cbZPyb!;I&NR+$F?}wy0asa&$vuWI0@^6_{$X>av~Y z7BB1Pnb=TxNS!y&HMA&Ib4m3FOY^>}DKF84d9jtIE$y;pzKlG!4Gt>5rD}`5ZMqH{ z8?(dEGE>ZVS)N7ZyIaujHVnRhIr~cJ8`4%fRFZON;FPb576zJ!%6V3^(EYKumyOTb zf2V#g2XE5&J~|1iCt(hMRP|EK^?MZytyoh=#|QX*-f0XT|2}>w!aoyUGcek!N2Ypg zIxjY@Q*?Xr80O9Tw~qgh#v^}id+UDxgK^t`;eFyz|M#Xp`zea>Y(=wwQ!0Z4q1+E3 z3kLUHHPBQiLHm%(wJr3cQt@n`TmaQOG^MY^7!=H$>b+D0LszYP zOL?C@h9msFdSK-g8&@A)Eobya>S1-!=x#Quzq~)1&uHT`m@q(^z?uJe^Z;>z31DqTzwea+cCz#C($?4@kT6~hN_jJnyzOViyOM3=wMs` z`*wkTiDKWhzgmoWyG1|s2IE=PmAuFJO8d=^=~w5XpQGAhs;S5O|F->`>fryxN8%qi z&q|Qo-N0s*(v0ebQ^k(P)Tv{~PL*vc+1lDxs$^T4xJ}{ejuks>-m+PzEi0EgJJ){S z>HYU>zwhk5ato`sY~Hk{!-p#(k3>cujy$m4Yt#@BM6MsZx{vY-I&fH5A6Z(z;pb7I zhrd0{v?uQC9^ILG%!}KppAR1n4w+WJa<4f@4n-U~a3DB1>iqt(BS(%7NIo1)$m~Ew zv5R{T~IXRg>H?vdc%udR_dHKfm8w5VDZkuTGUYVNCANwbtO!@Ix8=LcU@(Wf^9?;dfs%5E?7Iw~^2Tt8v@Y{p@SH|`~Ua|eRR-8Gro22{=Ihv!6!yaecqCMJW@TYK?kKJo@Otlb65`3c#WK z{`%F+r#U%0#<@CIwJBA~q-?cD6B8fY%m3|V0U`SQy!@9hU*u(FT^Lu#&b(Af^U5`y z{f^(b_f#+aX->hboTtz7o<05LUdl1w298xt%xvm)Sss1(Lh|+Nx6WUC{N&f%YbUQ< zxqRtD)Zt~FKdfX@%3{oti1_%62?;6ZZ$5fF>#Lk!?!=uvckyC;{I`q8mn~Uq=xlyo ze&25opT7Rh$N>WeufKWX@PWw4$U}$1z8qT1dCHo=pgnuS_6F@ezp#0;X3ai!oqu6} z5Yyg0fvY?|=&%U*7ErE(`=<}@{bA$A{O|qgmj@0XJjnk8Gd^8e`H8;I*5n&v^b=@&lY;-BH>--3E7` z_HV@T&5TpqHXk2d8FzDsRS)K762-=%83JwdPSDZV@1mWdv#ZS(p?V_|%u zaCj@DrPTl*l>4Fe^7>!?hW-=!@A!6}8d{Jq4Ih-QdyW20uOM%!JFu(R;Zrn+ORSn@ z%6(`=e`+K-44&hI`yT&59^W;5h4|A{Ur{w@U#fo&9g`2xfK~sqnH9Rv97DBi9nnPo z4!?3TJ~!2m{h4-A^~y7Tdu(Vd{*1=lCDq|Vzq2YiS}NA6j$a3#7XB3N`%WZovh&Ay ziNrS^gQmw({8-n2xo5P$s)4UnGH>(xf9kvTAH3(R{$HQ|T+b9$FvXpaKOeSmybh;Z z`R}R;uHzupg;aizDgI3h_&ur{Z^5|7@Dt!SFzzF~9_99zQ~ti{reT)oh+5(QwZcbe zty;>?HPP00#9xfAJn;&C{6lx7ALf*>kR244dLZ9{tQh{{5T&D=_xKld&-As|27@f4_zHN zI`}=CF&1e$X|QxcgQnBG$j6P2CkN|!RurUIVHx1Y>)}q*f1=7lT19gStcCXET2d1mM>!V$fA=W zhCYRAsm!83uQ_5=OJxq)ICJUOe@)+g0sZv(+K)%K>FZ?r@#NlGemVjTA#}{p5L#j6 z6(WBR{y)ugrT(myXbr8#*3yUHPQP696z!(}9e@VmHgaujZ$^@fL-TQ{hT?bdN@D42YrUvCa~co2fUjFM3zN_(RBiKQ zG>EUFJE}UT>Oa1QsTRBDC%AreE*RnJ}f-x<_{8n4KrAFkSu4~X-q{&BI+sn(ww z>i{@s=ppkM{=e{^{dX^eQ5(F~6)QCrbe{LJc}thf{G>`5^UBS?_Sv?@ zZ{3!yD`$UbYi?rKu>Ynt+js2pU$ee@?TV#L?Hly-UG2X!z<<@+F10F@GOP5_mrK6c z?6+?7rp2E(vNEx#=00P=q6I5f%$YxH`hXf14&6qL9Y58pQ>QN`jvY6;t3$IvLxv9S q;ZnVNliovy4jI_EE?v*oPUQgQ)oIht&FupNkyZpt!>9OPKK&1omed&l literal 0 HcmV?d00001 diff --git a/Sandbox/src/SandboxApp.bf b/Sandbox/src/SandboxApp.bf index ad63efb..ffb5124 100644 --- a/Sandbox/src/SandboxApp.bf +++ b/Sandbox/src/SandboxApp.bf @@ -4,6 +4,7 @@ using GlitchyEngine.Events; using System.Diagnostics; using GlitchLog; using GlitchyEngine.ImGui; +using ImGui; namespace Sandbox { @@ -29,9 +30,9 @@ namespace Sandbox private bool OnImGuiRender(ImGuiRenderEvent e) { - ImGui.ImGui.Begin("Test"); + ImGui.Begin("Test"); - ImGui.ImGui.End(); + ImGui.End(); return false; }