stymee / SilkVulkanTutorial

C# port of Vulkan Game Engine Tutorial
MIT License
56 stars 5 forks source link

textures #7

Open Samma2009 opened 3 months ago

Samma2009 commented 3 months ago

I need some help implementing textures, i tried porting the texture system of https://github.com/JacobYealy/LittleVulkanEngine from c++ to c# but I get a memory corruption error when vk.CmdDrawIndexed(commandBuffer, indexCount, 1, 0, 0, 0); is called

Samma2009 commented 3 months ago

here is code:

using Lve;
using Silk.NET.Vulkan;

namespace Sandbox02ImGui;

public partial class FirstApp : IDisposable
{
    // set to true to force FIFO swapping
    private const bool USE_FIFO = false;
        // Window stuff
    private IView window = null!;
    private int width = 1280;
    private int height = 720;
    private string windowName = "Falkon Engine 2";
    private long fpsUpdateInterval = 5 * 10_000;
    private long fpsLastUpdate;
    private string BuildVersion;

    // Vk api
    private readonly Vk vk = null!;

    // ImGui
    private ImGuiController imGuiController = null!;

    private LveDevice device = null!;
    private LveRenderer lveRenderer = null!;
    private LveDescriptorPool globalPool = null!;

    private Dictionary<uint, LveGameObject> gameObjects = new();
    private Dictionary<uint, LveGameObject> BackupObjects = new();
    int SelectedIndex = -1;

    private ICamera camera = null!;

    private SimpleRenderSystem simpleRenderSystem = null!;
    private PointLightRenderSystem pointLightRenderSystem = null!;

    private CameraController cameraController = null!;
    private KeyboardController keyboardController = null!;

    private GlobalUbo[] ubos = null!;
    private LveBuffer[] uboBuffers = null!;

    private LveDescriptorSetLayout globalSetLayout = null!;
    private DescriptorSet[] globalDescriptorSets = null!;
    LveImage img;

    public FirstApp()
    {

        BuildVersion = "V"+DateTime.Today.Day + DateTime.Today.Month + DateTime.Today.Year + DateTime.Now.Hour;

        log.RestartTimer();
        log.d("startup", "starting up");

        vk = Vk.GetApi();
        log.d("startup", "got vk");

        initWindow();
        log.d("startup", "got window");

        device = new LveDevice(vk, window);
        log.d("startup", "got device");

        lveRenderer = new LveRenderer(vk, window, device, useFifo: USE_FIFO);
        log.d("startup", "got renderer");

        globalPool = new LveDescriptorPool.Builder(vk, device)
            .SetMaxSets((uint)LveSwapChain.MAX_FRAMES_IN_FLIGHT)
            .AddPoolSize(DescriptorType.UniformBuffer, (uint)LveSwapChain.MAX_FRAMES_IN_FLIGHT)
            .AddPoolSize(DescriptorType.CombinedImageSampler, 5 * (uint)LveSwapChain.MAX_FRAMES_IN_FLIGHT)
            .Build();
        log.d("startup", "global descriptor pool created");

        loadGameObjects();
        log.d("startup", "objects loaded");

        imGuiController = new ImGuiController(
                vk,
                window,
                window.CreateInput(),
                device.VkPhysicalDevice,
                device.GraphicsFamilyIndex,
                LveSwapChain.MAX_FRAMES_IN_FLIGHT,
                lveRenderer.SwapChainImageFormat,
                lveRenderer.SwapChainDepthFormat,
                device.GetMsaaSamples()
            );
        log.d("startup", "imgui loaded");
        CreateTextureSampler();

        img = LveImage.CreateImageFromFile(vk,device, "galaxy.jpg");

    }
    private IntPtr imguiTextureId;
    private Sampler textureSampler;

    private unsafe void CreateTextureSampler()
    {
        SamplerCreateInfo samplerInfo = new()
        {
            SType = StructureType.SamplerCreateInfo,
            MagFilter = Filter.Linear,
            MinFilter = Filter.Linear,
            AddressModeU = SamplerAddressMode.Repeat,
            AddressModeV = SamplerAddressMode.Repeat,
            AddressModeW = SamplerAddressMode.Repeat,
            AnisotropyEnable = Vk.True,
            MaxAnisotropy = 16,
            BorderColor = BorderColor.IntOpaqueBlack,
            UnnormalizedCoordinates = Vk.False,
            CompareEnable = Vk.False,
            CompareOp = CompareOp.Always,
            MipmapMode = SamplerMipmapMode.Linear,
            MipLodBias = 0.0f,
            MinLod = 0.0f,
            MaxLod = 0.0f
        };

        if (vk.CreateSampler(device.VkDevice, &samplerInfo, null, out textureSampler) != Result.Success)
        {
            throw new Exception("failed to create texture sampler!");
        }
    }

    private unsafe void CreateImGuiTexture(Vk vk, LveDevice device, ImageView imageView)
    {
        var playout = globalSetLayout.GetDescriptorSetLayout();
        // Create a descriptor set for the texture
        DescriptorSetAllocateInfo allocInfo = new()
        {
            SType = StructureType.DescriptorSetAllocateInfo,
            DescriptorPool = globalPool.GetDescriptorPool(),
            DescriptorSetCount = 1,
            PSetLayouts = &playout
        };

        DescriptorSet descriptorSet;
        if (vk.AllocateDescriptorSets(device.VkDevice, &allocInfo, &descriptorSet) != Result.Success)
        {
            throw new Exception("failed to allocate descriptor set!");
        }

        DescriptorImageInfo imageInfo = new()
        {
            ImageLayout = ImageLayout.ShaderReadOnlyOptimal,
            ImageView = imageView,
            Sampler = textureSampler
        };

        WriteDescriptorSet descriptorWrite = new()
        {
            SType = StructureType.WriteDescriptorSet,
            DstSet = descriptorSet,
            DstBinding = 0,
            DstArrayElement = 0,
            DescriptorType = DescriptorType.CombinedImageSampler,
            DescriptorCount = 1,
            PImageInfo = &imageInfo
        };

        vk.UpdateDescriptorSets(device.VkDevice, 1, &descriptorWrite, 0, null);

        // Store the descriptor set handle as an IntPtr
        imguiTextureId = new IntPtr((int)descriptorSet.Handle);
    }

    public void Run()
    {
        int frames = LveSwapChain.MAX_FRAMES_IN_FLIGHT;
        ubos = new GlobalUbo[frames];
        uboBuffers = new LveBuffer[frames];
        for (int i = 0; i < frames; i++)
        {
            ubos[i] = new GlobalUbo();
            uboBuffers[i] = new(
                vk, device,
                GlobalUbo.SizeOf(),
                1,
                BufferUsageFlags.UniformBufferBit,
                MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit
                );
            uboBuffers[i].Map();
        }
        log.d("run", "initialized ubo buffers");

        globalSetLayout = new LveDescriptorSetLayout.Builder(vk, device)
            .AddBinding(0, DescriptorType.UniformBuffer, ShaderStageFlags.AllGraphics)
            .AddBinding(1, DescriptorType.CombinedImageSampler, ShaderStageFlags.ShaderStageFragmentBit)
            .Build();

        globalDescriptorSets = new DescriptorSet[frames];
        for (var i = 0; i < globalDescriptorSets.Length; i++)
        {
            var bufferInfo = uboBuffers[i].DescriptorInfo();
            var imageInfo1 = img.DescriptorImageInfo();
            _ = new LveDescriptorSetWriter(vk, device, globalSetLayout)
                .WriteBuffer(0, bufferInfo)
                .WriteImage(1,imageInfo1)
                .Build(
                    globalPool,
                    globalSetLayout.GetDescriptorSetLayout(), ref globalDescriptorSets[i]
                    );
        }
        log.d("run", "got globalDescriptorSets");

        simpleRenderSystem = new(
            vk, device,
            lveRenderer.GetSwapChainRenderPass(),
            globalSetLayout.GetDescriptorSetLayout()
            );

        pointLightRenderSystem = new(
            vk, device,
            lveRenderer.GetSwapChainRenderPass(),
            globalSetLayout.GetDescriptorSetLayout()
            );
        log.d("run", "got render systems");

        camera = new PerspectiveCamera(new(0,.5f,.5f), 60, 0,0, window.FramebufferSize);
        //camera = new PerspectiveCamera(new Vector3(5,5,5), 45f, 0f, 0f, window.FramebufferSize);
        keyboardController = new((IWindow)window);
        keyboardController.OnKeyPressed += onKeyPressed;
        cameraController = new(camera,keyboardController, (IWindow)window);
        resize(window.FramebufferSize);
        log.d("run", "got camera and controls");

        //Console.WriteLine($"GlobalUbo blittable={BlittableHelper<GlobalUbo>.IsBlittable}");
        FirstAppGuiInit();

        MainLoop();
    }

    private void onKeyPressed(Key key)
    {
        switch (key)
        {
            case Key.Space:
                pointLightRenderSystem.RotateLightsEnabled = !pointLightRenderSystem.RotateLightsEnabled;
                break;
            case Key.KeypadAdd:
                pointLightRenderSystem.RotateSpeed += 0.5f;
                break;
            case Key.KeypadSubtract:
                pointLightRenderSystem.RotateSpeed -= 0.5f;
                break;
            default:
                break;
        }
    }

    // mouse stuff
    private MouseState mouseLast;

    private void render(double delta)
    {
        imGuiController.Update((float)delta);
        cameraController.MoveCamera(delta);
        try
        {
            FirstAppGuiUpdate(imguiTextureId);
        }
        catch { }
        if (Playing)
        {
            foreach (var item in gameObjects)
                foreach (var item1 in item.Value.Componenets) item1.Update(delta);
        }
        //ImGui.ShowDemoWindow();

        mouseLast = cameraController.GetMouseState();

        var commandBuffer = lveRenderer.BeginFrame();
        int frameIndex = lveRenderer.GetFrameIndex();

        if (commandBuffer is not null)
        {
            FrameInfo frameInfo = new()
            {
                FrameIndex = frameIndex,
                FrameTime = (float)delta,
                CommandBuffer = commandBuffer.Value,
                Camera = camera,
                GlobalDescriptorSet = globalDescriptorSets[frameIndex],
                GameObjects = gameObjects
            };

            pointLightRenderSystem.Update(frameInfo, ref ubos[frameIndex]);

            ubos[frameIndex].Update(camera.GetProjectionMatrix(), camera.GetViewMatrix(), camera.GetFrontVec4());
            uboBuffers[frameIndex].WriteBytesToBuffer(ubos[frameIndex].AsBytes());

            lveRenderer.BeginSwapChainRenderPass(commandBuffer.Value);

            // render solid objects first!
            simpleRenderSystem.Render(frameInfo);

            pointLightRenderSystem.Render(frameInfo);

            imGuiController.Render(commandBuffer.Value, lveRenderer.SwapChain.GetFrameBufferAt((uint)frameIndex), lveRenderer.SwapChain.GetSwapChainExtent());

            lveRenderer.EndSwapChainRenderPass(commandBuffer.Value);

            lveRenderer.EndFrame();

        }
    }

