feat: world state, multiple lights, textures, stdio MCP, Claude config
- Add get_world_state AI command that dumps ECS entities with Transform, Camera, Material, Light, and Mesh summaries. - Add set_material AI command to update albedo/roughness/metallic/texture. - Expose both commands as MCP HTTP tools and stdio tools. - Add Light component and support up to 4 directional lights via a Vulkan uniform buffer (descriptor set 0) with std140 layout. - Move per-frame lighting/camera data into the uniform buffer; push constants now carry only MVP + material properties (96 bytes). - Add Texture class for PNG loading and Vulkan image/view/sampler creation. - Add per-entity combined image sampler descriptor set (set 1) and use it for albedo texture sampling in the fragment shader. - Generate a checkerboard floor texture in Program.cs. - Add McpStdioServer for headless stdio MCP (Claude Desktop compatible). - Add claude_desktop_config.json and scripts/start_mcp_engine.sh. - Update CORTEX_ENGINE_ARCHITECTURE.md with runtime notes, CLI arguments, Vulkan pipeline details, and MCP client configuration. - All configs (Debug/Release/ReleaseAOT) build successfully.
This commit is contained in:
@@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Flecs.NET.Core;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Silk.NET.Core;
|
||||
using Silk.NET.Vulkan;
|
||||
using Engine.Core;
|
||||
@@ -20,6 +22,13 @@ public sealed unsafe class MeshRenderer : IDisposable
|
||||
private readonly Swapchain _swapchain;
|
||||
private readonly VulkanPipeline _pipeline;
|
||||
private readonly ScreenshotCapture _screenshot;
|
||||
private readonly UniformBuffer _frameConstantsBuffer;
|
||||
private DescriptorPool _frameDescriptorPool;
|
||||
private DescriptorSet _frameDescriptorSet;
|
||||
private DescriptorPool _textureDescriptorPool;
|
||||
private readonly Dictionary<string, Texture> _textures = new();
|
||||
private readonly Dictionary<Texture, DescriptorSet> _textureDescriptorSets = new();
|
||||
private Texture? _defaultTexture;
|
||||
private readonly Dictionary<Entity, MeshBuffers> _buffers = new();
|
||||
private CommandPool _commandPool;
|
||||
private CommandBuffer[] _commandBuffers = null!;
|
||||
@@ -28,18 +37,38 @@ public sealed unsafe class MeshRenderer : IDisposable
|
||||
private Silk.NET.Vulkan.Fence[] _inFlightFences = null!;
|
||||
private int _currentFrame;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
[StructLayout(LayoutKind.Sequential, Size = 96)]
|
||||
private struct PushConstants
|
||||
{
|
||||
public Matrix4x4 Mvp;
|
||||
public Vector3 LightDirection;
|
||||
public float Pad1;
|
||||
public Vector3 LightColor;
|
||||
public float Pad2;
|
||||
public Vector3 AmbientColor;
|
||||
public float Pad3;
|
||||
public Vector3 MaterialAlbedo;
|
||||
public float MaterialRoughness;
|
||||
public float MaterialMetallic;
|
||||
public uint UseTexture;
|
||||
public uint TextureIndex;
|
||||
public uint Pad0;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 48)]
|
||||
private struct GpuLight
|
||||
{
|
||||
public Vector3 Direction;
|
||||
public float Intensity;
|
||||
public Vector3 Color;
|
||||
public float Padding;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 224)]
|
||||
private struct FrameConstants
|
||||
{
|
||||
public Vector3 CameraPosition;
|
||||
public float Pad4;
|
||||
public uint LightCount;
|
||||
public Vector3 AmbientColor;
|
||||
public float AmbientPadding;
|
||||
public GpuLight Light0;
|
||||
public GpuLight Light1;
|
||||
public GpuLight Light2;
|
||||
public GpuLight Light3;
|
||||
}
|
||||
|
||||
private sealed class MeshBuffers : IDisposable
|
||||
@@ -67,6 +96,11 @@ public sealed unsafe class MeshRenderer : IDisposable
|
||||
_screenshot = new ScreenshotCapture(context, swapchain);
|
||||
|
||||
_pipeline = new VulkanPipeline(context, swapchain);
|
||||
_frameConstantsBuffer = new UniformBuffer(context, (ulong)sizeof(FrameConstants));
|
||||
CreateFrameDescriptorPool();
|
||||
CreateFrameDescriptorSet();
|
||||
CreateTextureDescriptorPool();
|
||||
CreateDefaultTexture();
|
||||
CreateCommandPool();
|
||||
CreateCommandBuffers();
|
||||
CreateSyncObjects();
|
||||
@@ -135,6 +169,120 @@ public sealed unsafe class MeshRenderer : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateFrameDescriptorPool()
|
||||
{
|
||||
var poolSize = new DescriptorPoolSize
|
||||
{
|
||||
Type = DescriptorType.UniformBuffer,
|
||||
DescriptorCount = 1
|
||||
};
|
||||
|
||||
var createInfo = new DescriptorPoolCreateInfo
|
||||
{
|
||||
SType = StructureType.DescriptorPoolCreateInfo,
|
||||
MaxSets = 1,
|
||||
PoolSizeCount = 1,
|
||||
PPoolSizes = &poolSize
|
||||
};
|
||||
|
||||
DescriptorPool descriptorPool;
|
||||
var result = _context.Vk.CreateDescriptorPool(_context.Device, &createInfo, null, &descriptorPool);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateDescriptorPool failed: {result}");
|
||||
_frameDescriptorPool = descriptorPool;
|
||||
}
|
||||
|
||||
private void CreateFrameDescriptorSet()
|
||||
{
|
||||
var layout = _pipeline.FrameDescriptorSetLayout;
|
||||
var allocInfo = new DescriptorSetAllocateInfo
|
||||
{
|
||||
SType = StructureType.DescriptorSetAllocateInfo,
|
||||
DescriptorPool = _frameDescriptorPool,
|
||||
DescriptorSetCount = 1,
|
||||
PSetLayouts = &layout
|
||||
};
|
||||
|
||||
DescriptorSet descriptorSet;
|
||||
var result = _context.Vk.AllocateDescriptorSets(_context.Device, &allocInfo, &descriptorSet);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkAllocateDescriptorSets failed: {result}");
|
||||
_frameDescriptorSet = descriptorSet;
|
||||
|
||||
var bufferInfo = new DescriptorBufferInfo
|
||||
{
|
||||
Buffer = _frameConstantsBuffer.Buffer,
|
||||
Offset = 0,
|
||||
Range = (ulong)sizeof(FrameConstants)
|
||||
};
|
||||
|
||||
var write = new WriteDescriptorSet
|
||||
{
|
||||
SType = StructureType.WriteDescriptorSet,
|
||||
DstSet = _frameDescriptorSet,
|
||||
DstBinding = 0,
|
||||
DstArrayElement = 0,
|
||||
DescriptorType = DescriptorType.UniformBuffer,
|
||||
DescriptorCount = 1,
|
||||
PBufferInfo = &bufferInfo
|
||||
};
|
||||
|
||||
_context.Vk.UpdateDescriptorSets(_context.Device, 1, &write, 0, null);
|
||||
}
|
||||
|
||||
private void CreateTextureDescriptorPool()
|
||||
{
|
||||
var poolSize = new DescriptorPoolSize
|
||||
{
|
||||
Type = DescriptorType.CombinedImageSampler,
|
||||
DescriptorCount = 16
|
||||
};
|
||||
|
||||
var createInfo = new DescriptorPoolCreateInfo
|
||||
{
|
||||
SType = StructureType.DescriptorPoolCreateInfo,
|
||||
MaxSets = 16,
|
||||
PoolSizeCount = 1,
|
||||
PPoolSizes = &poolSize
|
||||
};
|
||||
|
||||
DescriptorPool descriptorPool;
|
||||
var result = _context.Vk.CreateDescriptorPool(_context.Device, &createInfo, null, &descriptorPool);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateDescriptorPool (texture) failed: {result}");
|
||||
_textureDescriptorPool = descriptorPool;
|
||||
}
|
||||
|
||||
private void CreateDefaultTexture()
|
||||
{
|
||||
var whitePixel = new byte[] { 255, 255, 255, 255 };
|
||||
_defaultTexture = CreateTextureFromBytes("__default__", whitePixel, 1, 1);
|
||||
}
|
||||
|
||||
private Texture CreateTextureFromBytes(string key, byte[] rgbaPixels, uint width, uint height)
|
||||
{
|
||||
var path = $"/tmp/cortex_texture_{key}.png";
|
||||
System.IO.File.WriteAllBytes(path, EncodePng(rgbaPixels, width, height));
|
||||
var texture = new Texture(_context, path);
|
||||
try
|
||||
{
|
||||
System.IO.File.Delete(path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore cleanup failure.
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static byte[] EncodePng(byte[] rgbaPixels, uint width, uint height)
|
||||
{
|
||||
using var image = SixLabors.ImageSharp.Image.LoadPixelData<Rgba32>(rgbaPixels, (int)width, (int)height);
|
||||
using var stream = new System.IO.MemoryStream();
|
||||
image.SaveAsPng(stream);
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
public void RequestScreenshot(string outputPath) => _screenshot.Request(outputPath);
|
||||
|
||||
public bool IsScreenshotRequested => _screenshot.IsRequested;
|
||||
@@ -184,6 +332,8 @@ public sealed unsafe class MeshRenderer : IDisposable
|
||||
|
||||
_context.Vk.CmdBeginRenderPass(cmd, &renderPassInfo, SubpassContents.Inline);
|
||||
_context.Vk.CmdBindPipeline(cmd, PipelineBindPoint.Graphics, _pipeline.Handle);
|
||||
var frameDescriptorSet = _frameDescriptorSet;
|
||||
_context.Vk.CmdBindDescriptorSets(cmd, PipelineBindPoint.Graphics, _pipeline.Layout, 0, 1, &frameDescriptorSet, 0, null);
|
||||
|
||||
var viewport = new Viewport(0, 0, _swapchain.Extent.Width, _swapchain.Extent.Height, 0, 1);
|
||||
var scissor = new Rect2D(new Offset2D(0, 0), _swapchain.Extent);
|
||||
@@ -195,6 +345,14 @@ public sealed unsafe class MeshRenderer : IDisposable
|
||||
var proj = camera.GetProjectionMatrix();
|
||||
var drawCmd = cmd;
|
||||
|
||||
var frameConstants = BuildFrameConstants(world, camera);
|
||||
var frameConstantsBytes = new byte[sizeof(FrameConstants)];
|
||||
fixed (byte* p = frameConstantsBytes)
|
||||
{
|
||||
*(FrameConstants*)p = frameConstants;
|
||||
}
|
||||
_frameConstantsBuffer.Update(frameConstantsBytes);
|
||||
|
||||
world.Each((Entity e, ref Mesh mesh, ref Transform transform) =>
|
||||
{
|
||||
if (!_buffers.TryGetValue(e, out var buffers))
|
||||
@@ -208,14 +366,20 @@ public sealed unsafe class MeshRenderer : IDisposable
|
||||
buffers.VertexBuffer.Update(bytes);
|
||||
|
||||
var mvp = Matrix4x4.Transpose(Matrix4x4.Multiply(view, proj));
|
||||
var texture = GetTexture(material);
|
||||
var textureDescriptorSet = GetTextureDescriptorSet(texture);
|
||||
var textureSet = textureDescriptorSet;
|
||||
_context.Vk.CmdBindDescriptorSets(drawCmd, PipelineBindPoint.Graphics, _pipeline.Layout, 1, 1, &textureSet, 0, null);
|
||||
|
||||
var push = new PushConstants
|
||||
{
|
||||
Mvp = mvp,
|
||||
LightDirection = new Vector3(0.5f, -1.0f, -0.5f),
|
||||
LightColor = new Vector3(1.0f, 0.95f, 0.8f),
|
||||
AmbientColor = new Vector3(0.15f, 0.15f, 0.2f),
|
||||
CameraPosition = camera.Position
|
||||
MaterialAlbedo = material.Albedo,
|
||||
MaterialRoughness = material.Roughness,
|
||||
MaterialMetallic = material.Metallic,
|
||||
UseTexture = material.HasTexture ? 1u : 0u,
|
||||
TextureIndex = 0,
|
||||
Pad0 = 0
|
||||
};
|
||||
|
||||
var pushSize = (uint)sizeof(PushConstants);
|
||||
@@ -335,6 +499,117 @@ public sealed unsafe class MeshRenderer : IDisposable
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private Texture GetTexture(Material material)
|
||||
{
|
||||
if (!material.HasTexture)
|
||||
return _defaultTexture!;
|
||||
|
||||
if (_textures.TryGetValue(material.TexturePath!, out var texture))
|
||||
return texture;
|
||||
|
||||
if (!System.IO.File.Exists(material.TexturePath!))
|
||||
return _defaultTexture!;
|
||||
|
||||
texture = new Texture(_context, material.TexturePath!);
|
||||
_textures[material.TexturePath!] = texture;
|
||||
return texture;
|
||||
}
|
||||
|
||||
private DescriptorSet GetTextureDescriptorSet(Texture texture)
|
||||
{
|
||||
if (_textureDescriptorSets.TryGetValue(texture, out var descriptorSet))
|
||||
return descriptorSet;
|
||||
|
||||
var layout = _pipeline.TextureDescriptorSetLayout;
|
||||
var allocInfo = new DescriptorSetAllocateInfo
|
||||
{
|
||||
SType = StructureType.DescriptorSetAllocateInfo,
|
||||
DescriptorPool = _textureDescriptorPool,
|
||||
DescriptorSetCount = 1,
|
||||
PSetLayouts = &layout
|
||||
};
|
||||
|
||||
DescriptorSet set;
|
||||
var result = _context.Vk.AllocateDescriptorSets(_context.Device, &allocInfo, &set);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkAllocateDescriptorSets (texture) failed: {result}");
|
||||
|
||||
var imageInfo = new DescriptorImageInfo
|
||||
{
|
||||
ImageLayout = ImageLayout.ShaderReadOnlyOptimal,
|
||||
ImageView = texture.View,
|
||||
Sampler = texture.Sampler
|
||||
};
|
||||
|
||||
var write = new WriteDescriptorSet
|
||||
{
|
||||
SType = StructureType.WriteDescriptorSet,
|
||||
DstSet = set,
|
||||
DstBinding = 0,
|
||||
DstArrayElement = 0,
|
||||
DescriptorType = DescriptorType.CombinedImageSampler,
|
||||
DescriptorCount = 1,
|
||||
PImageInfo = &imageInfo
|
||||
};
|
||||
|
||||
_context.Vk.UpdateDescriptorSets(_context.Device, 1, &write, 0, null);
|
||||
_textureDescriptorSets[texture] = set;
|
||||
return set;
|
||||
}
|
||||
|
||||
private FrameConstants BuildFrameConstants(World world, Camera camera)
|
||||
{
|
||||
var frameConstants = new FrameConstants
|
||||
{
|
||||
CameraPosition = camera.Position,
|
||||
LightCount = 0,
|
||||
AmbientColor = new Vector3(0.4f, 0.4f, 0.45f),
|
||||
AmbientPadding = 0
|
||||
};
|
||||
|
||||
world.Each((Entity e, ref Light light) =>
|
||||
{
|
||||
if (frameConstants.LightCount >= 4)
|
||||
return;
|
||||
|
||||
var index = (int)frameConstants.LightCount;
|
||||
frameConstants.LightCount++;
|
||||
SetLight(ref frameConstants, index, new GpuLight
|
||||
{
|
||||
Direction = light.Direction,
|
||||
Intensity = light.Intensity,
|
||||
Color = light.Color,
|
||||
Padding = 0
|
||||
});
|
||||
});
|
||||
|
||||
// Fallback: if no light components exist, add a default directional light.
|
||||
if (frameConstants.LightCount == 0)
|
||||
{
|
||||
frameConstants.LightCount = 1;
|
||||
SetLight(ref frameConstants, 0, new GpuLight
|
||||
{
|
||||
Direction = new Vector3(0.5f, -1.0f, -0.5f),
|
||||
Intensity = 1.0f,
|
||||
Color = new Vector3(1.0f, 0.95f, 0.8f),
|
||||
Padding = 0
|
||||
});
|
||||
}
|
||||
|
||||
return frameConstants;
|
||||
}
|
||||
|
||||
private static void SetLight(ref FrameConstants frameConstants, int index, GpuLight light)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: frameConstants.Light0 = light; break;
|
||||
case 1: frameConstants.Light1 = light; break;
|
||||
case 2: frameConstants.Light2 = light; break;
|
||||
case 3: frameConstants.Light3 = light; break;
|
||||
}
|
||||
}
|
||||
|
||||
private Camera GetCamera(World world)
|
||||
{
|
||||
var camera = new Camera(
|
||||
@@ -374,6 +649,16 @@ public sealed unsafe class MeshRenderer : IDisposable
|
||||
}
|
||||
|
||||
_context.Vk.DestroyCommandPool(_context.Device, _commandPool, null);
|
||||
_context.Vk.DestroyDescriptorPool(_context.Device, _textureDescriptorPool, null);
|
||||
_context.Vk.DestroyDescriptorPool(_context.Device, _frameDescriptorPool, null);
|
||||
|
||||
foreach (var texture in _textures.Values)
|
||||
texture.Dispose();
|
||||
_textures.Clear();
|
||||
|
||||
_defaultTexture?.Dispose();
|
||||
|
||||
_frameConstantsBuffer.Dispose();
|
||||
|
||||
_pipeline.Dispose();
|
||||
}
|
||||
|
||||
@@ -3,36 +3,67 @@
|
||||
layout(location = 0) in vec3 fragColor;
|
||||
layout(location = 1) in vec3 fragNormal;
|
||||
layout(location = 2) in vec3 fragWorldPos;
|
||||
layout(location = 3) in vec2 fragUv;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
struct Light
|
||||
{
|
||||
vec3 direction;
|
||||
float intensity;
|
||||
vec3 color;
|
||||
float _pad;
|
||||
};
|
||||
|
||||
layout(set = 0, binding = 0) uniform FrameConstants
|
||||
{
|
||||
vec3 cameraPosition;
|
||||
uint lightCount;
|
||||
vec3 ambientColor;
|
||||
float _pad;
|
||||
Light lights[4];
|
||||
} frame;
|
||||
|
||||
layout(set = 1, binding = 0) uniform sampler2D albedoTexture;
|
||||
|
||||
layout(push_constant) uniform PushConstants
|
||||
{
|
||||
mat4 mvp;
|
||||
vec3 lightDirection;
|
||||
float pad1;
|
||||
vec3 lightColor;
|
||||
float pad2;
|
||||
vec3 ambientColor;
|
||||
float pad3;
|
||||
vec3 cameraPosition;
|
||||
float pad4;
|
||||
vec3 materialAlbedo;
|
||||
float materialRoughness;
|
||||
float materialMetallic;
|
||||
uint useTexture;
|
||||
uint textureIndex;
|
||||
uint _pad0;
|
||||
uint _pad1;
|
||||
} push;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 normal = normalize(fragNormal);
|
||||
vec3 lightDir = normalize(-push.lightDirection);
|
||||
vec3 viewDir = normalize(push.cameraPosition - fragWorldPos);
|
||||
vec3 halfDir = normalize(lightDir + viewDir);
|
||||
vec3 viewDir = normalize(frame.cameraPosition - fragWorldPos);
|
||||
vec3 albedo = fragColor * push.materialAlbedo;
|
||||
if (push.useTexture != 0u)
|
||||
{
|
||||
albedo *= texture(albedoTexture, fragUv).rgb;
|
||||
}
|
||||
float roughness = clamp(push.materialRoughness, 0.05, 1.0);
|
||||
float metallic = clamp(push.materialMetallic, 0.0, 1.0);
|
||||
|
||||
float diff = max(dot(normal, lightDir), 0.0);
|
||||
float spec = pow(max(dot(normal, halfDir), 0.0), 64.0) * 0.5;
|
||||
vec3 result = frame.ambientColor * albedo;
|
||||
|
||||
vec3 diffuse = push.lightColor * diff;
|
||||
vec3 specular = push.lightColor * spec;
|
||||
vec3 ambient = push.ambientColor;
|
||||
for (uint i = 0u; i < frame.lightCount; i++)
|
||||
{
|
||||
vec3 lightDir = normalize(-frame.lights[i].direction);
|
||||
vec3 halfDir = normalize(lightDir + viewDir);
|
||||
float diff = max(dot(normal, lightDir), 0.0);
|
||||
float spec = pow(max(dot(normal, halfDir), 0.0), mix(8.0, 128.0, 1.0 - roughness)) * mix(0.5, 1.0, metallic);
|
||||
|
||||
vec3 diffuse = frame.lights[i].color * diff * frame.lights[i].intensity;
|
||||
vec3 specular = frame.lights[i].color * spec * frame.lights[i].intensity;
|
||||
|
||||
result += diffuse * albedo + specular;
|
||||
}
|
||||
|
||||
vec3 result = (ambient + diffuse + specular) * fragColor;
|
||||
outColor = vec4(result, 1.0);
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -7,18 +7,35 @@ layout(location = 2) in vec3 inNormal;
|
||||
layout(location = 0) out vec3 fragColor;
|
||||
layout(location = 1) out vec3 fragNormal;
|
||||
layout(location = 2) out vec3 fragWorldPos;
|
||||
layout(location = 3) out vec2 fragUv;
|
||||
|
||||
struct Light
|
||||
{
|
||||
vec3 direction;
|
||||
float intensity;
|
||||
vec3 color;
|
||||
float _pad;
|
||||
};
|
||||
|
||||
layout(set = 0, binding = 0) uniform FrameConstants
|
||||
{
|
||||
vec3 cameraPosition;
|
||||
uint lightCount;
|
||||
vec3 ambientColor;
|
||||
float _pad;
|
||||
Light lights[4];
|
||||
} frame;
|
||||
|
||||
layout(push_constant) uniform PushConstants
|
||||
{
|
||||
mat4 mvp;
|
||||
vec3 lightDirection;
|
||||
float pad1;
|
||||
vec3 lightColor;
|
||||
float pad2;
|
||||
vec3 ambientColor;
|
||||
float pad3;
|
||||
vec3 cameraPosition;
|
||||
float pad4;
|
||||
vec3 materialAlbedo;
|
||||
float materialRoughness;
|
||||
float materialMetallic;
|
||||
uint useTexture;
|
||||
uint textureIndex;
|
||||
uint _pad0;
|
||||
uint _pad1;
|
||||
} push;
|
||||
|
||||
void main()
|
||||
@@ -27,4 +44,5 @@ void main()
|
||||
fragColor = inColor;
|
||||
fragNormal = inNormal;
|
||||
fragWorldPos = inPosition;
|
||||
fragUv = inPosition.xz * 0.5 + 0.5;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Silk.NET.Vulkan;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace Engine.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// A Vulkan texture: image, device memory, image view, and sampler.
|
||||
/// </summary>
|
||||
public sealed unsafe class Texture : IDisposable
|
||||
{
|
||||
private readonly VulkanContext _context;
|
||||
public Silk.NET.Vulkan.Image Image { get; }
|
||||
public DeviceMemory Memory { get; }
|
||||
public ImageView View { get; }
|
||||
public Sampler Sampler { get; }
|
||||
public uint Width { get; }
|
||||
public uint Height { get; }
|
||||
|
||||
public Texture(VulkanContext context, string path)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
using var image = SixLabors.ImageSharp.Image.Load<Rgba32>(path);
|
||||
Width = (uint)image.Width;
|
||||
Height = (uint)image.Height;
|
||||
|
||||
var pixels = new byte[Width * Height * 4];
|
||||
image.CopyPixelDataTo(pixels);
|
||||
|
||||
Image = CreateImage(Width, Height);
|
||||
var memoryRequirements = GetImageMemoryRequirements(Image);
|
||||
Memory = AllocateMemory(memoryRequirements, MemoryPropertyFlags.DeviceLocalBit);
|
||||
|
||||
var bindResult = _context.Vk.BindImageMemory(_context.Device, Image, Memory, 0);
|
||||
if (bindResult != Result.Success)
|
||||
throw new InvalidOperationException($"vkBindImageMemory failed: {bindResult}");
|
||||
|
||||
UploadPixels(pixels);
|
||||
|
||||
View = CreateImageView(Image);
|
||||
Sampler = CreateSampler();
|
||||
}
|
||||
|
||||
private Silk.NET.Vulkan.Image CreateImage(uint width, uint height)
|
||||
{
|
||||
var createInfo = new ImageCreateInfo
|
||||
{
|
||||
SType = StructureType.ImageCreateInfo,
|
||||
ImageType = ImageType.Type2D,
|
||||
Extent = new Extent3D(width, height, 1),
|
||||
MipLevels = 1,
|
||||
ArrayLayers = 1,
|
||||
Format = Format.R8G8B8A8Srgb,
|
||||
Tiling = ImageTiling.Optimal,
|
||||
InitialLayout = ImageLayout.Undefined,
|
||||
Usage = ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
|
||||
SharingMode = SharingMode.Exclusive,
|
||||
Samples = SampleCountFlags.Count1Bit
|
||||
};
|
||||
|
||||
Silk.NET.Vulkan.Image image;
|
||||
var result = _context.Vk.CreateImage(_context.Device, &createInfo, null, &image);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateImage failed: {result}");
|
||||
return image;
|
||||
}
|
||||
|
||||
private MemoryRequirements GetImageMemoryRequirements(Silk.NET.Vulkan.Image image)
|
||||
{
|
||||
MemoryRequirements requirements;
|
||||
_context.Vk.GetImageMemoryRequirements(_context.Device, image, &requirements);
|
||||
return requirements;
|
||||
}
|
||||
|
||||
private DeviceMemory AllocateMemory(MemoryRequirements requirements, MemoryPropertyFlags properties)
|
||||
{
|
||||
var memoryTypeIndex = FindMemoryType(requirements.MemoryTypeBits, properties);
|
||||
var allocateInfo = new MemoryAllocateInfo
|
||||
{
|
||||
SType = StructureType.MemoryAllocateInfo,
|
||||
AllocationSize = requirements.Size,
|
||||
MemoryTypeIndex = memoryTypeIndex
|
||||
};
|
||||
|
||||
DeviceMemory memory;
|
||||
var result = _context.Vk.AllocateMemory(_context.Device, &allocateInfo, null, &memory);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkAllocateMemory failed: {result}");
|
||||
return memory;
|
||||
}
|
||||
|
||||
private uint FindMemoryType(uint typeFilter, MemoryPropertyFlags properties)
|
||||
{
|
||||
PhysicalDeviceMemoryProperties memoryProperties;
|
||||
_context.Vk.GetPhysicalDeviceMemoryProperties(_context.PhysicalDevice, &memoryProperties);
|
||||
for (var i = 0; i < memoryProperties.MemoryTypeCount; i++)
|
||||
{
|
||||
if ((typeFilter & (1u << i)) != 0 &&
|
||||
(memoryProperties.MemoryTypes[i].PropertyFlags & properties) == properties)
|
||||
{
|
||||
return (uint)i;
|
||||
}
|
||||
}
|
||||
throw new InvalidOperationException("Failed to find suitable memory type for texture.");
|
||||
}
|
||||
|
||||
private void UploadPixels(byte[] pixels)
|
||||
{
|
||||
var imageSize = (ulong)pixels.Length;
|
||||
|
||||
var stagingBuffer = CreateBuffer(imageSize, BufferUsageFlags.TransferSrcBit);
|
||||
var stagingMemory = AllocateStagingMemory(stagingBuffer);
|
||||
|
||||
var bindResult = _context.Vk.BindBufferMemory(_context.Device, stagingBuffer, stagingMemory, 0);
|
||||
if (bindResult != Result.Success)
|
||||
throw new InvalidOperationException($"vkBindBufferMemory for staging failed: {bindResult}");
|
||||
|
||||
void* mappedData;
|
||||
var mapResult = _context.Vk.MapMemory(_context.Device, stagingMemory, 0, imageSize, MemoryMapFlags.None, &mappedData);
|
||||
if (mapResult != Result.Success)
|
||||
throw new InvalidOperationException($"vkMapMemory failed: {mapResult}");
|
||||
|
||||
fixed (byte* src = pixels)
|
||||
{
|
||||
global::System.Buffer.MemoryCopy(src, mappedData, (long)imageSize, pixels.Length);
|
||||
}
|
||||
|
||||
_context.Vk.UnmapMemory(_context.Device, stagingMemory);
|
||||
|
||||
ExecuteOneTimeCommand(cmd =>
|
||||
{
|
||||
TransitionImageLayout(cmd, Image, ImageLayout.Undefined, ImageLayout.TransferDstOptimal);
|
||||
|
||||
var bufferCopy = new BufferImageCopy
|
||||
{
|
||||
BufferOffset = 0,
|
||||
BufferRowLength = 0,
|
||||
BufferImageHeight = 0,
|
||||
ImageSubresource = new ImageSubresourceLayers
|
||||
{
|
||||
AspectMask = ImageAspectFlags.ColorBit,
|
||||
MipLevel = 0,
|
||||
BaseArrayLayer = 0,
|
||||
LayerCount = 1
|
||||
},
|
||||
ImageOffset = new Offset3D(0, 0, 0),
|
||||
ImageExtent = new Extent3D(Width, Height, 1)
|
||||
};
|
||||
|
||||
_context.Vk.CmdCopyBufferToImage(cmd, stagingBuffer, Image, ImageLayout.TransferDstOptimal, 1, &bufferCopy);
|
||||
|
||||
TransitionImageLayout(cmd, Image, ImageLayout.TransferDstOptimal, ImageLayout.ShaderReadOnlyOptimal);
|
||||
});
|
||||
|
||||
_context.Vk.FreeMemory(_context.Device, stagingMemory, null);
|
||||
_context.Vk.DestroyBuffer(_context.Device, stagingBuffer, null);
|
||||
}
|
||||
|
||||
private Silk.NET.Vulkan.Buffer CreateBuffer(ulong size, BufferUsageFlags usage)
|
||||
{
|
||||
var createInfo = new BufferCreateInfo
|
||||
{
|
||||
SType = StructureType.BufferCreateInfo,
|
||||
Size = size,
|
||||
Usage = usage,
|
||||
SharingMode = SharingMode.Exclusive
|
||||
};
|
||||
|
||||
Silk.NET.Vulkan.Buffer buffer;
|
||||
var result = _context.Vk.CreateBuffer(_context.Device, &createInfo, null, &buffer);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateBuffer failed: {result}");
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private DeviceMemory AllocateStagingMemory(Silk.NET.Vulkan.Buffer buffer)
|
||||
{
|
||||
var requirements = GetBufferMemoryRequirements(buffer);
|
||||
return AllocateMemory(requirements, MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit);
|
||||
}
|
||||
|
||||
private MemoryRequirements GetBufferMemoryRequirements(Silk.NET.Vulkan.Buffer buffer)
|
||||
{
|
||||
MemoryRequirements requirements;
|
||||
_context.Vk.GetBufferMemoryRequirements(_context.Device, buffer, &requirements);
|
||||
return requirements;
|
||||
}
|
||||
|
||||
private void TransitionImageLayout(CommandBuffer cmd, Silk.NET.Vulkan.Image image, ImageLayout oldLayout, ImageLayout newLayout)
|
||||
{
|
||||
var barrier = new ImageMemoryBarrier
|
||||
{
|
||||
SType = StructureType.ImageMemoryBarrier,
|
||||
OldLayout = oldLayout,
|
||||
NewLayout = newLayout,
|
||||
SrcQueueFamilyIndex = uint.MaxValue,
|
||||
DstQueueFamilyIndex = uint.MaxValue,
|
||||
Image = image,
|
||||
SubresourceRange = new ImageSubresourceRange
|
||||
{
|
||||
AspectMask = ImageAspectFlags.ColorBit,
|
||||
BaseMipLevel = 0,
|
||||
LevelCount = 1,
|
||||
BaseArrayLayer = 0,
|
||||
LayerCount = 1
|
||||
}
|
||||
};
|
||||
|
||||
var srcStage = PipelineStageFlags.TopOfPipeBit;
|
||||
var dstStage = PipelineStageFlags.TransferBit;
|
||||
AccessFlags srcAccessMask = 0;
|
||||
AccessFlags dstAccessMask = AccessFlags.TransferWriteBit;
|
||||
|
||||
if (oldLayout == ImageLayout.TransferDstOptimal && newLayout == ImageLayout.ShaderReadOnlyOptimal)
|
||||
{
|
||||
srcStage = PipelineStageFlags.TransferBit;
|
||||
dstStage = PipelineStageFlags.FragmentShaderBit;
|
||||
srcAccessMask = AccessFlags.TransferWriteBit;
|
||||
dstAccessMask = AccessFlags.ShaderReadBit;
|
||||
}
|
||||
|
||||
barrier.SrcAccessMask = srcAccessMask;
|
||||
barrier.DstAccessMask = dstAccessMask;
|
||||
|
||||
_context.Vk.CmdPipelineBarrier(cmd, srcStage, dstStage, 0, 0, null, 0, null, 1, &barrier);
|
||||
}
|
||||
|
||||
private void ExecuteOneTimeCommand(Action<CommandBuffer> action)
|
||||
{
|
||||
var allocInfo = new CommandBufferAllocateInfo
|
||||
{
|
||||
SType = StructureType.CommandBufferAllocateInfo,
|
||||
CommandPool = _context.CommandPool,
|
||||
Level = CommandBufferLevel.Primary,
|
||||
CommandBufferCount = 1
|
||||
};
|
||||
|
||||
CommandBuffer commandBuffer;
|
||||
var result = _context.Vk.AllocateCommandBuffers(_context.Device, &allocInfo, &commandBuffer);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkAllocateCommandBuffers failed: {result}");
|
||||
|
||||
var beginInfo = new CommandBufferBeginInfo
|
||||
{
|
||||
SType = StructureType.CommandBufferBeginInfo,
|
||||
Flags = CommandBufferUsageFlags.OneTimeSubmitBit
|
||||
};
|
||||
_context.Vk.BeginCommandBuffer(commandBuffer, &beginInfo);
|
||||
|
||||
action(commandBuffer);
|
||||
|
||||
_context.Vk.EndCommandBuffer(commandBuffer);
|
||||
|
||||
var submitInfo = new SubmitInfo
|
||||
{
|
||||
SType = StructureType.SubmitInfo,
|
||||
CommandBufferCount = 1,
|
||||
PCommandBuffers = &commandBuffer
|
||||
};
|
||||
|
||||
_context.Vk.QueueSubmit(_context.GraphicsQueue, 1, &submitInfo, new Fence());
|
||||
_context.Vk.QueueWaitIdle(_context.GraphicsQueue);
|
||||
|
||||
_context.Vk.FreeCommandBuffers(_context.Device, _context.CommandPool, 1, &commandBuffer);
|
||||
}
|
||||
|
||||
private ImageView CreateImageView(Silk.NET.Vulkan.Image image)
|
||||
{
|
||||
var createInfo = new ImageViewCreateInfo
|
||||
{
|
||||
SType = StructureType.ImageViewCreateInfo,
|
||||
Image = image,
|
||||
ViewType = ImageViewType.Type2D,
|
||||
Format = Format.R8G8B8A8Srgb,
|
||||
SubresourceRange = new ImageSubresourceRange
|
||||
{
|
||||
AspectMask = ImageAspectFlags.ColorBit,
|
||||
BaseMipLevel = 0,
|
||||
LevelCount = 1,
|
||||
BaseArrayLayer = 0,
|
||||
LayerCount = 1
|
||||
}
|
||||
};
|
||||
|
||||
ImageView view;
|
||||
var result = _context.Vk.CreateImageView(_context.Device, &createInfo, null, &view);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateImageView failed: {result}");
|
||||
return view;
|
||||
}
|
||||
|
||||
private Sampler CreateSampler()
|
||||
{
|
||||
var createInfo = new SamplerCreateInfo
|
||||
{
|
||||
SType = StructureType.SamplerCreateInfo,
|
||||
MagFilter = Filter.Linear,
|
||||
MinFilter = Filter.Linear,
|
||||
AddressModeU = SamplerAddressMode.Repeat,
|
||||
AddressModeV = SamplerAddressMode.Repeat,
|
||||
AddressModeW = SamplerAddressMode.Repeat,
|
||||
AnisotropyEnable = false,
|
||||
BorderColor = BorderColor.IntOpaqueBlack,
|
||||
UnnormalizedCoordinates = false,
|
||||
CompareEnable = false,
|
||||
MipmapMode = SamplerMipmapMode.Linear,
|
||||
MipLodBias = 0.0f,
|
||||
MinLod = 0.0f,
|
||||
MaxLod = 1.0f
|
||||
};
|
||||
|
||||
Sampler sampler;
|
||||
var result = _context.Vk.CreateSampler(_context.Device, &createInfo, null, &sampler);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateSampler failed: {result}");
|
||||
return sampler;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_context.Vk.DeviceWaitIdle(_context.Device);
|
||||
_context.Vk.DestroySampler(_context.Device, Sampler, null);
|
||||
_context.Vk.DestroyImageView(_context.Device, View, null);
|
||||
_context.Vk.DestroyImage(_context.Device, Image, null);
|
||||
_context.Vk.FreeMemory(_context.Device, Memory, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using Silk.NET.Vulkan;
|
||||
|
||||
namespace Engine.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// A host-visible, coherent Vulkan buffer for uniform data that is updated every frame.
|
||||
/// </summary>
|
||||
public sealed unsafe class UniformBuffer : IDisposable
|
||||
{
|
||||
private readonly VulkanContext _context;
|
||||
public Silk.NET.Vulkan.Buffer Buffer { get; }
|
||||
public DeviceMemory Memory { get; }
|
||||
public ulong Size { get; }
|
||||
|
||||
public UniformBuffer(VulkanContext context, ulong size)
|
||||
{
|
||||
_context = context;
|
||||
Size = size;
|
||||
|
||||
Buffer = CreateBuffer(Size, BufferUsageFlags.UniformBufferBit);
|
||||
var memoryRequirements = GetMemoryRequirements(Buffer);
|
||||
Memory = AllocateMemory(memoryRequirements, MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit);
|
||||
|
||||
var result = _context.Vk.BindBufferMemory(_context.Device, Buffer, Memory, 0);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkBindBufferMemory failed: {result}");
|
||||
}
|
||||
|
||||
private Silk.NET.Vulkan.Buffer CreateBuffer(ulong size, BufferUsageFlags usage)
|
||||
{
|
||||
var createInfo = new BufferCreateInfo
|
||||
{
|
||||
SType = StructureType.BufferCreateInfo,
|
||||
Size = size,
|
||||
Usage = usage,
|
||||
SharingMode = SharingMode.Exclusive
|
||||
};
|
||||
|
||||
Silk.NET.Vulkan.Buffer buffer;
|
||||
var result = _context.Vk.CreateBuffer(_context.Device, &createInfo, null, &buffer);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateBuffer failed: {result}");
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private MemoryRequirements GetMemoryRequirements(Silk.NET.Vulkan.Buffer buffer)
|
||||
{
|
||||
MemoryRequirements requirements;
|
||||
_context.Vk.GetBufferMemoryRequirements(_context.Device, buffer, &requirements);
|
||||
return requirements;
|
||||
}
|
||||
|
||||
private DeviceMemory AllocateMemory(MemoryRequirements requirements, MemoryPropertyFlags properties)
|
||||
{
|
||||
var memoryTypeIndex = FindMemoryType(requirements.MemoryTypeBits, properties);
|
||||
var allocateInfo = new MemoryAllocateInfo
|
||||
{
|
||||
SType = StructureType.MemoryAllocateInfo,
|
||||
AllocationSize = requirements.Size,
|
||||
MemoryTypeIndex = memoryTypeIndex
|
||||
};
|
||||
|
||||
DeviceMemory memory;
|
||||
var result = _context.Vk.AllocateMemory(_context.Device, &allocateInfo, null, &memory);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkAllocateMemory failed: {result}");
|
||||
return memory;
|
||||
}
|
||||
|
||||
private uint FindMemoryType(uint typeFilter, MemoryPropertyFlags properties)
|
||||
{
|
||||
PhysicalDeviceMemoryProperties memoryProperties;
|
||||
_context.Vk.GetPhysicalDeviceMemoryProperties(_context.PhysicalDevice, &memoryProperties);
|
||||
for (var i = 0; i < memoryProperties.MemoryTypeCount; i++)
|
||||
{
|
||||
if ((typeFilter & (1u << i)) != 0 &&
|
||||
(memoryProperties.MemoryTypes[i].PropertyFlags & properties) == properties)
|
||||
{
|
||||
return (uint)i;
|
||||
}
|
||||
}
|
||||
throw new InvalidOperationException("Failed to find suitable memory type for uniform buffer.");
|
||||
}
|
||||
|
||||
public void Update(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if ((ulong)data.Length != Size)
|
||||
throw new ArgumentException($"Uniform buffer update size mismatch: {data.Length} != {Size}");
|
||||
|
||||
void* mappedData;
|
||||
var result = _context.Vk.MapMemory(_context.Device, Memory, 0, Size, MemoryMapFlags.None, &mappedData);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkMapMemory failed: {result}");
|
||||
|
||||
fixed (byte* src = data)
|
||||
{
|
||||
global::System.Buffer.MemoryCopy(src, mappedData, (long)Size, data.Length);
|
||||
}
|
||||
|
||||
_context.Vk.UnmapMemory(_context.Device, Memory);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_context.Vk.DeviceWaitIdle(_context.Device);
|
||||
_context.Vk.DestroyBuffer(_context.Device, Buffer, null);
|
||||
_context.Vk.FreeMemory(_context.Device, Memory, null);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ public sealed unsafe class VulkanContext : IDisposable
|
||||
public SurfaceKHR Surface { get; private set; }
|
||||
public uint GraphicsFamilyIndex { get; private set; }
|
||||
public uint PresentFamilyIndex { get; private set; }
|
||||
public CommandPool CommandPool { get; private set; }
|
||||
|
||||
public VulkanContext(Sdl3Window window, bool enableValidation = true)
|
||||
{
|
||||
@@ -42,6 +43,23 @@ public sealed unsafe class VulkanContext : IDisposable
|
||||
CreateLogicalDevice();
|
||||
LoadDeviceExtensions();
|
||||
GetQueues();
|
||||
CreateCommandPool();
|
||||
}
|
||||
|
||||
private void CreateCommandPool()
|
||||
{
|
||||
var createInfo = new CommandPoolCreateInfo
|
||||
{
|
||||
SType = StructureType.CommandPoolCreateInfo,
|
||||
QueueFamilyIndex = GraphicsFamilyIndex,
|
||||
Flags = CommandPoolCreateFlags.ResetCommandBufferBit
|
||||
};
|
||||
|
||||
CommandPool commandPool;
|
||||
var result = Vk.CreateCommandPool(Device, &createInfo, null, &commandPool);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateCommandPool failed: {result}");
|
||||
CommandPool = commandPool;
|
||||
}
|
||||
|
||||
private void CreateInstance(Sdl3Window window, bool enableValidation)
|
||||
@@ -290,6 +308,8 @@ public sealed unsafe class VulkanContext : IDisposable
|
||||
_disposed = true;
|
||||
|
||||
Vk.DeviceWaitIdle(Device);
|
||||
if (CommandPool.Handle != 0)
|
||||
Vk.DestroyCommandPool(Device, CommandPool, null);
|
||||
if (Device.Handle != 0)
|
||||
Vk.DestroyDevice(Device, null);
|
||||
if (Surface.Handle != 0)
|
||||
|
||||
@@ -15,6 +15,8 @@ public sealed unsafe class VulkanPipeline : IDisposable
|
||||
|
||||
public Pipeline Handle { get; }
|
||||
public PipelineLayout Layout { get; }
|
||||
public DescriptorSetLayout FrameDescriptorSetLayout { get; }
|
||||
public DescriptorSetLayout TextureDescriptorSetLayout { get; }
|
||||
private readonly ShaderModule _vertexModule;
|
||||
private readonly ShaderModule _fragmentModule;
|
||||
|
||||
@@ -26,6 +28,8 @@ public sealed unsafe class VulkanPipeline : IDisposable
|
||||
_vertexModule = CreateShaderModule("vertex.spv");
|
||||
_fragmentModule = CreateShaderModule("fragment.spv");
|
||||
|
||||
FrameDescriptorSetLayout = CreateFrameDescriptorSetLayout();
|
||||
TextureDescriptorSetLayout = CreateTextureDescriptorSetLayout();
|
||||
Layout = CreatePipelineLayout();
|
||||
Handle = CreateGraphicsPipeline();
|
||||
}
|
||||
@@ -53,19 +57,69 @@ public sealed unsafe class VulkanPipeline : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private DescriptorSetLayout CreateFrameDescriptorSetLayout()
|
||||
{
|
||||
var binding = new DescriptorSetLayoutBinding
|
||||
{
|
||||
Binding = 0,
|
||||
DescriptorType = DescriptorType.UniformBuffer,
|
||||
DescriptorCount = 1,
|
||||
StageFlags = ShaderStageFlags.VertexBit | ShaderStageFlags.FragmentBit
|
||||
};
|
||||
|
||||
var createInfo = new DescriptorSetLayoutCreateInfo
|
||||
{
|
||||
SType = StructureType.DescriptorSetLayoutCreateInfo,
|
||||
BindingCount = 1,
|
||||
PBindings = &binding
|
||||
};
|
||||
|
||||
DescriptorSetLayout layout;
|
||||
var result = _context.Vk.CreateDescriptorSetLayout(_context.Device, &createInfo, null, &layout);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateDescriptorSetLayout failed: {result}");
|
||||
return layout;
|
||||
}
|
||||
|
||||
private DescriptorSetLayout CreateTextureDescriptorSetLayout()
|
||||
{
|
||||
var binding = new DescriptorSetLayoutBinding
|
||||
{
|
||||
Binding = 0,
|
||||
DescriptorType = DescriptorType.CombinedImageSampler,
|
||||
DescriptorCount = 1,
|
||||
StageFlags = ShaderStageFlags.FragmentBit
|
||||
};
|
||||
|
||||
var createInfo = new DescriptorSetLayoutCreateInfo
|
||||
{
|
||||
SType = StructureType.DescriptorSetLayoutCreateInfo,
|
||||
BindingCount = 1,
|
||||
PBindings = &binding
|
||||
};
|
||||
|
||||
DescriptorSetLayout layout;
|
||||
var result = _context.Vk.CreateDescriptorSetLayout(_context.Device, &createInfo, null, &layout);
|
||||
if (result != Result.Success)
|
||||
throw new InvalidOperationException($"vkCreateDescriptorSetLayout (texture) failed: {result}");
|
||||
return layout;
|
||||
}
|
||||
|
||||
private PipelineLayout CreatePipelineLayout()
|
||||
{
|
||||
var pushConstantRange = new PushConstantRange
|
||||
{
|
||||
StageFlags = ShaderStageFlags.VertexBit | ShaderStageFlags.FragmentBit,
|
||||
Offset = 0,
|
||||
Size = (uint)(32 * sizeof(float))
|
||||
Size = 96
|
||||
};
|
||||
|
||||
var setLayouts = stackalloc DescriptorSetLayout[] { FrameDescriptorSetLayout, TextureDescriptorSetLayout };
|
||||
var createInfo = new PipelineLayoutCreateInfo
|
||||
{
|
||||
SType = StructureType.PipelineLayoutCreateInfo,
|
||||
SetLayoutCount = 0,
|
||||
SetLayoutCount = 2,
|
||||
PSetLayouts = setLayouts,
|
||||
PushConstantRangeCount = 1,
|
||||
PPushConstantRanges = &pushConstantRange
|
||||
};
|
||||
@@ -244,6 +298,8 @@ public sealed unsafe class VulkanPipeline : IDisposable
|
||||
_context.Vk.DeviceWaitIdle(_context.Device);
|
||||
_context.Vk.DestroyPipeline(_context.Device, Handle, null);
|
||||
_context.Vk.DestroyPipelineLayout(_context.Device, Layout, null);
|
||||
_context.Vk.DestroyDescriptorSetLayout(_context.Device, FrameDescriptorSetLayout, null);
|
||||
_context.Vk.DestroyDescriptorSetLayout(_context.Device, TextureDescriptorSetLayout, null);
|
||||
_context.Vk.DestroyShaderModule(_context.Device, _vertexModule, null);
|
||||
_context.Vk.DestroyShaderModule(_context.Device, _fragmentModule, null);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user