Open Samma2009 opened 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>();
}
}
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