    private void MainLoop()
    {
        window.Run();

        vk.DeviceWaitIdle(device.VkDevice);
    }

    private void initWindow()
    {
        var options = WindowOptions.DefaultVulkan with
        {
            Size = new Vector2D<int>(width, height),
            Title = windowName
        };

        window = Window.Create(options);
        window.Initialize();

        if (window.VkSurface is null)
        {
            throw new Exception("Windowing platform doesn't support Vulkan.");
        }

        fpsLastUpdate = DateTime.Now.Ticks;

        window.FramebufferResize += resize;
        window.Update += updateWindow;
        window.Render += render;
    }

    private void updateWindow(double frametime)
    {

        if (DateTime.Now.Ticks - fpsLastUpdate < fpsUpdateInterval) return;

        fpsLastUpdate = DateTime.Now.Ticks;
        if (window is IWindow w)
        {
            //w.Title = $"{windowName} | W {window.Size.X}x{window.Size.Y} | FPS {Math.Ceiling(1d / obj)} | ";
            w.Title = windowName + " " + BuildVersion;
        }

    }

    private void resize(Vector2D<int> newsize)
    {
        camera.Resize(0, 0, (uint)newsize.X, (uint)newsize.Y);
        cameraController.Resize(newsize);
        FirstAppGuiResize(0, 0, (uint)newsize.X, (uint)newsize.Y, newsize);
    }

    private void loadGameObjects()
    {
        var flatVase = LveGameObject.CreateGameObject();
        flatVase.Model = ModelUtils.LoadModelFromFile(vk, device, "Assets/flat_vase.obj");
        flatVase.Name = "FlatVase";
        flatVase.Transform.Translation = new(-.5f, 0, 0.0f);
        flatVase.Transform.Scale = new(3.0f, 1.5f, 3.0f);
        gameObjects.Add(flatVase.Id, flatVase);

        var smoothVase = LveGameObject.CreateGameObject();
        smoothVase.Model = ModelUtils.LoadModelFromFile(vk, device, "Assets/smooth_vase.obj");
        smoothVase.Name = "SoothVase";
        smoothVase.Transform.Translation = new(.5f, 0, 0.0f);
        smoothVase.Transform.Scale = new(3.0f, 1.5f, 3.0f);
        gameObjects.Add(smoothVase.Id, smoothVase);

        var floor = LveGameObject.CreateGameObject();
        floor.Model = ModelUtils.LoadModelFromFile(vk, device, "Assets/quad.obj");
        floor.Name = "Floor";
        floor.Transform.Translation = new(0f, 0, 0f);
        floor.Transform.Scale = new(3f, 1f, 3f);
        gameObjects.Add(floor.Id, floor);

        var lightColors = new Vector4[]
        {
          new(1f, .1f, .1f, 1f),
          new(.1f, .1f, 1f, 1f),
          new(.1f, 1f, .1f, 1f),
          new(1f, 1f, .1f, 1f),
          new(.1f, 1f, 1f, 1f),
          new(1f, 1f, 1f, 1f)
        };
        for (var i = 0; i < 6; i++)
        {
            var pointLight = LveGameObject.MakePointLight(
                0.2f, 0.05f, lightColors[i]
                );
            var rotateLight = Matrix4x4.CreateRotationY(i * MathF.PI / lightColors.Length * 2f);
            pointLight.Transform.Translation = Vector3.Transform(new(1.25f, 1.25f, 0f), rotateLight);
            pointLight.Name = "Light" + i;
            gameObjects.Add(pointLight.Id, pointLight);
        }
    }
    public unsafe void Dispose()
    {
        window.Dispose();
        lveRenderer.Dispose();
        simpleRenderSystem.Dispose();
        pointLightRenderSystem.Dispose();
        imGuiController.Dispose();
        device.Dispose();

        GC.SuppressFinalize(this);
    }
}
using Sandbox02ImGui;
using Silk.NET.Vulkan;
using StbSharp;
using System;
using System.IO;
using System.Runtime.InteropServices;

namespace Lve
{

    public unsafe class LveImage : IDisposable
    {
        private LveDevice lveDevice;
        private uint width, height, mipLevels, arrayLayers = 1;
        private Image image;
        private DeviceMemory imageMemory;
        private ImageView imageView;
        private Sampler textureSampler;
        Vk vk;

        public LveImage(Vk vk,LveDevice device, uint w, uint h, LveBuffer imageBuffer)
        {
            lveDevice = device;
            width = w;
            height = h;
            mipLevels = (uint)Math.Floor(Math.Log2(Math.Max(width, height))) + 1;
            this.vk = vk;
            imageMemory = new();
            image = new();
            imageView = new();
            textureSampler = new();

            CreateImage(Format.R8G8B8A8Srgb, ImageTiling.Optimal,
                ImageUsageFlags.ImageUsageTransferSrcBit | ImageUsageFlags.ImageUsageTransferDstBit |
                ImageUsageFlags.ImageUsageSampledBit, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit);

            TransitionImageLayout(Format.R8G8B8A8Srgb, ImageLayout.Undefined, ImageLayout.TransferDstOptimal);
            lveDevice.CopyBufferToImage(imageBuffer.VkBuffer, image, width, height, arrayLayers);
            GenerateMipmaps();
            CreateImageView(Format.R8G8B8A8Srgb);
            CreateTextureSampler();
        }

        public void Dispose()
        {
            vk.DestroySampler(lveDevice.VkDevice, textureSampler, null);
            vk.DestroyImageView(lveDevice.VkDevice, imageView, null);
            vk.DestroyImage(lveDevice.VkDevice, image, null);
            vk.FreeMemory(lveDevice.VkDevice, imageMemory, null);
        }

        public DescriptorImageInfo DescriptorImageInfo()
        {
            return new DescriptorImageInfo
            {
                Sampler = textureSampler,
                ImageView = imageView,
                ImageLayout = ImageLayout.ShaderReadOnlyOptimal,
            };
        }

        private void CreateImage(Format format, ImageTiling tiling, ImageUsageFlags usage, MemoryPropertyFlags properties)
        {
            ImageCreateInfo imageInfo = new ImageCreateInfo
            {
                SType = StructureType.ImageCreateInfo,
                ImageType = ImageType.ImageType2D,
                Extent = new Extent3D(width, height, 1),
                MipLevels = mipLevels,
                ArrayLayers = arrayLayers,
                Format = format,
                Tiling = tiling,
                InitialLayout = ImageLayout.Undefined,
                Usage = usage,
                Samples = SampleCountFlags.SampleCount1Bit,
                SharingMode = SharingMode.Exclusive,
            };

            lveDevice.CreateImageWithInfo(imageInfo, properties, out image, out imageMemory);
        }

        public static LveImage CreateImageFromFile(Vk vk,LveDevice lveDevice, string filepath)
        {
            int texWidth, texHeight, texChannels;
            byte[] data = File.ReadAllBytes(filepath);
            var img = StbImage.LoadFromMemory(data);
            var pixels = img.Data;
            texWidth = img.Width;
            texHeight = img.Height;
            uint pixelCount = (uint)(texWidth * texHeight);
            uint pixelSize = (uint)Marshal.SizeOf<uint>();

            if (pixels.Length == 0)
            {
                throw new Exception("failed to load texture image!");
            }

            var imgBuffer = new LveBuffer(vk,lveDevice, pixelSize, pixelCount, BufferUsageFlags.BufferUsageTransferSrcBit,
                MemoryPropertyFlags.MemoryPropertyHostVisibleBit | MemoryPropertyFlags.MemoryPropertyHostCoherentBit);

            imgBuffer.Map();
            imgBuffer.WriteToBuffer(pixels);

            return new LveImage(vk,lveDevice, (uint)texWidth, (uint)texHeight, imgBuffer);
        }

        private unsafe void TransitionImageLayout(Format format, ImageLayout oldLayout, ImageLayout newLayout)
        {
            CommandBuffer commandBuffer = lveDevice.BeginSingleTimeCommands();

            ImageMemoryBarrier barrier = new ImageMemoryBarrier
            {
                SType = StructureType.ImageMemoryBarrier,
                OldLayout = oldLayout,
                NewLayout = newLayout,
                SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
                DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
                Image = image,
                SubresourceRange = new ImageSubresourceRange
                {
                    AspectMask = ImageAspectFlags.ImageAspectColorBit,
                    BaseMipLevel = 0,
                    LevelCount = mipLevels,
                    BaseArrayLayer = 0,
                    LayerCount = 1,
                },
            };

            PipelineStageFlags sourceStage;
            PipelineStageFlags destinationStage;

            if (oldLayout == ImageLayout.Undefined && newLayout == ImageLayout.TransferDstOptimal)
            {
                barrier.SrcAccessMask = 0;
                barrier.DstAccessMask = AccessFlags.AccessTransferWriteBit;

                sourceStage = PipelineStageFlags.PipelineStageTopOfPipeBit;
                destinationStage = PipelineStageFlags.PipelineStageTransferBit;
            }
            else if (oldLayout == ImageLayout.TransferDstOptimal && newLayout == ImageLayout.ShaderReadOnlyOptimal)
            {
                barrier.SrcAccessMask = AccessFlags.AccessTransferWriteBit;
                barrier.DstAccessMask = AccessFlags.AccessShaderReadBit;

                sourceStage = PipelineStageFlags.PipelineStageTransferBit;
                destinationStage = PipelineStageFlags.PipelineStageFragmentShaderBit;
            }
            else
            {
                throw new ArgumentException("unsupported layout transition!");
            }

            vk.CmdPipelineBarrier(
                commandBuffer,
                sourceStage, destinationStage,
                0,
                0, null,
                0, null,
                1, in barrier
            );

            lveDevice.EndSingleTimeCommands(commandBuffer);
        }

