using System; using System.Numerics; using System.Runtime.InteropServices; using System.Text; using ImGuiNET; using OpenTK.Windowing.GraphicsLibraryFramework; using Veldrid; using Veldrid.OpenGL; using Veldrid.SPIRV; using Veldrid.Vk; namespace Pow3r { internal sealed unsafe partial class Program { private const string VDVertexShader = @" #version 460 layout (location = 0) in vec2 Position; layout (location = 1) in vec2 UV; layout (location = 2) in vec4 Color; layout (set = 0, binding = 0) uniform ProjMtx { mat4 _ProjMtx; }; layout (location = 0) out vec2 Frag_UV; layout (location = 1) out vec4 Frag_Color; // Converts a color from sRGB gamma to linear light gamma vec4 toLinear(vec4 sRGB) { bvec3 cutoff = lessThan(sRGB.rgb, vec3(0.04045)); vec3 higher = pow((sRGB.rgb + vec3(0.055))/vec3(1.055), vec3(2.4)); vec3 lower = sRGB.rgb/vec3(12.92); return vec4(mix(higher, lower, cutoff), sRGB.a); } void main() { Frag_UV = UV; Frag_Color = toLinear(Color); gl_Position = _ProjMtx * vec4(Position.xy,0,1); }"; private const string VDFragmentShader = @" #version 460 layout (location = 0) in vec2 Frag_UV; layout (location = 1) in vec4 Frag_Color; layout (set = 1, binding = 0) uniform texture2D Texture; layout (set = 1, binding = 1) uniform sampler TextureSampler; layout (location = 0) out vec4 Out_Color; void main() { Out_Color = Frag_Color * texture(sampler2D(Texture, TextureSampler), Frag_UV.st); }"; private VeldridRenderer _vdRenderer = VeldridRenderer.Vulkan; private GraphicsDevice _vdGfxDevice; private CommandList _vdCommandList; private Pipeline _vdPipeline; private Shader[] _vdShaders; private ResourceSet _vdSetTexture; private ResourceSet _vdSetProjMatrix; private Texture _vdTexture; private Sampler _vdSampler; private DeviceBuffer _vdProjMatrixUniformBuffer; private int _vdLastWidth; private int _vdLastHeight; private VdFencedDatum[] _fencedData = Array.Empty(); private void InitVeldrid() { var options = new GraphicsDeviceOptions { #if DEBUG Debug = true, #endif HasMainSwapchain = true, SyncToVerticalBlank = _vsync, PreferStandardClipSpaceYDirection = true, SwapchainSrgbFormat = true }; GLFW.GetFramebufferSize(_window.WindowPtr, out var w, out var h); var hwnd = GLFW.GetWin32Window(_window.WindowPtr); var hinstance = GetModuleHandleA(null); switch (_vdRenderer) { case VeldridRenderer.Vulkan: _vdGfxDevice = GraphicsDevice.CreateVulkan( options, VkSurfaceSource.CreateWin32((nint) hinstance, hwnd), (uint) w, (uint) h); break; case VeldridRenderer.D3D11: _vdGfxDevice = GraphicsDevice.CreateD3D11(options, hwnd, (uint) w, (uint) h); break; case VeldridRenderer.OpenGL: { var platInfo = new OpenGLPlatformInfo( (nint) _window.WindowPtr, GLFW.GetProcAddress, ptr => GLFW.MakeContextCurrent((Window*) ptr), () => (nint) GLFW.GetCurrentContext(), () => GLFW.MakeContextCurrent(null), ptr => GLFW.DestroyWindow((Window*) ptr), () => GLFW.SwapBuffers(_window.WindowPtr), vsync => GLFW.SwapInterval(vsync ? 1 : 0)); _vdGfxDevice = GraphicsDevice.CreateOpenGL(options, platInfo, (uint) w, (uint) h); break; } } var factory = _vdGfxDevice.ResourceFactory; _vdCommandList = factory.CreateCommandList(); _vdCommandList.Name = "Honk"; var vtxLayout = new VertexLayoutDescription( new VertexElementDescription("Position", VertexElementFormat.Float2, VertexElementSemantic.TextureCoordinate), new VertexElementDescription("UV", VertexElementFormat.Float2, VertexElementSemantic.TextureCoordinate), new VertexElementDescription("Color", VertexElementFormat.Byte4_Norm, VertexElementSemantic.TextureCoordinate)); var vtxShaderDesc = new ShaderDescription( ShaderStages.Vertex, Encoding.UTF8.GetBytes(VDVertexShader), "main"); var fragShaderDesc = new ShaderDescription( ShaderStages.Fragment, Encoding.UTF8.GetBytes(VDFragmentShader), "main"); _vdShaders = factory.CreateFromSpirv(vtxShaderDesc, fragShaderDesc); _vdShaders[0].Name = "VertexShader"; _vdShaders[1].Name = "FragmentShader"; var layoutTexture = factory.CreateResourceLayout(new ResourceLayoutDescription( new ResourceLayoutElementDescription( "Texture", ResourceKind.TextureReadOnly, ShaderStages.Fragment), new ResourceLayoutElementDescription( "TextureSampler", ResourceKind.Sampler, ShaderStages.Fragment))); layoutTexture.Name = "LayoutTexture"; var layoutProjMatrix = factory.CreateResourceLayout(new ResourceLayoutDescription( new ResourceLayoutElementDescription( "ProjMtx", ResourceKind.UniformBuffer, ShaderStages.Vertex))); layoutProjMatrix.Name = "LayoutProjMatrix"; var pipelineDesc = new GraphicsPipelineDescription( new BlendStateDescription( RgbaFloat.White, new BlendAttachmentDescription( true, BlendFactor.SourceAlpha, BlendFactor.InverseSourceAlpha, BlendFunction.Add, BlendFactor.One, BlendFactor.InverseSourceAlpha, BlendFunction.Add) ), DepthStencilStateDescription.Disabled, new RasterizerStateDescription( FaceCullMode.None, PolygonFillMode.Solid, FrontFace.Clockwise, depthClipEnabled: false, scissorTestEnabled: true), PrimitiveTopology.TriangleList, new ShaderSetDescription(new[] {vtxLayout}, _vdShaders), new[] {layoutProjMatrix, layoutTexture}, new OutputDescription( null, new OutputAttachmentDescription(PixelFormat.B8_G8_R8_A8_UNorm_SRgb)) ); _vdPipeline = factory.CreateGraphicsPipeline(pipelineDesc); _vdPipeline.Name = "MainPipeline"; _vdProjMatrixUniformBuffer = factory.CreateBuffer(new BufferDescription( (uint) sizeof(Matrix4x4), BufferUsage.Dynamic | BufferUsage.UniformBuffer)); _vdProjMatrixUniformBuffer.Name = "_vdProjMatrixUniformBuffer"; _vdSetProjMatrix = factory.CreateResourceSet(new ResourceSetDescription( layoutProjMatrix, _vdProjMatrixUniformBuffer)); _vdSetProjMatrix.Name = "_vdSetProjMatrix"; var io = ImGui.GetIO(); io.Fonts.GetTexDataAsRGBA32(out byte* pixels, out var width, out var height, out _); _vdTexture = factory.CreateTexture(TextureDescription.Texture2D( (uint) width, (uint) height, mipLevels: 1, arrayLayers: 1, PixelFormat.R8_G8_B8_A8_UNorm_SRgb, TextureUsage.Sampled)); _vdTexture.Name = "MainTexture"; _vdSampler = factory.CreateSampler(SamplerDescription.Linear); _vdSampler.Name = "MainSampler"; _vdGfxDevice.UpdateTexture( _vdTexture, (IntPtr) pixels, (uint) (width * height * 4), x: 0, y: 0, z: 0, (uint) width, (uint) height, depth: 1, mipLevel: 0, arrayLayer: 0); _vdSetTexture = factory.CreateResourceSet(new ResourceSetDescription( layoutTexture, _vdTexture, _vdSampler)); _vdSetTexture.Name = "SetTexture"; io.Fonts.SetTexID((nint) 0); io.Fonts.ClearTexData(); _vdGfxDevice.ResizeMainWindow((uint) w, (uint) h); _vdGfxDevice.SwapBuffers(); } private void RenderVeldrid() { GLFW.GetFramebufferSize(_window.WindowPtr, out var fbW, out var fbH); if (_vdLastWidth != fbW && _vdLastHeight != fbH) { _vdGfxDevice.ResizeMainWindow((uint) fbW, (uint) fbH); _vdLastWidth = fbW; _vdLastHeight = fbH; } _vdCommandList.Begin(); _vdCommandList.SetFramebuffer(_vdGfxDevice.SwapchainFramebuffer); _vdCommandList.SetViewport(0, new Viewport(0, 0, fbW, fbH, 0, 1)); _vdCommandList.ClearColorTarget(0, RgbaFloat.Black); var factory = _vdGfxDevice.ResourceFactory; var drawData = ImGui.GetDrawData(); ref var fencedData = ref GetFreeFencedData(); ref var vtxBuf = ref fencedData.VertexBuffer; ref var idxBuf = ref fencedData.IndexBuffer; var byteLenVtx = (uint) (sizeof(ImDrawVert) * drawData.TotalVtxCount); if (fencedData.VertexBuffer == null || vtxBuf.SizeInBytes < byteLenVtx) { vtxBuf?.Dispose(); vtxBuf = factory.CreateBuffer(new BufferDescription( byteLenVtx, BufferUsage.VertexBuffer | BufferUsage.Dynamic)); vtxBuf.Name = "_vdVtxBuffer"; } var byteLenIdx = (uint) (sizeof(ushort) * drawData.TotalIdxCount); if (idxBuf == null || idxBuf.SizeInBytes < byteLenIdx) { idxBuf?.Dispose(); idxBuf = factory.CreateBuffer(new BufferDescription( byteLenIdx, BufferUsage.IndexBuffer | BufferUsage.Dynamic)); idxBuf.Name = "_vdIdxBuffer"; } var vtxOffset = 0; var idxOffset = 0; var mappedVtxBuf = MappedToSpan(_vdGfxDevice.Map(vtxBuf, MapMode.Write)); var mappedIdxBuf = MappedToSpan(_vdGfxDevice.Map(idxBuf, MapMode.Write)); var l = drawData.DisplayPos.X; var r = drawData.DisplayPos.X + drawData.DisplaySize.X; var t = drawData.DisplayPos.Y; var b = drawData.DisplayPos.Y + drawData.DisplaySize.Y; var matrix = Matrix4x4.CreateOrthographicOffCenter(l, r, b, t, -1, 1); var clipOff = drawData.DisplayPos; var clipScale = drawData.FramebufferScale; _vdCommandList.UpdateBuffer(_vdProjMatrixUniformBuffer, 0, ref matrix); _vdCommandList.SetPipeline(_vdPipeline); _vdCommandList.SetGraphicsResourceSet(0, _vdSetProjMatrix); _vdCommandList.SetGraphicsResourceSet(1, _vdSetTexture); _vdCommandList.SetVertexBuffer(0, vtxBuf); _vdCommandList.SetIndexBuffer(idxBuf, IndexFormat.UInt16); for (var n = 0; n < drawData.CmdListsCount; n++) { var drawList = drawData.CmdListsRange[n]; var drawVtx = new Span((void*) drawList.VtxBuffer.Data, drawList.VtxBuffer.Size); var drawIdx = new Span((void*) drawList.IdxBuffer.Data, drawList.IdxBuffer.Size); drawVtx.CopyTo(mappedVtxBuf[vtxOffset..]); drawIdx.CopyTo(mappedIdxBuf[idxOffset..]); for (var cmdI = 0; cmdI < drawList.CmdBuffer.Size; cmdI++) { var cmd = drawList.CmdBuffer[cmdI]; Vector4 clipRect = default; clipRect.X = (cmd.ClipRect.X - clipOff.X) * clipScale.X; clipRect.Y = (cmd.ClipRect.Y - clipOff.Y) * clipScale.Y; clipRect.Z = (cmd.ClipRect.Z - clipOff.X) * clipScale.X; clipRect.W = (cmd.ClipRect.W - clipOff.Y) * clipScale.Y; _vdCommandList.SetScissorRect( 0, (uint) clipRect.X, (uint) clipRect.Y, (uint) (clipRect.Z - clipRect.X), (uint) (clipRect.W - clipRect.Y)); _vdCommandList.DrawIndexed( cmd.ElemCount, 1, (uint) (cmd.IdxOffset + idxOffset), (int) (cmd.VtxOffset + vtxOffset), 0); } vtxOffset += drawVtx.Length; idxOffset += drawIdx.Length; } _vdGfxDevice.Unmap(vtxBuf); _vdGfxDevice.Unmap(idxBuf); _vdCommandList.End(); _vdGfxDevice.SubmitCommands(_vdCommandList, fencedData.Fence); _vdGfxDevice.SwapBuffers(); } private ref VdFencedDatum GetFreeFencedData() { for (var i = 0; i < _fencedData.Length; i++) { ref var fenced = ref _fencedData[i]; if (fenced.Fence.Signaled) { fenced.Fence.Reset(); return ref fenced; } } Array.Resize(ref _fencedData, _fencedData.Length + 1); ref var slot = ref _fencedData[^1]; slot = new VdFencedDatum {Fence = _vdGfxDevice.ResourceFactory.CreateFence(false)}; return ref slot; } private static Span MappedToSpan(MappedResourceView mapped) where T : struct { return MemoryMarshal.CreateSpan(ref mapped[0], mapped.Count); } [DllImport("kernel32.dll")] private static extern void* GetModuleHandleA(byte* lpModuleName); private struct VdFencedDatum { public Fence Fence; public DeviceBuffer IndexBuffer; public DeviceBuffer VertexBuffer; } private enum VeldridRenderer { Vulkan, D3D11, OpenGL } } }