        private void CreateImageView(Format format)
        {
            ImageViewCreateInfo viewInfo = new ImageViewCreateInfo
            {
                SType = StructureType.ImageViewCreateInfo,
                Image = image,
                ViewType = ImageViewType.ImageViewType2D,
                Format = format,
                SubresourceRange = new ImageSubresourceRange
                {
                    AspectMask = ImageAspectFlags.ImageAspectColorBit,
                    BaseMipLevel = 0,
                    LevelCount = mipLevels,
                    BaseArrayLayer = 0,
                    LayerCount = 1,
                }
            };

            if (vk.CreateImageView(lveDevice.VkDevice, in viewInfo, null, out imageView) != Result.Success)
            {
                throw new Exception("failed to create texture image view!");
            }
        }

        private void CreateTextureSampler()
        {
            SamplerCreateInfo samplerInfo = new SamplerCreateInfo
            {
                SType = StructureType.SamplerCreateInfo,
                MagFilter = Filter.Linear,
                MinFilter = Filter.Linear,
                AddressModeU = SamplerAddressMode.Repeat,
                AddressModeV = SamplerAddressMode.Repeat,
                AddressModeW = SamplerAddressMode.Repeat,
                AnisotropyEnable = Vk.True,
                MaxAnisotropy = lveDevice.GetProperties().Limits.MaxSamplerAnisotropy,
                BorderColor = BorderColor.IntOpaqueBlack,
                UnnormalizedCoordinates = Vk.False,
                CompareEnable = Vk.False,
                CompareOp = CompareOp.Always,
                MipmapMode = SamplerMipmapMode.Linear,
            };

            if (vk.CreateSampler(lveDevice.VkDevice, in samplerInfo, null, out textureSampler) != Result.Success)
            {
                throw new Exception("failed to create texture sampler!");
            }
        }

        private void GenerateMipmaps()
        {
            PhysicalDevice physicalDevice = lveDevice.VkPhysicalDevice;
            Format imageFormat = Format.R8G8B8A8Srgb;
            vk.GetPhysicalDeviceFormatProperties(physicalDevice, imageFormat, out FormatProperties formatProperties);

            if ((formatProperties.OptimalTilingFeatures & FormatFeatureFlags.FormatFeatureSampledImageFilterLinearBit) == 0)
            {
                throw new Exception("texture image format does not support linear blitting!");
            }

            CommandBuffer commandBuffer = lveDevice.BeginSingleTimeCommands();

            ImageMemoryBarrier barrier = new ImageMemoryBarrier
            {
                SType = StructureType.ImageMemoryBarrier,
                Image = image,
                SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
                DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
                SubresourceRange = new ImageSubresourceRange
                {
                    AspectMask = ImageAspectFlags.ImageAspectColorBit,
                    BaseArrayLayer = 0,
                    LayerCount = 1,
                    LevelCount = 1,
                }
            };

            int mipWidth = (int)width;
            int mipHeight = (int)height;

            for (uint i = 1; i < mipLevels; i++)
            {
                barrier.SubresourceRange.BaseMipLevel = i - 1;
                barrier.OldLayout = ImageLayout.TransferDstOptimal;
                barrier.NewLayout = ImageLayout.TransferSrcOptimal;
                barrier.SrcAccessMask = AccessFlags.AccessTransferWriteBit;
                barrier.DstAccessMask = AccessFlags.AccessTransferReadBit;

                vk.CmdPipelineBarrier(commandBuffer, PipelineStageFlags.PipelineStageTransferBit,
                    PipelineStageFlags.PipelineStageTransferBit, 0, 0, null, 0, null, 1, in barrier);

                ImageBlit blit = new ImageBlit
                {
                    SrcOffsets = new ImageBlit.SrcOffsetsBuffer() { Element0 = new(0,0,0), Element1=new(mipWidth,mipHeight,1)},
                    SrcSubresource = new ImageSubresourceLayers
                    {
                        AspectMask = ImageAspectFlags.ImageAspectColorBit,
                        MipLevel = i - 1,
                        BaseArrayLayer = 0,
                        LayerCount = 1,
                    },
                    DstOffsets = new ImageBlit.DstOffsetsBuffer() { Element0=new(0, 0, 0), Element1=new(mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1) },
                    DstSubresource = new ImageSubresourceLayers
                    {
                        AspectMask = ImageAspectFlags.ImageAspectColorBit,
                        MipLevel = i,
                        BaseArrayLayer = 0,
                        LayerCount = 1,
                    }
                };

                vk.CmdBlitImage(commandBuffer, image, ImageLayout.TransferSrcOptimal, image, ImageLayout.TransferDstOptimal,
                    1, in blit, Filter.Linear);

                barrier.OldLayout = ImageLayout.TransferSrcOptimal;
                barrier.NewLayout = ImageLayout.ShaderReadOnlyOptimal;
                barrier.SrcAccessMask = AccessFlags.AccessTransferReadBit;
                barrier.DstAccessMask = AccessFlags.AccessShaderReadBit;

                vk.CmdPipelineBarrier(commandBuffer, PipelineStageFlags.PipelineStageTransferBit,
                    PipelineStageFlags.PipelineStageFragmentShaderBit, 0, 0, null, 0, null, 1, in barrier);

                if (mipWidth > 1) mipWidth /= 2;
                if (mipHeight > 1) mipHeight /= 2;
            }

            barrier.SubresourceRange.BaseMipLevel = mipLevels - 1;
            barrier.OldLayout = ImageLayout.TransferDstOptimal;
            barrier.NewLayout = ImageLayout.ShaderReadOnlyOptimal;
            barrier.SrcAccessMask = AccessFlags.AccessTransferWriteBit;
            barrier.DstAccessMask = AccessFlags.AccessShaderReadBit;

            vk.CmdPipelineBarrier(commandBuffer, PipelineStageFlags.PipelineStageTransferBit,
                PipelineStageFlags.PipelineStageFragmentShaderBit, 0, 0, null, 0, null, 1, in barrier);

            lveDevice.EndSingleTimeCommands(commandBuffer);
        }
    }
}
namespace Sandbox02ImGui;

public unsafe class LveDevice : IDisposable
{
    private readonly Vk vk = null!;
    private readonly IView window;

    public Device VkDevice => device;

    private ExtDebugUtils debugUtils = null!;
    private DebugUtilsMessengerEXT debugMessenger;

    private bool enableValidationLayers = true;
    private string[] validationLayers = { "VK_LAYER_KHRONOS_validation" };
    //private List<string> instanceExtensions = new() { ExtDebugUtils.ExtensionName };
    //private List<string> deviceExtensions = new() { KhrSwapchain.ExtensionName };

    private readonly string[] deviceExtensions = new[]
{
        KhrSwapchain.ExtensionName,
        KhrSynchronization2.ExtensionName
    };

    private Instance instance;
    public Instance Instance => instance;

    private KhrSurface khrSurface = null!;
    private SurfaceKHR surface;
    public SurfaceKHR Surface => surface;

    private PhysicalDevice physicalDevice;
    public PhysicalDevice VkPhysicalDevice => physicalDevice;

    private string deviceName = "unknown";
    public string DeviceName => deviceName;

    public PhysicalDeviceProperties GetProperties()
    {
        vk.GetPhysicalDeviceProperties(physicalDevice, out PhysicalDeviceProperties properties);
        return properties;
    }

    private SampleCountFlags msaaSamples = SampleCountFlags.Count1Bit;
    public SampleCountFlags GetMsaaSamples() => msaaSamples;
    private Device device;

    private uint graphicsFamilyIndex;
    public uint GraphicsFamilyIndex => graphicsFamilyIndex;
    private Queue graphicsQueue;
    public Queue GraphicsQueue => graphicsQueue;

    private Queue presentQueue;
    public Queue PresentQueue => presentQueue;

    private CommandPool commandPool;
    public CommandPool GetCommandPool() => commandPool;

    public LveDevice(Vk vk, IView window)
    {
        this.vk = vk;
        this.window = window;
        createInstance();
        setupDebugMessenger();
        createSurface();
        pickPhysicalDevice();
        createLogicalDevice();
        createCommandPool();
    }

    public void CopyBuffer(Buffer srcBuffer, Buffer dstBuffer, ulong size)
    {
        CommandBuffer commandBuffer = BeginSingleTimeCommands();

        BufferCopy copyRegion = new()
        {
            //SrcOffset = 0,
            //DstOffset = 0,
            Size = size,
        };

        vk.CmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, copyRegion);

        EndSingleTimeCommands(commandBuffer);
    }

    public void CreateBuffer(
        ulong size, 
        BufferUsageFlags usage, 
        MemoryPropertyFlags properties, 
        ref Buffer buffer, 
        ref DeviceMemory bufferMemory
        )
    {
        BufferCreateInfo bufferInfo = new()
        {
            SType = StructureType.BufferCreateInfo,
            Size = size,
            Usage = usage,
            SharingMode = SharingMode.Exclusive,
        };

        fixed (Buffer* bufferPtr = &buffer)
        {
            if (vk.CreateBuffer(device, bufferInfo, null, bufferPtr) != Result.Success)
            {
                throw new Exception("failed to create vertex buffer!");
            }
        }

        MemoryRequirements memRequirements = new();
        vk.GetBufferMemoryRequirements(device, buffer, out memRequirements);

        MemoryAllocateInfo allocateInfo = new()
        {
            SType = StructureType.MemoryAllocateInfo,
            AllocationSize = memRequirements.Size,
            MemoryTypeIndex = FindMemoryType(memRequirements.MemoryTypeBits, properties),
        };

        fixed (DeviceMemory* bufferMemoryPtr = &bufferMemory)
        {
            if (vk.AllocateMemory(device, allocateInfo, null, bufferMemoryPtr) != Result.Success)
            {
                throw new Exception("failed to allocate vertex buffer memory!");
            }
        }

        vk.BindBufferMemory(device, buffer, bufferMemory, 0);
    }

    public void createInstance()
    {

        if (enableValidationLayers && !CheckValidationLayerSupport())
        {
            throw new Exception("validation layers requested, but not available!");
        }

        ApplicationInfo appInfo = new()
        {
            SType = StructureType.ApplicationInfo,
            PApplicationName = (byte*)Marshal.StringToHGlobalAnsi("Hello Triangle"),
            ApplicationVersion = new Version32(1, 0, 0),
            PEngineName = (byte*)Marshal.StringToHGlobalAnsi("No Engine"),
            EngineVersion = new Version32(1, 0, 0),
            ApiVersion = Vk.Version12
        };

        InstanceCreateInfo createInfo = new()
        {
            SType = StructureType.InstanceCreateInfo,
            PApplicationInfo = &appInfo
        };

        var extensions = GetRequiredExtensions();
        createInfo.EnabledExtensionCount = (uint)extensions.Length;
        createInfo.PpEnabledExtensionNames = (byte**)SilkMarshal.StringArrayToPtr(extensions); ;

        if (enableValidationLayers)
        {
            createInfo.EnabledLayerCount = (uint)validationLayers.Length;
            createInfo.PpEnabledLayerNames = (byte**)SilkMarshal.StringArrayToPtr(validationLayers);

            DebugUtilsMessengerCreateInfoEXT debugCreateInfo = new();
            PopulateDebugMessengerCreateInfo(ref debugCreateInfo);
            createInfo.PNext = &debugCreateInfo;
        }
        else
        {
            createInfo.EnabledLayerCount = 0;
            createInfo.PNext = null;
        }

        if (vk.CreateInstance(createInfo, null, out instance) != Result.Success)
        {
            throw new Exception("failed to create instance!");
        }

        Marshal.FreeHGlobal((IntPtr)appInfo.PApplicationName);
        Marshal.FreeHGlobal((IntPtr)appInfo.PEngineName);
        SilkMarshal.Free((nint)createInfo.PpEnabledExtensionNames);

        if (enableValidationLayers)
        {
            SilkMarshal.Free((nint)createInfo.PpEnabledLayerNames);
        }

    }

    public void createSurface()
    {
        if (!vk.TryGetInstanceExtension<KhrSurface>(instance, out khrSurface))
        {
            throw new NotSupportedException("KHR_surface extension not found.");
        }

        if (window.VkSurface is null)
        {
            throw new ApplicationException("window.VkSurface is null and shouldn't be!");
        }

        surface = window.VkSurface.Create<AllocationCallbacks>(instance.ToHandle(), null).ToSurface();
    }

    public unsafe void pickPhysicalDevice()
    {
        uint devicedCount = 0;
        vk.EnumeratePhysicalDevices(instance, ref devicedCount, null);

        if (devicedCount == 0)
        {
            throw new Exception("failed to find GPUs with Vulkan support!");
        }

        var devices = new PhysicalDevice[devicedCount];
        fixed (PhysicalDevice* devicesPtr = devices)
        {
            vk.EnumeratePhysicalDevices(instance, ref devicedCount, devicesPtr);
        }

        foreach (var device in devices)
        {
            if (IsDeviceSuitable(device))
            {
                physicalDevice = device;
                msaaSamples = GetMaxUsableSampleCount();
                break;
            }
        }

        if (physicalDevice.Handle == 0)
        {
            throw new Exception("failed to find a suitable GPU!");
        }

        vk.GetPhysicalDeviceProperties(physicalDevice, out PhysicalDeviceProperties properties);
        deviceName = getStringFromBytePointer(properties.DeviceName, 50).Trim();
        log.d("device", $"using {deviceName}");
    }

    private static string getStringFromBytePointer(byte* pointer, int length)
    {
        // Create a span from the byte pointer and decode the string
        Span<byte> span = new Span<byte>(pointer, length);
        return Encoding.UTF8.GetString(span);
    }

    public void createLogicalDevice()
    {
        var indices = FindQueueFamilies(physicalDevice);

        var uniqueQueueFamilies = new[] { indices.GraphicsFamily!.Value, indices.PresentFamily!.Value };
        uniqueQueueFamilies = uniqueQueueFamilies.Distinct().ToArray();

        graphicsFamilyIndex = indices.GraphicsFamily.Value;

        using var mem = GlobalMemory.Allocate(uniqueQueueFamilies.Length * sizeof(DeviceQueueCreateInfo));
        var queueCreateInfos = (DeviceQueueCreateInfo*)Unsafe.AsPointer(ref mem.GetPinnableReference());

        float queuePriority = 1.0f;
        for (int i = 0; i < uniqueQueueFamilies.Length; i++)
        {
            queueCreateInfos[i] = new()
            {
                SType = StructureType.DeviceQueueCreateInfo,
                QueueFamilyIndex = uniqueQueueFamilies[i],
                QueueCount = 1
            };

            queueCreateInfos[i].PQueuePriorities = &queuePriority;
        }

        PhysicalDeviceFeatures deviceFeatures = new()
        {
            SamplerAnisotropy = true,
        };

        // Enable Synchronization 2 to eliminate a validation layer error, thanks gpt4!
        PhysicalDeviceSynchronization2FeaturesKHR sync2Features = new()
        {
            SType = StructureType.PhysicalDeviceSynchronization2FeaturesKhr,
            Synchronization2 = Vk.True
        };

        PhysicalDeviceFeatures2 deviceFeatures2 = new()
        {
            SType = StructureType.PhysicalDeviceFeatures2,
            PNext = &sync2Features
        };

        vk.GetPhysicalDeviceFeatures2(physicalDevice, &deviceFeatures2);

        DeviceCreateInfo createInfo = new()
        {
            SType = StructureType.DeviceCreateInfo,
            QueueCreateInfoCount = (uint)uniqueQueueFamilies.Length,
            PQueueCreateInfos = queueCreateInfos,

            PEnabledFeatures = &deviceFeatures,
            PNext = &sync2Features,

            EnabledExtensionCount = (uint)deviceExtensions.Length,
            PpEnabledExtensionNames = (byte**)SilkMarshal.StringArrayToPtr(deviceExtensions)
        };

        if (enableValidationLayers)
        {
            createInfo.EnabledLayerCount = (uint)validationLayers.Length;
            createInfo.PpEnabledLayerNames = (byte**)SilkMarshal.StringArrayToPtr(validationLayers);
        }
        else
        {
            createInfo.EnabledLayerCount = 0;
        }

        if (vk.CreateDevice(physicalDevice, in createInfo, null, out device) != Result.Success)
        {
            throw new Exception("failed to create logical device!");
        }

        vk.GetDeviceQueue(device, indices.GraphicsFamily!.Value, 0, out graphicsQueue);
        vk.GetDeviceQueue(device, indices.PresentFamily!.Value, 0, out presentQueue);

        if (enableValidationLayers)
        {
            SilkMarshal.Free((nint)createInfo.PpEnabledLayerNames);
        }

        SilkMarshal.Free((nint)createInfo.PpEnabledExtensionNames);
    }

    public void createCommandPool()
    {
        var queueFamiliyIndicies = FindQueueFamilies(physicalDevice);

        CommandPoolCreateInfo poolInfo = new()
        {
            SType = StructureType.CommandPoolCreateInfo,
            QueueFamilyIndex = queueFamiliyIndicies.GraphicsFamily!.Value,
            // added flag below to eliminate a validation layer error about clearing command buffer before recording
            Flags = CommandPoolCreateFlags.TransientBit | CommandPoolCreateFlags.ResetCommandBufferBit
        };

        if (vk.CreateCommandPool(device, poolInfo, null, out commandPool) != Result.Success)
        {
            throw new Exception("failed to create command pool!");
        }
    }

    // helpers

    public CommandBuffer BeginSingleTimeCommands()
    {
        CommandBufferAllocateInfo allocateInfo = new()
        {
            SType = StructureType.CommandBufferAllocateInfo,
            Level = CommandBufferLevel.Primary,
            CommandPool = commandPool,
            CommandBufferCount = 1,
        };

        CommandBuffer commandBuffer = default;
        vk.AllocateCommandBuffers(device, allocateInfo, out commandBuffer);

        CommandBufferBeginInfo beginInfo = new()
        {
            SType = StructureType.CommandBufferBeginInfo,
            Flags = CommandBufferUsageFlags.OneTimeSubmitBit,
        };

        vk.BeginCommandBuffer(commandBuffer, beginInfo);

        return commandBuffer;
    }

    public void EndSingleTimeCommands(CommandBuffer commandBuffer)
    {
        vk.EndCommandBuffer(commandBuffer);

        SubmitInfo submitInfo = new()
        {
            SType = StructureType.SubmitInfo,
            CommandBufferCount = 1,
            PCommandBuffers = &commandBuffer,
        };

        vk.QueueSubmit(graphicsQueue, 1, submitInfo, default);
        vk.QueueWaitIdle(graphicsQueue);

        vk.FreeCommandBuffers(device, commandPool, 1, commandBuffer);
    }

    public bool checkValidationLayerSupport()
    {
        uint propCount = 0;
        var result = vk.EnumerateInstanceLayerProperties(ref propCount, null);
        if (propCount == 0)
        {
            return false;
        }

        var ret = false;
        using var mem = GlobalMemory.Allocate((int)propCount * sizeof(LayerProperties));
        var props = (LayerProperties*)Unsafe.AsPointer(ref mem.GetPinnableReference());
        vk.EnumerateInstanceLayerProperties(ref propCount, props);

        for (int i = 0; i < propCount; i++)
        {
            var layerName = GetString(props[i].LayerName);
            if (layerName == validationLayers[0]) ret = true;
            //Console.WriteLine($"{i} {layerName}");
        }
        return ret;
    }

    internal static unsafe string GetString(byte* stringStart)
    {
        int characters = 0;
        while (stringStart[characters] != 0)
        {
            characters++;
        }

        return Encoding.UTF8.GetString(stringStart, characters);
    }

    private void PopulateDebugMessengerCreateInfo(ref DebugUtilsMessengerCreateInfoEXT createInfo)
    {
        createInfo.SType = StructureType.DebugUtilsMessengerCreateInfoExt;
        createInfo.MessageSeverity = DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt |
                                     DebugUtilsMessageSeverityFlagsEXT.WarningBitExt |
                                     DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt;
        createInfo.MessageType = DebugUtilsMessageTypeFlagsEXT.GeneralBitExt |
                                 DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt |
                                 DebugUtilsMessageTypeFlagsEXT.ValidationBitExt;
        createInfo.PfnUserCallback = (DebugUtilsMessengerCallbackFunctionEXT)DebugCallback;
    }

    private void setupDebugMessenger()
    {
        if (!enableValidationLayers) return;

        //TryGetInstanceExtension equivilant to method CreateDebugUtilsMessengerEXT from original tutorial.
        if (!vk.TryGetInstanceExtension(instance, out debugUtils)) return;

        DebugUtilsMessengerCreateInfoEXT createInfo = new();
        PopulateDebugMessengerCreateInfo(ref createInfo);

        if (debugUtils!.CreateDebugUtilsMessenger(instance, in createInfo, null, out debugMessenger) != Result.Success)
        {
            throw new Exception("failed to set up debug messenger!");
        }
    }

    private uint DebugCallback(DebugUtilsMessageSeverityFlagsEXT messageSeverity, DebugUtilsMessageTypeFlagsEXT messageTypes, DebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData)
    {
        if (messageSeverity == DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt) return Vk.False;

        var msg = Marshal.PtrToStringAnsi((nint)pCallbackData->PMessage);

        Debug.WriteLine($"{messageSeverity} | validation layer: {msg}");

        return Vk.False;
    }

    public struct SwapChainSupportDetails
    {
        public SurfaceCapabilitiesKHR Capabilities;
        public SurfaceFormatKHR[] Formats;
        public PresentModeKHR[] PresentModes;
    }

    public SwapChainSupportDetails QuerySwapChainSupport() => QuerySwapChainSupport(physicalDevice);

    private SwapChainSupportDetails QuerySwapChainSupport(PhysicalDevice physicalDevice)
    {
        var details = new SwapChainSupportDetails();

        khrSurface.GetPhysicalDeviceSurfaceCapabilities(physicalDevice, surface, out details.Capabilities);

        uint formatCount = 0;
        khrSurface.GetPhysicalDeviceSurfaceFormats(physicalDevice, surface, ref formatCount, null);

        if (formatCount != 0)
        {
            details.Formats = new SurfaceFormatKHR[formatCount];
            fixed (SurfaceFormatKHR* formatsPtr = details.Formats)
            {
                khrSurface.GetPhysicalDeviceSurfaceFormats(physicalDevice, surface, ref formatCount, formatsPtr);
            }
        }
        else
        {
            details.Formats = Array.Empty<SurfaceFormatKHR>();
        }

        uint presentModeCount = 0;
        khrSurface.GetPhysicalDeviceSurfacePresentModes(physicalDevice, surface, ref presentModeCount, null);

        if (presentModeCount != 0)
        {
            details.PresentModes = new PresentModeKHR[presentModeCount];
            fixed (PresentModeKHR* formatsPtr = details.PresentModes)
            {
                khrSurface.GetPhysicalDeviceSurfacePresentModes(physicalDevice, surface, ref presentModeCount, formatsPtr);
            }

        }
        else
        {
            details.PresentModes = Array.Empty<PresentModeKHR>();
        }

        return details;
    }

    private bool IsDeviceSuitable(PhysicalDevice device)
    {
        var indices = FindQueueFamilies(device);

        bool extensionsSupported = CheckDeviceExtensionsSupport(device);

        bool swapChainAdequate = false;
        if (extensionsSupported)
        {
            var swapChainSupport = QuerySwapChainSupport(device);
            swapChainAdequate = swapChainSupport.Formats.Any() && swapChainSupport.PresentModes.Any();
        }

        PhysicalDeviceFeatures supportedFeatures;
        vk.GetPhysicalDeviceFeatures(device, out supportedFeatures);

        PhysicalDeviceSynchronization2FeaturesKHR sync2Features = new()
        {
            SType = StructureType.PhysicalDeviceSynchronization2FeaturesKhr,
            Synchronization2 = Vk.True
        };

        PhysicalDeviceFeatures2 deviceFeatures2 = new()
        {
            SType = StructureType.PhysicalDeviceFeatures2,
            PNext = & sync2Features
        };

        //PhysicalDeviceFeatures2 supportedFeatures2;
        //vk.GetPhysicalDeviceFeatures2(device, out supportedFeatures2);
        vk.GetPhysicalDeviceFeatures2(device, &deviceFeatures2);

        return indices.IsComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.SamplerAnisotropy && sync2Features.Synchronization2;
    }

    private bool CheckDeviceExtensionsSupport(PhysicalDevice device)
    {
        uint extentionsCount = 0;
        vk.EnumerateDeviceExtensionProperties(device, (byte*)null, ref extentionsCount, null);

        var availableExtensions = new ExtensionProperties[extentionsCount];
        fixed (ExtensionProperties* availableExtensionsPtr = availableExtensions)
        {
            vk.EnumerateDeviceExtensionProperties(device, (byte*)null, ref extentionsCount, availableExtensionsPtr);
        }

        var availableExtensionNames = availableExtensions.Select(extension => Marshal.PtrToStringAnsi((IntPtr)extension.ExtensionName)).ToHashSet();

        return deviceExtensions.All(availableExtensionNames.Contains);

    }

    public struct QueueFamilyIndices
    {
        public uint? GraphicsFamily { get; set; }
        public uint? PresentFamily { get; set; }
        public bool IsComplete()
        {
            return GraphicsFamily.HasValue && PresentFamily.HasValue;
        }
    }

    public QueueFamilyIndices FindQueueFamilies() => FindQueueFamilies(physicalDevice);

    private QueueFamilyIndices FindQueueFamilies(PhysicalDevice device)
    {
        var indices = new QueueFamilyIndices();

        uint queueFamilityCount = 0;
        vk.GetPhysicalDeviceQueueFamilyProperties(device, ref queueFamilityCount, null);

        var queueFamilies = new QueueFamilyProperties[queueFamilityCount];
        fixed (QueueFamilyProperties* queueFamiliesPtr = queueFamilies)
        {
            vk.GetPhysicalDeviceQueueFamilyProperties(device, ref queueFamilityCount, queueFamiliesPtr);
        }

        uint i = 0;
        foreach (var queueFamily in queueFamilies)
        {
            if (queueFamily.QueueFlags.HasFlag(QueueFlags.GraphicsBit))
            {
                indices.GraphicsFamily = i;
            }

            khrSurface.GetPhysicalDeviceSurfaceSupport(device, i, surface, out var presentSupport);

            if (presentSupport)
            {
                indices.PresentFamily = i;
            }

            if (indices.IsComplete())
            {
                break;
            }

            i++;
        }

        return indices;
    }

    private string[] GetRequiredExtensions()
    {
        var glfwExtensions = window!.VkSurface!.GetRequiredExtensions(out var glfwExtensionCount);
        var extensions = SilkMarshal.PtrToStringArray((nint)glfwExtensions, (int)glfwExtensionCount);

        if (enableValidationLayers)
        {
            return extensions.Append(ExtDebugUtils.ExtensionName).ToArray();
        }

        return extensions;
    }

    private bool CheckValidationLayerSupport()
    {
        uint layerCount = 0;
        vk.EnumerateInstanceLayerProperties(ref layerCount, null);
        var availableLayers = new LayerProperties[layerCount];
        fixed (LayerProperties* availableLayersPtr = availableLayers)
        {
            vk.EnumerateInstanceLayerProperties(ref layerCount, availableLayersPtr);
        }

        var availableLayerNames = availableLayers.Select(layer => Marshal.PtrToStringAnsi((IntPtr)layer.LayerName)).ToHashSet();

        return validationLayers.All(availableLayerNames.Contains);
    }

    private SampleCountFlags GetMaxUsableSampleCount()
    {
        vk.GetPhysicalDeviceProperties(physicalDevice, out var physicalDeviceProperties);

        var counts = physicalDeviceProperties.Limits.FramebufferColorSampleCounts & physicalDeviceProperties.Limits.FramebufferDepthSampleCounts;

        return counts switch
        {
            var c when (c & SampleCountFlags.Count64Bit) != 0 => SampleCountFlags.Count64Bit,
            var c when (c & SampleCountFlags.Count32Bit) != 0 => SampleCountFlags.Count32Bit,
            var c when (c & SampleCountFlags.Count16Bit) != 0 => SampleCountFlags.Count16Bit,
            var c when (c & SampleCountFlags.Count8Bit) != 0 => SampleCountFlags.Count8Bit,
            var c when (c & SampleCountFlags.Count4Bit) != 0 => SampleCountFlags.Count4Bit,
            var c when (c & SampleCountFlags.Count2Bit) != 0 => SampleCountFlags.Count2Bit,
            _ => SampleCountFlags.Count1Bit
        };
    }

    private Format FindSupportedFormat(IEnumerable<Format> candidates, ImageTiling tiling, FormatFeatureFlags features)
    {
        foreach (var format in candidates)
        {
            vk.GetPhysicalDeviceFormatProperties(physicalDevice, format, out var props);

            if (tiling == ImageTiling.Linear && (props.LinearTilingFeatures & features) == features)
            {
                return format;
            }
            else if (tiling == ImageTiling.Optimal && (props.OptimalTilingFeatures & features) == features)
            {
                return format;
            }
        }

        throw new Exception("failed to find supported format!");
    }

    public void CopyBufferToImage(Buffer buffer, Image image, uint width, uint height, uint layerCount)
    {
        CommandBuffer commandBuffer = BeginSingleTimeCommands();

        BufferImageCopy region = new BufferImageCopy
        {
            BufferOffset = 0,
            BufferRowLength = 0,
            BufferImageHeight = 0,
            ImageSubresource = new ImageSubresourceLayers
            {
                AspectMask = ImageAspectFlags.ImageAspectColorBit,
                MipLevel = 0,
                BaseArrayLayer = 0,
                LayerCount = layerCount,
            },
            ImageOffset = new Offset3D(0, 0, 0),
            ImageExtent = new Extent3D(width, height, 1),
        };

        vk.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.TransferDstOptimal, 1, region);
        EndSingleTimeCommands(commandBuffer);
    }
    public void CreateImageWithInfo(ImageCreateInfo imageInfo, MemoryPropertyFlags properties, out Image image, out DeviceMemory imageMemory)
    {
        if (vk.CreateImage(device, in imageInfo, null, out image) != Result.Success)
        {
            throw new Exception("failed to create image!");
        }

        MemoryRequirements memRequirements;
        vk.GetImageMemoryRequirements(device, image, out memRequirements);

        MemoryAllocateInfo allocInfo = new MemoryAllocateInfo
        {
            SType = StructureType.MemoryAllocateInfo,
            AllocationSize = memRequirements.Size,
            MemoryTypeIndex = FindMemoryType(memRequirements.MemoryTypeBits, properties),
        };

        if (vk.AllocateMemory(device, in allocInfo, null, out imageMemory) != Result.Success)
        {
            throw new Exception("failed to allocate image memory!");
        }

        if (vk.BindImageMemory(device, image, imageMemory, 0) != Result.Success)
        {
            throw new Exception("failed to bind image memory!");
        }
    }

    public Format FindDepthFormat()
    {
        return FindSupportedFormat(new[] { Format.D32Sfloat, Format.D32SfloatS8Uint, Format.D24UnormS8Uint }, ImageTiling.Optimal, FormatFeatureFlags.DepthStencilAttachmentBit);
    }

    public uint FindMemoryType(uint typeFilter, MemoryPropertyFlags properties)
    {
        PhysicalDeviceMemoryProperties memProperties;
        vk.GetPhysicalDeviceMemoryProperties(physicalDevice, out memProperties);

        for (int i = 0; i < memProperties.MemoryTypeCount; i++)
        {
            if ((typeFilter & (1 << i)) != 0 && (memProperties.MemoryTypes[i].PropertyFlags & properties) == properties)
            {
                return (uint)i;
            }
        }

        throw new Exception("failed to find suitable memory type!");
    }
    public unsafe void Dispose()
    {
        vk.DestroyCommandPool(device, commandPool, null);
        vk.DestroyDevice(device, null);
        GC.SuppressFinalize(this);
    }

}

using System;
using System.Net;

namespace Sandbox02ImGui;

public class LveGameObject
{
    static uint currentId = 0;

    // Id prop
    private uint id = 0;
    public uint Id => id;

    // other props
    private LveModel? model = null;
    public LveModel? Model { get => model; set => model = value; }

    public int textureBinding = 1;

    public string ModelPath = "";

    private PointLightComponent? pointLight;
    public PointLightComponent? PointLight { get => pointLight; set => pointLight = value; }

    public Vector4 Color = new(1.0f);
    //public Vector4 Color { get => color; set => color = value; }
    public string Name = Path.GetFileNameWithoutExtension(Path.GetRandomFileName());

    private TransformComponent transform;
    public TransformComponent Transform { get => transform; set => transform = value; }

    public List<Component> Componenets = new();

    public LveGameObject Clone()
    {
        return new LveGameObject(id)
        {
            Name = this.Name,
            model = this.model,
            pointLight = this.pointLight,
            Color = this.Color,
            transform = new TransformComponent()
            {
                Translation = this.transform.Translation,
                Rotation = this.transform.Rotation,
                Scale = this.transform.Scale,
            },
            Componenets = this.Componenets.ToArray().ToList()
        };
    }

    public class Component
    {

        string ID;
        string ScriptPath;
        string CurrentScript;
        string ScriptText;
        LveGameObject parent;
        object instance;
        Type myType;
        public Component(LveGameObject p) => EditorStart(p);
        public void Update(double delta)
        {
            MethodInfo myMethod = myType.GetMethod("Update")!;
            myMethod.Invoke(instance, new object[] { delta });
        }
        public void Start(LveGameObject p,Assembly asm)
        {
            parent = p;
            myType = asm.GetType(Path.GetFileNameWithoutExtension(ScriptPath).Replace(" ", "_"))!;
            MethodInfo myMethod = myType.GetMethod("Start")!;
            instance = Activator.CreateInstance(myType)!;
            myMethod.Invoke(instance, new object[]{parent});
        }
        public void EditorUpdate()
        {
            ImGui.InputText("Script##"+ID,ref ScriptPath,500);
            if (File.Exists(Program.ProjectPath+ScriptPath) && CurrentScript != ScriptPath)
            {
                if (ImGui.Button("Read file##" + ID))
                {
                    CurrentScript = ScriptPath;
                }
            }
            else if (!File.Exists(Program.ProjectPath + ScriptPath))
            {
                if (ImGui.Button("Create file##" + ID))
                {
                    File.WriteAllText(Program.ProjectPath + ScriptPath,
                        $@"public class {Path.GetFileNameWithoutExtension(ScriptPath).Replace(" ", "_")}
                        {{
                            public void Start(){{}}
                            public void Update(){{}}
                        }}");
                    CurrentScript = ScriptPath;
                }
            }
            else if (File.Exists(Program.ProjectPath + ScriptPath) && CurrentScript == ScriptPath)
            {
                if (ImGui.Button("Open editor##" + ID))
                {
                    string pathToOpen = Program.ProjectPath + ScriptPath;

                    Process process = new Process();
                    process.StartInfo.FileName = "code";
                    process.StartInfo.Arguments = $"\"{pathToOpen}\"";  
                    process.StartInfo.UseShellExecute = true;
                    process.StartInfo.CreateNoWindow = true;
                    process.Start();
                }
            }
        }
        public void EditorStart(LveGameObject p)
        {
            ID = Path.GetFileNameWithoutExtension(Path.GetRandomFileName());
            ScriptPath = "Script.cs";
            parent = p;
        }
    }

    public class TransformComponent
    {
        public Vector3 Translation;
        public Vector3 Rotation;
        public Vector3 Scale;

        public TransformComponent()
        {
            Translation = Vector3.Zero;
            Rotation = Vector3.Zero;
            Scale = Vector3.One;
        }        

        public Matrix4x4 Mat4Old()
        {
            var matTranslate = Matrix4x4.CreateTranslation(Translation);
            var matScale = Matrix4x4.CreateScale(Scale);
            var matRot = Matrix4x4.CreateFromYawPitchRoll(Rotation.Y, Rotation.X, Rotation.Z);
            return matScale * matRot * matTranslate;
        }

        public Matrix4x4 Mat4()
        {
            float c3 = MathF.Cos(Rotation.Z);
            float s3 = MathF.Sin(Rotation.Z);
            float c2 = MathF.Cos(Rotation.X);
            float s2 = MathF.Sin(Rotation.X);
            float c1 = MathF.Cos(Rotation.Y);
            float s1 = MathF.Sin(Rotation.Y);

            return new(
                Scale.X * (c1 * c3 + s1 * s2 * s3),
                Scale.X * (c2 * s3),
                Scale.X * (c1 * s2 * s3 - c3 * s1),
                0.0f,
                Scale.Y * (c3 * s1 * s2 - c1 * s3),
                Scale.Y * (c2 * c3),
                Scale.Y * (c1 * c3 * s2 + s1 * s3),
                0.0f,
                Scale.Z * (c2 * s1),
                Scale.Z * (-s2),
                Scale.Z * (c1 * c2),
                0.0f,
                Translation.X, Translation.Y, Translation.Z, 1.0f
            );
        }

        public Matrix4x4 NormalMatrix()
        {
            float c3 = MathF.Cos(Rotation.Z);
            float s3 = MathF.Sin(Rotation.Z);
            float c2 = MathF.Cos(Rotation.X);
            float s2 = MathF.Sin(Rotation.X);
            float c1 = MathF.Cos(Rotation.Y);
            float s1 = MathF.Sin(Rotation.Y);

            var invScale = new Vector3(1f/Scale.X, 1f/Scale.Y, 1f/Scale.Z);

            return new(
                invScale.X * (c1 * c3 + s1 * s2 * s3),
                invScale.X * (c2 * s3),
                invScale.X * (c1 * s2 * s3 - c3 * s1),
                0.0f,
                invScale.Y * (c3 * s1 * s2 - c1 * s3),
                invScale.Y * (c2 * c3),
                invScale.Y * (c1 * c3 * s2 + s1 * s3),
                0.0f,
                invScale.Z * (c2 * s1),
                invScale.Z * (-s2),
                invScale.Z * (c1 * c2),
                0.0f,
                0.0f, 0.0f, 0.0f, 1.0f
            );
        }
    }

    public static LveGameObject MakePointLight(float intensity, float radius, Vector4 color)
    {
        LveGameObject gameObj = CreateGameObject();
        gameObj.Color = color;
        gameObj.Transform.Scale.X = radius;
        gameObj.PointLight = new PointLightComponent(intensity);
        return gameObj;
    }

    public static LveGameObject CreateGameObject()
    {
        currentId++;
        return new LveGameObject(currentId);
    }

    private LveGameObject(uint id)
    {
        this.id = id;
        transform = new();
    }
}
using JeremyAnsel.Media.WavefrontObj;
using Silk.NET.Vulkan;

namespace Sandbox02ImGui;

public class LveModel : IDisposable
{
    private readonly Vk vk = null!;
    private readonly LveDevice device = null!;

    private LveBuffer vertexBuffer = null!;
    private uint vertexCount;

    private bool hasIndexBuffer = false;
    private LveBuffer indexBuffer = null!;
    private uint indexCount;

    public LveModel(Vk vk, LveDevice device, Builder builder)
    {
        this.vk = vk;
        this.device = device;
        vertexCount = (uint)builder.Vertices.Length;
        createVertexBuffers(builder.Vertices);
        indexCount = (uint)builder.Indices.Length;
        if (indexCount > 0)
        {
            hasIndexBuffer = true;
            createIndexBuffers(builder.Indices);
        }
    }

    private unsafe void createVertexBuffers(Vertex[] vertices)
    {
        var instanceSize = (ulong)Vertex.SizeOf();
        ulong bufferSize = instanceSize * (ulong)vertices.Length;

        LveBuffer stagingBuffer = new(vk, device,
            instanceSize, vertexCount,
            BufferUsageFlags.TransferSrcBit,
            MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit
            );
        stagingBuffer.Map();
        stagingBuffer.WriteToBuffer(vertices);

        vertexBuffer = new(vk, device,
            instanceSize, vertexCount,
            BufferUsageFlags.VertexBufferBit | BufferUsageFlags.TransferDstBit,
            MemoryPropertyFlags.DeviceLocalBit
            );

        device.CopyBuffer(stagingBuffer.VkBuffer, vertexBuffer.VkBuffer, bufferSize);
    }

    private unsafe void createIndexBuffers(uint[] indices)
    {
        var instanceSize = (ulong)(Unsafe.SizeOf<uint>());
        ulong bufferSize = instanceSize * (ulong)indices.Length;

        LveBuffer stagingBuffer = new(vk, device,
            instanceSize, indexCount,
            BufferUsageFlags.TransferSrcBit,
            MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit
            );
        stagingBuffer.Map();
        stagingBuffer.WriteToBuffer(indices);

        indexBuffer = new(vk, device,
            instanceSize, indexCount,
            BufferUsageFlags.IndexBufferBit | BufferUsageFlags.TransferDstBit,
            MemoryPropertyFlags.DeviceLocalBit
            );

        device.CopyBuffer(stagingBuffer.VkBuffer, indexBuffer.VkBuffer, bufferSize);
    }

    public unsafe void Bind(CommandBuffer commandBuffer)
    {
        var vertexBuffers = new Buffer[] { vertexBuffer.VkBuffer };
        var offsets = new ulong[] { 0 };

        fixed (ulong* offsetsPtr = offsets)
        fixed (Buffer* vertexBuffersPtr = vertexBuffers)
        {
            vk.CmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffersPtr, offsetsPtr);
        }

        if (hasIndexBuffer)
        {
            vk.CmdBindIndexBuffer(commandBuffer, indexBuffer.VkBuffer, 0, IndexType.Uint32);
        }
    }

    public void Draw(CommandBuffer commandBuffer)
    {
        if (hasIndexBuffer)
        {
            vk.CmdDrawIndexed(commandBuffer, indexCount, 1, 0, 0, 0);
        }
        else
        {
            vk.CmdDraw(commandBuffer, vertexCount, 1, 0, 0);
        }
    }
    public unsafe void Dispose()
    {
        vertexBuffer.Dispose();
        indexBuffer.Dispose();
        GC.SuppressFinalize(this);
    }

    public struct Builder
    {
        public Vertex[] Vertices;
        public uint[] Indices;

        public Builder()
        {
            Vertices = Array.Empty<Vertex>();
            Indices = Array.Empty<uint>();
        }

        public unsafe void LoadModel(string path)
        {
            log.d("obj", $" loading {path}...");

            var objFile = ObjFile.FromFile(path);

            var vertexMap = new Dictionary<Vertex, uint>();
            var vertices = new List<Vertex>();
            var indices = new List<uint>();

            foreach (var face in objFile.Faces)
            {
                foreach (var vFace in face.Vertices)
                {
                    var vertexIndex = vFace.Vertex;
                    var vertex = objFile.Vertices[vertexIndex - 1];
                    var positionOut = new Vector3(vertex.Position.X, -vertex.Position.Y, vertex.Position.Z);
                    var colorOut = Vector3.Zero;
                    if (vertex.Color is not null)
                    {
                        colorOut = new(vertex.Color.Value.X, vertex.Color.Value.Y, vertex.Color.Value.Z);
                    }
                    else
                    {
                        colorOut = new(1f, 1f, 1f);
                    }

                    var normalIndex = vFace.Normal;
                    var normal = objFile.VertexNormals[normalIndex - 1];
                    var normalOut = new Vector3(normal.X, -normal.Y, normal.Z);

                    var textureIndex = vFace.Texture;
                    var texture = objFile.TextureVertices[textureIndex - 1];
                    //Flip Y for OBJ in Vulkan
                    var textureOut = new Vector2(texture.X, -texture.Y);

                    Vertex vertexOut = new()
                    {
                        Position = positionOut,
                        Color = colorOut,
                        Normal = normalOut,
                        UV = textureOut
                    };
                    if (vertexMap.TryGetValue(vertexOut, out var meshIndex))
                    {
                        indices.Add(meshIndex);
                    }
                    else
                    {
                        indices.Add((uint)vertices.Count);
                        vertexMap[vertexOut] = (uint)vertices.Count;
                        vertices.Add(vertexOut);
                    }
                }

            }

            Vertices = vertices.ToArray();
            Indices = indices.ToArray();

            log.d("obj", $" done {Vertices.Length} verts, {Indices.Length} indices");

        }

    }

}
namespace Sandbox02ImGui;

public class LvePipeline : IDisposable
{
    private readonly Vk vk = null!;
    private readonly LveDevice device = null!;

    private Pipeline graphicsPipeline;
    public Pipeline VkPipeline => graphicsPipeline;

    private ShaderModule vertShaderModule;
    private ShaderModule fragShaderModule;

    public LvePipeline(Vk vk, LveDevice device, byte[] vertBytes, byte[] fragBytes, PipelineConfigInfo configInfo)
    {
        this.vk = vk;
        this.device = device;
        createGraphicsPipeline(vertBytes, fragBytes, configInfo);
    }

    public LvePipeline(Vk vk, LveDevice device, string vertPath, string fragPath, PipelineConfigInfo configInfo)
    {
        this.vk = vk;
        this.device = device;
        createGraphicsPipeline(vertPath, fragPath, configInfo);
    }

    public void Bind(CommandBuffer commandBuffer)
    {
        vk.CmdBindPipeline(commandBuffer, PipelineBindPoint.Graphics, graphicsPipeline);
    }

    private unsafe void createGraphicsPipeline(string vertPath, string fragPath, PipelineConfigInfo configInfo)
    {
        var vertSource = getShaderBytes(vertPath);
        var fragSource = getShaderBytes(fragPath);
        createGraphicsPipeline(vertSource, fragSource, configInfo);
    }

    private unsafe void createGraphicsPipeline(byte[] vertBytes, byte[] fragBytes, PipelineConfigInfo configInfo)
    {

        vertShaderModule = createShaderModule(vertBytes);
        fragShaderModule = createShaderModule(fragBytes);

        PipelineShaderStageCreateInfo vertShaderStageInfo = new()
        {
            SType = StructureType.PipelineShaderStageCreateInfo,
            Stage = ShaderStageFlags.VertexBit,
            Module = vertShaderModule,
            PName = (byte*)SilkMarshal.StringToPtr("main"),
            Flags = PipelineShaderStageCreateFlags.None,
            PNext = null,
            PSpecializationInfo = null,
        };

        PipelineShaderStageCreateInfo fragShaderStageInfo = new()
        {
            SType = StructureType.PipelineShaderStageCreateInfo,
            Stage = ShaderStageFlags.FragmentBit,
            Module = fragShaderModule,
            PName = (byte*)SilkMarshal.StringToPtr("main"),
            Flags = PipelineShaderStageCreateFlags.None,
            PNext = null,
            PSpecializationInfo = null,
        };

        var shaderStages = stackalloc[]
        {
            vertShaderStageInfo,
            fragShaderStageInfo
        };

        var bindingDescriptions = configInfo.BindingDescriptions;
        var attributeDescriptions = configInfo.AttributeDescriptions;

        fixed (VertexInputBindingDescription* bindingDescriptionsPtr = bindingDescriptions)
        fixed (VertexInputAttributeDescription* attributeDescriptionsPtr = attributeDescriptions)
        {

            var vertextInputInfo = new PipelineVertexInputStateCreateInfo()
            {
                SType = StructureType.PipelineVertexInputStateCreateInfo,
                VertexAttributeDescriptionCount = (uint)attributeDescriptions.Length,
                VertexBindingDescriptionCount = (uint)bindingDescriptions.Length,
                PVertexAttributeDescriptions = attributeDescriptionsPtr,
                PVertexBindingDescriptions = bindingDescriptionsPtr,
            };

            // stole this from ImGui controller, pulled this out of the default pipelineConfig and constructor
            Span<DynamicState> dynamic_states = stackalloc DynamicState[] { DynamicState.Viewport, DynamicState.Scissor };
            var dynamic_state = new PipelineDynamicStateCreateInfo();
            dynamic_state.SType = StructureType.PipelineDynamicStateCreateInfo;
            dynamic_state.DynamicStateCount = (uint)dynamic_states.Length;
            dynamic_state.PDynamicStates = (DynamicState*)Unsafe.AsPointer(ref dynamic_states[0]);

            var pipelineInfo = new GraphicsPipelineCreateInfo()
            {
                SType = StructureType.GraphicsPipelineCreateInfo,
                StageCount = 2,
                PStages = shaderStages,
                PVertexInputState = &vertextInputInfo,
                PInputAssemblyState = &configInfo.InputAssemblyInfo,
                PViewportState = &configInfo.ViewportInfo,
                PRasterizationState = &configInfo.RasterizationInfo,
                PMultisampleState = &configInfo.MultisampleInfo,
                PColorBlendState = &configInfo.ColorBlendInfo,
                PDepthStencilState = &configInfo.DepthStencilInfo,
                PDynamicState = (PipelineDynamicStateCreateInfo*)Unsafe.AsPointer(ref dynamic_state),

                Layout = configInfo.PipelineLayout, 
                RenderPass = configInfo.RenderPass,  
                Subpass = configInfo.Subpass,       

                BasePipelineIndex = -1,
                BasePipelineHandle = default
            };

            if (vk.CreateGraphicsPipelines(device.VkDevice, default, 1, pipelineInfo, default, out graphicsPipeline) != Result.Success)
            {
                throw new Exception("failed to create graphics pipeline!");
            }

        }

        vk.DestroyShaderModule(device.VkDevice, fragShaderModule, null);
        vk.DestroyShaderModule(device.VkDevice, vertShaderModule, null);

        SilkMarshal.Free((nint)shaderStages[0].PName);
        SilkMarshal.Free((nint)shaderStages[1].PName);

    }

    private unsafe ShaderModule createShaderModule(byte[] code)
    {
        ShaderModuleCreateInfo createInfo = new()
        {
            SType = StructureType.ShaderModuleCreateInfo,
            CodeSize = (nuint)code.Length,
        };

        ShaderModule shaderModule;

        fixed (byte* codePtr = code)
        {
            createInfo.PCode = (uint*)codePtr;

            if (vk.CreateShaderModule(device.VkDevice, createInfo, null, out shaderModule) != Result.Success)
            {
                throw new Exception();
            }
        }

        return shaderModule;
    }

    private static byte[] getShaderBytes(string filename)
    {
        var assembly = Assembly.GetExecutingAssembly();
        //foreach (var item in assembly.GetManifestResourceNames())
        //{
        //    Console.WriteLine($"{item}");
        //}
        var resourceName = assembly.GetManifestResourceNames().FirstOrDefault(s => s.EndsWith(filename));
        if (resourceName is null) throw new ApplicationException($"*** No shader file found with name {filename}\n*** Check that resourceName and try again!  Did you forget to set glsl file to Embedded Resource/Do Not Copy?");

        using var stream = assembly.GetManifestResourceStream(resourceName) ?? throw new ApplicationException($"*** No shader file found at {resourceName}\n*** Check that resourceName and try again!  Did you forget to set glsl file to Embedded Resource/Do Not Copy?");
        using var ms = new MemoryStream();
        if (stream is null) return Array.Empty<byte>();
        stream.CopyTo(ms);
        return ms.ToArray();

    }

    // Default PipelineConfig
    public unsafe static void DefaultPipelineConfigInfo(ref PipelineConfigInfo configInfo)
    {
        configInfo.InputAssemblyInfo.SType = StructureType.PipelineInputAssemblyStateCreateInfo;
        configInfo.InputAssemblyInfo.Topology = PrimitiveTopology.TriangleList;
        configInfo.InputAssemblyInfo.PrimitiveRestartEnable = Vk.False; //imgui

        configInfo.ViewportInfo.SType = StructureType.PipelineViewportStateCreateInfo;
        configInfo.ViewportInfo.ViewportCount = 1;
        configInfo.ViewportInfo.PViewports = default; // imgui
        configInfo.ViewportInfo.ScissorCount = 1;
        configInfo.ViewportInfo.PScissors = default;  // imgui

        configInfo.RasterizationInfo.SType = StructureType.PipelineRasterizationStateCreateInfo;
        configInfo.RasterizationInfo.DepthClampEnable = Vk.False;
        configInfo.RasterizationInfo.RasterizerDiscardEnable = Vk.False;
        configInfo.RasterizationInfo.PolygonMode = PolygonMode.Fill;
        configInfo.RasterizationInfo.LineWidth = 1f;
        configInfo.RasterizationInfo.CullMode = CullModeFlags.None;
        configInfo.RasterizationInfo.FrontFace = FrontFace.CounterClockwise;
        configInfo.RasterizationInfo.DepthBiasEnable = Vk.False;
        configInfo.RasterizationInfo.DepthBiasConstantFactor = 0f;
        configInfo.RasterizationInfo.DepthBiasClamp = 0f;
        configInfo.RasterizationInfo.DepthBiasSlopeFactor = 0f;

        configInfo.MultisampleInfo.SType = StructureType.PipelineMultisampleStateCreateInfo;
        configInfo.MultisampleInfo.SampleShadingEnable = Vk.False;
        configInfo.MultisampleInfo.RasterizationSamples = SampleCountFlags.Count1Bit;
        configInfo.MultisampleInfo.MinSampleShading = 1.0f;
        configInfo.MultisampleInfo.PSampleMask = default;
        configInfo.MultisampleInfo.AlphaToCoverageEnable = Vk.False;
        configInfo.MultisampleInfo.AlphaToOneEnable = Vk.False;

        configInfo.ColorBlendAttachment.BlendEnable = Vk.False;
        configInfo.ColorBlendAttachment.SrcColorBlendFactor = BlendFactor.One;
        configInfo.ColorBlendAttachment.DstColorBlendFactor = BlendFactor.Zero;
        configInfo.ColorBlendAttachment.ColorBlendOp = BlendOp.Add;
        configInfo.ColorBlendAttachment.SrcAlphaBlendFactor = BlendFactor.One;
        configInfo.ColorBlendAttachment.DstAlphaBlendFactor = BlendFactor.Zero;
        configInfo.ColorBlendAttachment.AlphaBlendOp = BlendOp.Add;
        configInfo.ColorBlendAttachment.ColorWriteMask =
            ColorComponentFlags.RBit | ColorComponentFlags.GBit |
            ColorComponentFlags.BBit | ColorComponentFlags.ABit;

        configInfo.ColorBlendInfo.SType = StructureType.PipelineColorBlendStateCreateInfo;
        configInfo.ColorBlendInfo.LogicOpEnable = Vk.False;
        configInfo.ColorBlendInfo.LogicOp = LogicOp.Copy;
        configInfo.ColorBlendInfo.AttachmentCount = 1;
        configInfo.ColorBlendInfo.PAttachments = (PipelineColorBlendAttachmentState*)Unsafe.AsPointer(ref configInfo.ColorBlendAttachment);

        configInfo.ColorBlendInfo.BlendConstants[0] = 0;
        configInfo.ColorBlendInfo.BlendConstants[1] = 0;
        configInfo.ColorBlendInfo.BlendConstants[2] = 0;
        configInfo.ColorBlendInfo.BlendConstants[3] = 0;

        configInfo.DepthStencilInfo.SType = StructureType.PipelineDepthStencilStateCreateInfo;
        configInfo.DepthStencilInfo.DepthTestEnable = Vk.True;
        configInfo.DepthStencilInfo.DepthWriteEnable = Vk.True;
        configInfo.DepthStencilInfo.DepthCompareOp = CompareOp.Less;
        configInfo.DepthStencilInfo.DepthBoundsTestEnable = Vk.False;
        configInfo.DepthStencilInfo.MinDepthBounds = 0.0f;
        configInfo.DepthStencilInfo.MaxDepthBounds = 1.0f;
        configInfo.DepthStencilInfo.StencilTestEnable = Vk.False;
        configInfo.DepthStencilInfo.Front = default;
        configInfo.DepthStencilInfo.Back = default;

        configInfo.BindingDescriptions = Vertex.GetBindingDescriptions();
        configInfo.AttributeDescriptions = Vertex.GetAttributeDescriptions();

        // pulled dynamic state stuff out of here 

        //var dynamicStateEnables = stackalloc DynamicState[] { DynamicState.Viewport, DynamicState.Scissor };
        //Span<DynamicState> dynamic_states = stackalloc DynamicState[] { DynamicState.Viewport, DynamicState.Scissor };

        //PipelineDynamicStateCreateInfo dynamicState = new()
        //{

        //configInfo.DynamicStateInfo.SType = StructureType.PipelineDynamicStateCreateInfo;
        //configInfo.DynamicStateInfo.PDynamicStates = (DynamicState*)Unsafe.AsPointer(ref dynamic_states[0]);
        //configInfo.DynamicStateInfo.DynamicStateCount = (uint)dynamic_states.Length;
        //configInfo.DynamicStateInfo.Flags = 0;
        //};

        //return configInfo with
        //{
        //    InputAssemblyInfo = inputAssembly,
        //    //Viewport = viewport,
        //    //Scissor = scissor,
        //    ViewportInfo = viewportInfo,
        //    RasterizationInfo = rasterizer,
        //    MultisampleInfo = multisampling,
        //    ColorBlendAttachment = colorBlendAttachment,
        //    ColorBlendInfo = colorBlending,
        //    DepthStencilInfo = depthStencil,
        //    DynamicStateInfo = dynamicState,
        //};
    }

    public static void EnableAlphaBlending(ref PipelineConfigInfo configInfo)
    {
        configInfo.ColorBlendAttachment.BlendEnable = Vk.True;
        configInfo.ColorBlendAttachment.SrcColorBlendFactor = BlendFactor.SrcAlpha;
        configInfo.ColorBlendAttachment.DstColorBlendFactor = BlendFactor.OneMinusSrcAlpha;
        configInfo.ColorBlendAttachment.ColorBlendOp = BlendOp.Add;
        configInfo.ColorBlendAttachment.SrcAlphaBlendFactor = BlendFactor.One;
        configInfo.ColorBlendAttachment.DstAlphaBlendFactor = BlendFactor.OneMinusSrcAlpha;
        configInfo.ColorBlendAttachment.AlphaBlendOp = BlendOp.Add;
        configInfo.ColorBlendAttachment.ColorWriteMask = 
            ColorComponentFlags.RBit | ColorComponentFlags.GBit | 
            ColorComponentFlags.BBit | ColorComponentFlags.ABit;

    }
    public static void EnableMultiSampling(ref PipelineConfigInfo configInfo, SampleCountFlags msaaSamples)
    {
        configInfo.MultisampleInfo.RasterizationSamples = msaaSamples;
    }

    public unsafe void Dispose()
    {
        vk.DestroyShaderModule(device.VkDevice, vertShaderModule, null);
        vk.DestroyShaderModule(device.VkDevice, fragShaderModule, null);
        vk.DestroyPipeline(device.VkDevice, graphicsPipeline, null);

        GC.SuppressFinalize(this);
    }

}

public struct PipelineConfigInfo
{
    public VertexInputBindingDescription[] BindingDescriptions;
    public VertexInputAttributeDescription[] AttributeDescriptions;

    //public Viewport Viewport;
    //public Rect2D Scissor;
    public PipelineViewportStateCreateInfo ViewportInfo;
    public PipelineInputAssemblyStateCreateInfo InputAssemblyInfo;
    public PipelineRasterizationStateCreateInfo RasterizationInfo;
    public PipelineMultisampleStateCreateInfo MultisampleInfo;
    public PipelineColorBlendAttachmentState ColorBlendAttachment;
    public PipelineColorBlendStateCreateInfo ColorBlendInfo;
    public PipelineDepthStencilStateCreateInfo DepthStencilInfo;
    //public DynamicState[] DynamicStateEnables;

    //public PipelineDynamicStateCreateInfo DynamicStateInfo;
    public PipelineLayout PipelineLayout; // no default to be set
    public RenderPass RenderPass; // no default to be set
    public uint Subpass;
    //public DynamicState[] DynamicStateEnables; //= stackalloc DynamicState[] { DynamicState.Viewport, DynamicState.Scissor };

    public PipelineConfigInfo()
    {
        Subpass = 0;    
        BindingDescriptions = Array.Empty<VertexInputBindingDescription>();
        AttributeDescriptions = Array.Empty<VertexInputAttributeDescription>();

    //PipelineDynamicStateCreateInfo dynamicState = new()
    //{
    //unsafe
    //{
    //    Span<DynamicState> dynamic_states = stackalloc DynamicState[] { DynamicState.Viewport, DynamicState.Scissor };
    //    DynamicStateEnables = dynamic_states.ToArray();
    //    DynamicStateInfo.PDynamicStates = (DynamicState*)Unsafe.AsPointer(ref DynamicStateEnables[0]);
    //}

    //DynamicStateEnables = Array.Empty<DynamicState>();
}
}