</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DefineConstants>NETSTANDARD;NETSTANDARD2_0;WITH_SHADOWS;_WITH_VKVG</DefineConstants>
- </PropertyGroup>
-
- <ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <ProjectReference Include="$(RootDirectory)vke\vke.csproj" />
- </ItemGroup>
- <ItemGroup >
- <PackageReference Include="vke" Version="0.1.7-beta" Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "/>
- </ItemGroup>
+ </PropertyGroup>
<ItemGroup>
+ <PackageReference Include="vke" Version="0.1.7-beta" />
<PackageReference Include="SpirVTasks" Version="0.1.9-beta" />
<PackageReference Include="Vulkan" Version="0.1.4" />
</ItemGroup>
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using VK;
+
+namespace CVKL {
+ public class EnvironmentCube : GraphicPipeline {
+
+ GPUBuffer vboSkybox;
+
+ public Image cubemap { get; private set; }
+ public Image lutBrdf { get; private set; }
+ public Image irradianceCube { get; private set; }
+ public Image prefilterCube { get; set; }
+
+ public EnvironmentCube (string cubemapPath, DescriptorSet dsSkybox, PipelineLayout plLayout, Queue staggingQ, RenderPass renderPass, PipelineCache cache = null)
+ : base (renderPass, cache, "EnvCube pipeline") {
+
+ using (CommandPool cmdPool = new CommandPool (staggingQ.Dev, staggingQ.index)) {
+
+ vboSkybox = new GPUBuffer<float> (staggingQ, cmdPool, VkBufferUsageFlags.VertexBuffer, box_vertices);
+
+ cubemap = KTX.KTX.Load (staggingQ, cmdPool, cubemapPath,
+ VkImageUsageFlags.Sampled, VkMemoryPropertyFlags.DeviceLocal, true);
+ cubemap.CreateView (VkImageViewType.Cube, VkImageAspectFlags.Color);
+ cubemap.CreateSampler (VkSamplerAddressMode.ClampToEdge);
+ cubemap.SetName ("skybox Texture");
+ cubemap.Descriptor.imageLayout = VkImageLayout.ShaderReadOnlyOptimal;
+
+ GraphicPipelineConfig cfg = GraphicPipelineConfig.CreateDefault (VkPrimitiveTopology.TriangleList, renderPass.Samples, false);
+ cfg.RenderPass = renderPass;
+ cfg.Layout = plLayout;
+ cfg.AddVertexBinding (0, 3 * sizeof (float));
+ cfg.AddVertexAttributes (0, VkFormat.R32g32b32Sfloat);
+ cfg.AddShader (VkShaderStageFlags.Vertex, "#CVKLEnvironment.skybox.vert.spv");
+ cfg.AddShader (VkShaderStageFlags.Fragment, "#CVKLEnvironment.skybox.frag.spv");
+ cfg.multisampleState.rasterizationSamples = Samples;
+
+ layout = cfg.Layout;
+
+ init (cfg);
+
+ generateBRDFLUT (staggingQ, cmdPool);
+ generateCubemaps (staggingQ, cmdPool);
+ }
+
+ }
+
+ public void RecordDraw (CommandBuffer cmd) {
+ Bind (cmd);
+ cmd.BindVertexBuffer (vboSkybox);
+ cmd.Draw (36);
+ }
+
+ #region skybox
+
+ static float[] box_vertices = {
+ 1.0f, 1.0f,-1.0f, // +X side
+ 1.0f, 1.0f, 1.0f,
+ 1.0f,-1.0f, 1.0f,
+ 1.0f,-1.0f, 1.0f,
+ 1.0f,-1.0f,-1.0f,
+ 1.0f, 1.0f,-1.0f,
+
+ -1.0f,-1.0f,-1.0f, // +X side
+ -1.0f,-1.0f, 1.0f,
+ -1.0f, 1.0f, 1.0f,
+ -1.0f, 1.0f, 1.0f,
+ -1.0f, 1.0f,-1.0f,
+ -1.0f,-1.0f,-1.0f,
+
+ -1.0f, 1.0f,-1.0f, // +Y side
+ -1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+ -1.0f, 1.0f,-1.0f,
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f,-1.0f,
+
+ -1.0f,-1.0f,-1.0f, // -Y side
+ 1.0f,-1.0f,-1.0f,
+ 1.0f,-1.0f, 1.0f,
+ -1.0f,-1.0f,-1.0f,
+ 1.0f,-1.0f, 1.0f,
+ -1.0f,-1.0f, 1.0f,
+
+ -1.0f, 1.0f, 1.0f, // +Z side
+ -1.0f,-1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+ -1.0f,-1.0f, 1.0f,
+ 1.0f,-1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+
+ -1.0f,-1.0f,-1.0f, // -Z side
+ 1.0f, 1.0f,-1.0f,
+ 1.0f,-1.0f,-1.0f,
+ -1.0f,-1.0f,-1.0f,
+ -1.0f, 1.0f,-1.0f,
+ 1.0f, 1.0f,-1.0f,
+
+ };
+ #endregion
+
+ void generateBRDFLUT (Queue staggingQ, CommandPool cmdPool) {
+ const VkFormat format = VkFormat.R16g16Sfloat;
+ const int dim = 512;
+
+ lutBrdf = new Image (Dev, format, VkImageUsageFlags.ColorAttachment | VkImageUsageFlags.Sampled,
+ VkMemoryPropertyFlags.DeviceLocal, dim, dim);
+ lutBrdf.SetName ("lutBrdf");
+
+ lutBrdf.CreateView ();
+ lutBrdf.CreateSampler (VkSamplerAddressMode.ClampToEdge);
+
+ GraphicPipelineConfig cfg = GraphicPipelineConfig.CreateDefault (VkPrimitiveTopology.TriangleList, VkSampleCountFlags.SampleCount1, false);
+
+ cfg.Layout = new PipelineLayout (Dev, new DescriptorSetLayout (Dev));
+ cfg.RenderPass = new RenderPass (Dev);
+ cfg.RenderPass.AddAttachment (format, VkImageLayout.ShaderReadOnlyOptimal);
+ cfg.RenderPass.ClearValues.Add (new VkClearValue { color = new VkClearColorValue (0, 0, 0) });
+ cfg.RenderPass.AddSubpass (new SubPass (VkImageLayout.ColorAttachmentOptimal));
+ cfg.AddShader (VkShaderStageFlags.Vertex, "#CVKLEnvironment.genbrdflut.vert.spv");
+ cfg.AddShader (VkShaderStageFlags.Fragment, "#CVKLEnvironment.genbrdflut.frag.spv");
+
+ using (GraphicPipeline pl = new GraphicPipeline (cfg)) {
+ using (Framebuffer fb = new Framebuffer (cfg.RenderPass, dim, dim, lutBrdf)) {
+ CommandBuffer cmd = cmdPool.AllocateCommandBuffer ();
+ cmd.Start (VkCommandBufferUsageFlags.OneTimeSubmit);
+ pl.RenderPass.Begin (cmd, fb);
+ cmd.SetViewport (dim, dim);
+ cmd.SetScissor (dim, dim);
+ pl.Bind (cmd);
+ cmd.Draw (3, 1, 0, 0);
+ pl.RenderPass.End (cmd);
+ cmd.End ();
+
+ staggingQ.Submit (cmd);
+ staggingQ.WaitIdle ();
+
+ cmd.Free ();
+ }
+ }
+ lutBrdf.Descriptor.imageLayout = VkImageLayout.ShaderReadOnlyOptimal;
+ }
+
+ public enum CBTarget { IRRADIANCE = 0, PREFILTEREDENV = 1 };
+
+ public Image generateCubeMap (Queue staggingQ, CommandPool cmdPool, CBTarget target) {
+ const float deltaPhi = (2.0f * (float)Math.PI) / 180.0f;
+ const float deltaTheta = (0.5f * (float)Math.PI) / 64.0f;
+
+ VkFormat format = VkFormat.R32g32b32a32Sfloat;
+ uint dim = 64;
+
+ if (target == CBTarget.PREFILTEREDENV) {
+ format = VkFormat.R16g16b16a16Sfloat;
+ dim = 512;
+ }
+
+ uint numMips = (uint)Math.Floor (Math.Log (dim, 2)) + 1;
+
+ Image imgFbOffscreen = new Image (Dev, format, VkImageUsageFlags.TransferSrc | VkImageUsageFlags.ColorAttachment,
+ VkMemoryPropertyFlags.DeviceLocal, dim, dim);
+ imgFbOffscreen.SetName ("offscreenfb");
+ imgFbOffscreen.CreateView ();
+
+ Image cmap = new Image (Dev, format, VkImageUsageFlags.TransferDst | VkImageUsageFlags.Sampled,
+ VkMemoryPropertyFlags.DeviceLocal, dim, dim, VkImageType.Image2D, VkSampleCountFlags.SampleCount1, VkImageTiling.Optimal,
+ numMips, 6, 1, VkImageCreateFlags.CubeCompatible);
+ if (target == CBTarget.PREFILTEREDENV)
+ cmap.SetName ("prefilterenvmap");
+ else
+ cmap.SetName ("irradianceCube");
+ cmap.CreateView (VkImageViewType.Cube, VkImageAspectFlags.Color, 6, 0);
+ cmap.CreateSampler (VkSamplerAddressMode.ClampToEdge);
+
+ DescriptorPool dsPool = new DescriptorPool (Dev, 2, new VkDescriptorPoolSize (VkDescriptorType.CombinedImageSampler));
+
+ DescriptorSetLayout dsLayout = new DescriptorSetLayout (Dev,
+ new VkDescriptorSetLayoutBinding (0, VkShaderStageFlags.Fragment, VkDescriptorType.CombinedImageSampler));
+
+
+ GraphicPipelineConfig cfg = GraphicPipelineConfig.CreateDefault (VkPrimitiveTopology.TriangleList, VkSampleCountFlags.SampleCount1, false);
+ cfg.Layout = new PipelineLayout (Dev, dsLayout);
+ cfg.Layout.AddPushConstants (
+ new VkPushConstantRange (VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, (uint)Marshal.SizeOf<Matrix4x4> () + 8));
+
+ cfg.RenderPass = new RenderPass (Dev);
+ cfg.RenderPass.AddAttachment (format, VkImageLayout.ColorAttachmentOptimal);
+ cfg.RenderPass.ClearValues.Add (new VkClearValue { color = new VkClearColorValue (0, 0, 0) });
+ cfg.RenderPass.AddSubpass (new SubPass (VkImageLayout.ColorAttachmentOptimal));
+
+ cfg.AddVertexBinding (0, 3 * sizeof (float));
+ cfg.AddVertexAttributes (0, VkFormat.R32g32b32Sfloat);
+
+ cfg.AddShader (VkShaderStageFlags.Vertex, "#CVKLEnvironment.filtercube.vert.spv");
+ if (target == CBTarget.PREFILTEREDENV)
+ cfg.AddShader (VkShaderStageFlags.Fragment, "#CVKLEnvironment.prefilterenvmap.frag.spv");
+ else
+ cfg.AddShader (VkShaderStageFlags.Fragment, "#CVKLEnvironment.irradiancecube.frag.spv");
+
+ Matrix4x4[] matrices = {
+ // POSITIVE_X
+ Matrix4x4.CreateRotationX(Utils.DegreesToRadians(180)) * Matrix4x4.CreateRotationY(Utils.DegreesToRadians(90)),
+ // NEGATIVE_X
+ Matrix4x4.CreateRotationX(Utils.DegreesToRadians(180)) * Matrix4x4.CreateRotationY(Utils.DegreesToRadians(-90)),
+ // POSITIVE_Y
+ Matrix4x4.CreateRotationX(Utils.DegreesToRadians(-90)),
+ // NEGATIVE_Y
+ Matrix4x4.CreateRotationX(Utils.DegreesToRadians(90)),
+ // POSITIVE_Z
+ Matrix4x4.CreateRotationX(Utils.DegreesToRadians(180)),
+ // NEGATIVE_Z
+ Matrix4x4.CreateRotationZ(Utils.DegreesToRadians(180))
+ };
+
+ VkImageSubresourceRange subRes = new VkImageSubresourceRange (VkImageAspectFlags.Color, 0, numMips, 0, 6);
+
+ using (GraphicPipeline pl = new GraphicPipeline (cfg)) {
+
+ DescriptorSet dset = dsPool.Allocate (dsLayout);
+ DescriptorSetWrites dsUpdate = new DescriptorSetWrites (dsLayout);
+ dsUpdate.Write (Dev, dset, cubemap.Descriptor);
+ Dev.WaitIdle ();
+
+ using (Framebuffer fb = new Framebuffer (pl.RenderPass, dim, dim, imgFbOffscreen)) {
+ CommandBuffer cmd = cmdPool.AllocateCommandBuffer ();
+ cmd.Start (VkCommandBufferUsageFlags.OneTimeSubmit);
+
+ cmap.SetLayout (cmd, VkImageLayout.Undefined, VkImageLayout.TransferDstOptimal, subRes);
+
+ float roughness = 0;
+
+ cmd.SetScissor (dim, dim);
+ cmd.SetViewport ((float)(dim), (float)dim);
+
+ for (int m = 0; m < numMips; m++) {
+ roughness = (float)m / ((float)numMips - 1f);
+
+ for (int f = 0; f < 6; f++) {
+ pl.RenderPass.Begin (cmd, fb);
+
+ pl.Bind (cmd);
+
+ float viewPortSize = (float)Math.Pow (0.5, m) * dim;
+ cmd.SetViewport (viewPortSize, viewPortSize);
+ cmd.PushConstant (pl.Layout, VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment,
+ matrices[f] * Matrix4x4.CreatePerspectiveFieldOfView (Utils.DegreesToRadians (90), 1f, 0.1f, 512f));
+ if (target == CBTarget.IRRADIANCE) {
+ cmd.PushConstant (pl.Layout, VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, deltaPhi, (uint)Marshal.SizeOf<Matrix4x4> ());
+ cmd.PushConstant (pl.Layout, VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, deltaTheta, (uint)Marshal.SizeOf<Matrix4x4> () + 4);
+ } else {
+ cmd.PushConstant (pl.Layout, VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, roughness, (uint)Marshal.SizeOf<Matrix4x4> ());
+ cmd.PushConstant (pl.Layout, VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, 64u, (uint)Marshal.SizeOf<Matrix4x4> () + 4);
+ }
+
+ cmd.BindDescriptorSet (pl.Layout, dset);
+ cmd.BindVertexBuffer (vboSkybox);
+ cmd.Draw (36);
+
+ pl.RenderPass.End (cmd);
+
+ imgFbOffscreen.SetLayout (cmd, VkImageAspectFlags.Color,
+ VkImageLayout.ColorAttachmentOptimal, VkImageLayout.TransferSrcOptimal);
+
+ VkImageCopy region = new VkImageCopy ();
+ region.srcSubresource = new VkImageSubresourceLayers (VkImageAspectFlags.Color, 1);
+ region.dstSubresource = new VkImageSubresourceLayers (VkImageAspectFlags.Color, 1, (uint)m, (uint)f);
+ region.extent = new VkExtent3D { width = (uint)viewPortSize, height = (uint)viewPortSize, depth = 1 };
+
+ Vk.vkCmdCopyImage (cmd.Handle,
+ imgFbOffscreen.Handle, VkImageLayout.TransferSrcOptimal,
+ cmap.Handle, VkImageLayout.TransferDstOptimal,
+ 1, region.Pin ());
+ region.Unpin ();
+
+ imgFbOffscreen.SetLayout (cmd, VkImageAspectFlags.Color,
+ VkImageLayout.TransferSrcOptimal, VkImageLayout.ColorAttachmentOptimal);
+
+ }
+ }
+
+ cmap.SetLayout (cmd, VkImageLayout.TransferDstOptimal, VkImageLayout.ShaderReadOnlyOptimal, subRes);
+
+ cmd.End ();
+
+ staggingQ.Submit (cmd);
+ staggingQ.WaitIdle ();
+
+ cmd.Free ();
+ }
+ }
+ cmap.Descriptor.imageLayout = VkImageLayout.ShaderReadOnlyOptimal;
+
+ dsLayout.Dispose ();
+ imgFbOffscreen.Dispose ();
+ dsPool.Dispose ();
+
+ return cmap;
+ }
+
+ void generateCubemaps (Queue staggingQ, CommandPool cmdPool) {
+ irradianceCube = generateCubeMap (staggingQ, cmdPool, CBTarget.IRRADIANCE);
+ prefilterCube = generateCubeMap (staggingQ, cmdPool, CBTarget.PREFILTEREDENV);
+ }
+
+ protected override void Dispose (bool disposing) {
+ vboSkybox.Dispose ();
+ cubemap.Dispose ();
+ lutBrdf.Dispose ();
+ irradianceCube.Dispose ();
+ prefilterCube.Dispose ();
+
+ base.Dispose (disposing);
+ }
+ }
+
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <Import Project="$(RootDirectory)netfx.props" />
+ <PropertyGroup>
+ <AssemblyName>CVKLEnvironment</AssemblyName>
+ <PackageId>CVKLEnvironmentPipeline</PackageId>
+ <AssemblyVersion>0.1.0</AssemblyVersion>
+ <Description>CVKL Environment cube</Description>
+ <PackageTags>vulkan CVKL</PackageTags>
+ <PackageVersion>$(AssemblyVersion)-beta</PackageVersion>
+ <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
+ <PackageRequireLicenseAcceptance>False</PackageRequireLicenseAcceptance>
+ <PackageProjectUrl>https://github.com/jpbruyere/vk.net/blob/master/README.md</PackageProjectUrl>
+ <License>MIT</License>
+ <PackageReleaseNotes></PackageReleaseNotes>
+ <SynchReleaseVersion>false</SynchReleaseVersion>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+</Project>
--- /dev/null
+#version 450
+
+layout (location = 0) in vec3 inPos;
+
+layout(push_constant) uniform PushConsts {
+ layout (offset = 0) mat4 mvp;
+} pushConsts;
+
+layout (location = 0) out vec3 outUVW;
+
+out gl_PerVertex {
+ vec4 gl_Position;
+};
+
+void main()
+{
+ outUVW = inPos;
+ gl_Position = pushConsts.mvp * vec4(inPos.xyz, 1.0);
+}
--- /dev/null
+#version 450
+
+layout (location = 0) in vec2 inUV;
+layout (location = 0) out vec4 outColor;
+layout (constant_id = 0) const uint NUM_SAMPLES = 1024u;
+
+const float PI = 3.1415926536;
+
+// Based omn http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/
+float random(vec2 co)
+{
+ float a = 12.9898;
+ float b = 78.233;
+ float c = 43758.5453;
+ float dt= dot(co.xy ,vec2(a,b));
+ float sn= mod(dt,3.14);
+ return fract(sin(sn) * c);
+}
+
+vec2 hammersley2d(uint i, uint N)
+{
+ // Radical inverse based on http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
+ uint bits = (i << 16u) | (i >> 16u);
+ bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+ bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+ bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+ bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+ float rdi = float(bits) * 2.3283064365386963e-10;
+ return vec2(float(i) /float(N), rdi);
+}
+
+// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf
+vec3 importanceSample_GGX(vec2 Xi, float roughness, vec3 normal)
+{
+ // Maps a 2D point to a hemisphere with spread based on roughness
+ float alpha = roughness * roughness;
+ float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1;
+ float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y));
+ float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
+ vec3 H = vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
+
+ // Tangent space
+ vec3 up = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
+ vec3 tangentX = normalize(cross(up, normal));
+ vec3 tangentY = normalize(cross(normal, tangentX));
+
+ // Convert to world Space
+ return normalize(tangentX * H.x + tangentY * H.y + normal * H.z);
+}
+
+// Geometric Shadowing function
+float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness)
+{
+ float k = (roughness * roughness) / 2.0;
+ float GL = dotNL / (dotNL * (1.0 - k) + k);
+ float GV = dotNV / (dotNV * (1.0 - k) + k);
+ return GL * GV;
+}
+
+vec2 BRDF(float NoV, float roughness)
+{
+ // Normal always points along z-axis for the 2D lookup
+ const vec3 N = vec3(0.0, 0.0, 1.0);
+ vec3 V = vec3(sqrt(1.0 - NoV*NoV), 0.0, NoV);
+
+ vec2 LUT = vec2(0.0);
+ for(uint i = 0u; i < NUM_SAMPLES; i++) {
+ vec2 Xi = hammersley2d(i, NUM_SAMPLES);
+ vec3 H = importanceSample_GGX(Xi, roughness, N);
+ vec3 L = 2.0 * dot(V, H) * H - V;
+
+ float dotNL = max(dot(N, L), 0.0);
+ float dotNV = max(dot(N, V), 0.0);
+ float dotVH = max(dot(V, H), 0.0);
+ float dotNH = max(dot(H, N), 0.0);
+
+ if (dotNL > 0.0) {
+ float G = G_SchlicksmithGGX(dotNL, dotNV, roughness);
+ float G_Vis = (G * dotVH) / (dotNH * dotNV);
+ float Fc = pow(1.0 - dotVH, 5.0);
+ LUT += vec2((1.0 - Fc) * G_Vis, Fc * G_Vis);
+ }
+ }
+ return LUT / float(NUM_SAMPLES);
+}
+
+void main()
+{
+ outColor = vec4(BRDF(inUV.s, 1.0-inUV.t), 0.0, 1.0);
+}
\ No newline at end of file
--- /dev/null
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+void main()
+{
+ outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+ gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
+}
\ No newline at end of file
--- /dev/null
+// Generates an irradiance cube from an environment map using convolution
+
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 0) out vec4 outColor;
+layout (binding = 0) uniform samplerCube samplerEnv;
+
+layout(push_constant) uniform PushConsts {
+ layout (offset = 64) float deltaPhi;
+ layout (offset = 68) float deltaTheta;
+} consts;
+
+#define PI 3.1415926535897932384626433832795
+
+void main()
+{
+ vec3 N = normalize(inPos);
+ vec3 up = vec3(0.0, 1.0, 0.0);
+ vec3 right = normalize(cross(up, N));
+ up = cross(N, right);
+
+ const float TWO_PI = PI * 2.0;
+ const float HALF_PI = PI * 0.5;
+
+ vec3 color = vec3(0.0);
+ uint sampleCount = 0u;
+ for (float phi = 0.0; phi < TWO_PI; phi += consts.deltaPhi) {
+ for (float theta = 0.0; theta < HALF_PI; theta += consts.deltaTheta) {
+ vec3 tempVec = cos(phi) * right + sin(phi) * up;
+ vec3 sampleVector = cos(theta) * N + sin(theta) * tempVec;
+ color += texture(samplerEnv, sampleVector).rgb * cos(theta) * sin(theta);
+ sampleCount++;
+ }
+ }
+ outColor = vec4(PI * color / float(sampleCount), 1.0);//texture(samplerEnv, inPos).rgba;
+}
--- /dev/null
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 0) out vec4 outColor;
+
+layout (binding = 0) uniform samplerCube samplerEnv;
+
+layout(push_constant) uniform PushConsts {
+ layout (offset = 64) float roughness;
+ layout (offset = 68) uint numSamples;
+} consts;
+
+const float PI = 3.1415926536;
+
+// Based omn http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/
+float random(vec2 co)
+{
+ float a = 12.9898;
+ float b = 78.233;
+ float c = 43758.5453;
+ float dt= dot(co.xy ,vec2(a,b));
+ float sn= mod(dt,3.14);
+ return fract(sin(sn) * c);
+}
+
+vec2 hammersley2d(uint i, uint N)
+{
+ // Radical inverse based on http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
+ uint bits = (i << 16u) | (i >> 16u);
+ bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+ bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+ bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+ bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+ float rdi = float(bits) * 2.3283064365386963e-10;
+ return vec2(float(i) /float(N), rdi);
+}
+
+// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf
+vec3 importanceSample_GGX(vec2 Xi, float roughness, vec3 normal)
+{
+ // Maps a 2D point to a hemisphere with spread based on roughness
+ float alpha = roughness * roughness;
+ float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1;
+ float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y));
+ float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
+ vec3 H = vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
+
+ // Tangent space
+ vec3 up = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
+ vec3 tangentX = normalize(cross(up, normal));
+ vec3 tangentY = normalize(cross(normal, tangentX));
+
+ // Convert to world Space
+ return normalize(tangentX * H.x + tangentY * H.y + normal * H.z);
+}
+
+// Normal Distribution function
+float D_GGX(float dotNH, float roughness)
+{
+ float alpha = roughness * roughness;
+ float alpha2 = alpha * alpha;
+ float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
+ return (alpha2)/(PI * denom*denom);
+}
+
+vec3 prefilterEnvMap(vec3 R, float roughness)
+{
+ vec3 N = R;
+ vec3 V = R;
+ vec3 color = vec3(0.0);
+ float totalWeight = 0.0;
+ float envMapDim = float(textureSize(samplerEnv, 0).s);
+ for(uint i = 0u; i < consts.numSamples; i++) {
+ vec2 Xi = hammersley2d(i, consts.numSamples);
+ vec3 H = importanceSample_GGX(Xi, roughness, N);
+ vec3 L = 2.0 * dot(V, H) * H - V;
+ float dotNL = clamp(dot(N, L), 0.0, 1.0);
+ if(dotNL > 0.0) {
+ // Filtering based on https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-lighting/
+
+ float dotNH = clamp(dot(N, H), 0.0, 1.0);
+ float dotVH = clamp(dot(V, H), 0.0, 1.0);
+
+ // Probability Distribution Function
+ float pdf = D_GGX(dotNH, roughness) * dotNH / (4.0 * dotVH) + 0.0001;
+ // Slid angle of current smple
+ float omegaS = 1.0 / (float(consts.numSamples) * pdf);
+ // Solid angle of 1 pixel across all cube faces
+ float omegaP = 4.0 * PI / (6.0 * envMapDim * envMapDim);
+ // Biased (+1.0) mip level for better result
+ float mipLevel = roughness == 0.0 ? 0.0 : max(0.5 * log2(omegaS / omegaP) + 1.0, 0.0f);
+ color += textureLod(samplerEnv, L, mipLevel).rgb * dotNL;
+ totalWeight += dotNL;
+
+ }
+ }
+ return (color / totalWeight);
+}
+
+
+void main()
+{
+ vec3 N = normalize(inPos);
+ outColor = vec4(prefilterEnvMap(N, consts.roughness), 1.0);
+}
--- /dev/null
+#version 450
+
+layout (binding = 2) uniform samplerCube samplerEnv;
+
+layout (set = 0, location = 0) in vec3 inUVW;
+
+layout (set = 0, location = 0) out vec4 outColor;
+
+void main()
+{
+ outColor = vec4(textureLod(samplerEnv, inUVW, 1.5).rgb, 1.0);
+}
\ No newline at end of file
--- /dev/null
+#version 450
+
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_ARB_shading_language_420pack : enable
+
+layout (location = 0) in vec3 inPos;
+
+layout (binding = 0) uniform UBO
+{
+ mat4 projection;
+ mat4 model;
+ mat4 view;
+} ubo;
+
+layout (location = 0) out vec3 outUVW;
+
+out gl_PerVertex
+{
+ vec4 gl_Position;
+};
+
+void main()
+{
+ outUVW = inPos;
+ outUVW.y = -outUVW.y;
+ gl_Position = ubo.projection * mat4(mat3(ubo.view)) * vec4(inPos, 1.0);
+}
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DefineConstants>NETSTANDARD;NETSTANDARD2_0;WITH_SHADOWS;_WITH_VKVG</DefineConstants>
</PropertyGroup>
-
- <ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <ProjectReference Include="$(RootDirectory)vke\vke.csproj" />
- </ItemGroup>
- <ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <PackageReference Include="vke" Version="0.1.7-beta" />
- </ItemGroup>
-
+
<ItemGroup>
+ <PackageReference Include="vke" Version="0.1.7-beta" />
<PackageReference Include="SpirVTasks" Version="0.1.9-beta" />
<PackageReference Include="Vulkan" Version="0.1.4" />
</ItemGroup>
class Program : VkWindow {
static void Main (string[] args) {
+ SwapChain.PREFERED_FORMAT = VkFormat.B8g8r8a8Unorm;
#if DEBUG
Instance.VALIDATION = true;
Instance.DEBUG_UTILS = true;
float rotSpeed = 0.01f, zoomSpeed = 0.01f;
float rotX, rotY, rotZ = 0f, zoom = 1f;
+ float rotAnim = 1f;
+
struct Matrices {
public Matrix4x4 projection;
public Matrix4x4 view;
Program () : base () {
- font = new BMFont (Utils.DataDirectory + "font.fnt");
+ font = new BMFont ("../data/font.fnt");
vbo = new GPUBuffer<float> (dev, VkBufferUsageFlags.VertexBuffer, 1024);
ibo = new GPUBuffer<ushort> (dev, VkBufferUsageFlags.IndexBuffer, 2048);
staggingVbo.Dispose ();
staggingIbo.Dispose ();
+
+ UpdateFrequency = 10;
}
buildCommandBuffers ();
rebuildBuffers = false;
}
+ if (rotAnim < 0.000001f)
+ return;
+ rotY += rotAnim;
+ rotAnim *= 0.95f;
+ updateViewRequested = true;
}
void buildCommandBuffers () {
protected override void onKeyDown (Key key, int scanCode, Modifier modifiers) {
switch (key) {
+ case Key.Space:
+ rotAnim += 0.1f;
+ break;
case Key.F2:
if (modifiers.HasFlag (Modifier.Shift))
outlineColor.W -= 0.01f;
}
}
protected override void OnResize () {
+ base.OnResize ();
updateViewRequested = true;
}
protected override void OnResize () {
+ base.OnResize();
if (frameBuffers != null)
for (int i = 0; i < swapChain.ImageCount; ++i)
}
protected override void OnResize () {
+ base.OnResize();
updateMatrices ();
}
protected override void OnResize () {
- dev.WaitIdle ();
+ base.OnResize ();
#if WITH_VKVG
- vkvgPipeline.Resize ((int)swapChain.Width, (int)swapChain.Height, new DescriptorSetWrites (dsVkvg, dsLayout.Bindings[1]));
+ vkvgPipeline.Resize ((int)Width, (int)Height, new DescriptorSetWrites (dsVkvg, dsLayout.Bindings[1]));
#endif
updateMatrices ();
dsLayout = new DescriptorSetLayout (dev,
new VkDescriptorSetLayoutBinding (0, VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, VkDescriptorType.UniformBuffer));
- GraphicPipelineConfig cfg = GraphicPipelineConfig.CreateDefault (VkPrimitiveTopology.TriangleList, VkSampleCountFlags.SampleCount1);
+ GraphicPipelineConfig cfg = GraphicPipelineConfig.CreateDefault (VkPrimitiveTopology.TriangleList, VkSampleCountFlags.SampleCount1,false);
cfg.Layout = new PipelineLayout (dev, dsLayout);
- cfg.RenderPass = new RenderPass (dev, swapChain.ColorFormat, dev.GetSuitableDepthFormat (), cfg.Samples);
+ cfg.RenderPass = new RenderPass (dev, swapChain.ColorFormat, cfg.Samples);
cfg.AddVertexBinding<Vertex> (0);
cfg.AddVertexAttributes (0, VkFormat.R32g32b32Sfloat, VkFormat.R32g32b32Sfloat);
}
protected override void OnResize () {
- dev.WaitIdle ();
+ base.OnResize ();
if (frameBuffers != null)
for (int i = 0; i < swapChain.ImageCount; ++i)
frameBuffers[i] = new Framebuffer (pipeline.RenderPass, swapChain.Width, swapChain.Height,
(pipeline.Samples == VkSampleCountFlags.SampleCount1) ? new Image[] {
swapChain.images[i],
- null
} : new Image[] {
- null,
null,
swapChain.images[i]
});
buildCommandBuffers ();
-
- dev.WaitIdle ();
}
protected override void Dispose (bool disposing) {
protected Interface crow;
protected vkvg.Device vkvgDev;
protected CVKL.Image uiImage;
- protected bool isRunning;
+ protected bool isRunning, rebuildBuffers;
protected CrowWin (string name = "CrowWin", uint _width = 1024, uint _height = 768, bool vSync = false) :
base (name, _width, _height, vSync) {
int curModelIndex = 0;
bool reloadModel;
- bool rebuildBuffers;
Queue transferQ;
DeferredPbrRenderer renderer;
#if DEBUG
Instance.VALIDATION = true;
Instance.DEBUG_UTILS = true;
- Instance.RENDER_DOC_CAPTURE = false;
+ //Instance.RENDER_DOC_CAPTURE = false;
#endif
SwapChain.PREFERED_FORMAT = VkFormat.B8g8r8a8Srgb;
DeferredPbrRenderer.TEXTURE_ARRAY = true;
Utils.DataDirectory + "textures/uffizi_cube.ktx",
};
string[] modelPathes = {
- //"/mnt/devel/gts/vkChess.net/data/models/chess.glb",
Utils.DataDirectory + "models/DamagedHelmet/glTF/DamagedHelmet.gltf",
Utils.DataDirectory + "models/shadow.glb",
Utils.DataDirectory + "models/Hubble.glb",
float finalDebug = -1.0f;
void buildCommandBuffers () {
- cmdPbr?.Free ();
- cmdPbr = cmdPool.AllocateAndStart ();
- renderer.buildCommandBuffers (cmdPbr);
- cmdPbr.End ();
+ //cmdPbr?.Free ();
+ //cmdPbr = cmdPool.AllocateAndStart ();
+ //renderer.buildCommandBuffers (cmdPbr);
+ //cmdPbr.End ();
- cmdBlur?.Free ();
- cmdBlur = computeCmdPool.AllocateAndStart ();
- buildBlurCmd (cmdBlur);
+ //cmdBlur?.Free ();
+ //cmdBlur = computeCmdPool.AllocateAndStart ();
+ //buildBlurCmd (cmdBlur);
for (int i = 0; i < swapChain.ImageCount; ++i) {
cmds[i]?.Free ();
cmds[i] = cmdPool.AllocateAndStart ();
+ renderer.buildCommandBuffers (cmds[i]);
//renderer.hdrImgResolved.SetLayout (cmds[i], VkImageAspectFlags.Color,
- //VkAccessFlags.TransferRead, VkAccessFlags.ShaderRead,
- //VkImageLayout.TransferSrcOptimal, VkImageLayout.ShaderReadOnlyOptimal,
- //VkPipelineStageFlags.Transfer, VkPipelineStageFlags.FragmentShader);
+ //VkAccessFlags.TransferRead, VkAccessFlags.ShaderRead,
+ //VkImageLayout.TransferSrcOptimal, VkImageLayout.ShaderReadOnlyOptimal,
+ //VkPipelineStageFlags.Transfer, VkPipelineStageFlags.FragmentShader);
plToneMap.RenderPass.Begin (cmds[i], frameBuffers[i]);
}
if (cmds[idx] == null)
- return;
-
- presentQueue.Submit (cmdPbr, swapChain.presentComplete, renderer.DrawComplete);
-
- computeQ.Submit (cmdBlur, renderer.DrawComplete, blurComplete);
+ return;
- presentQueue.Submit (cmds[idx], blurComplete, drawComplete[idx]);
+ presentQueue.Submit (cmds[idx], swapChain.presentComplete, drawComplete[idx]);
presentQueue.Present (swapChain, drawComplete[idx]);
-
- presentQueue.WaitIdle ();
}
+
protected override void OnResize () {
+ base.OnResize ();
+
dev.WaitIdle ();
- renderer.Resize (swapChain.Width, swapChain.Height);
+ renderer.Resize (Width, Height);
UpdateView ();
protected override void OnResize () {
+ base.OnResize();
+
dev.WaitIdle ();
#if WITH_VKVG
vkvgPipeline.Resize ((int)swapChain.Width, (int)swapChain.Height,
<ItemGroup>
<GLSLShader Update="shaders/simpletexture.frag">
- <LogicalName>CVKL.simpletexture.frag.spv</LogicalName>
+ <LogicalName>vke.simpletexture.frag.spv</LogicalName>
</GLSLShader>
</ItemGroup>
-//
-// MarshaledObject.cs
+// Copyright (c) 2019 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
//
-// Author:
-// Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
-//
-// Copyright (c) 2019 jp
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
using System;
using System.Runtime.InteropServices;
-using System;
-using System.IO;
-using System.Runtime.InteropServices;
+// Copyright (c) 2019 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
using VK;
using static VK.Vk;
Random,
Linear
}
+ /// <summary>
+ /// A memory pool is a single chunck of memory of a kind shared among multiple resources.
+ /// </summary>
public class MemoryPool : IDisposable {
Device dev;
internal VkDeviceMemory vkMemory;
public IntPtr MappedData => mappedPointer;
public Resource Last => lastResource;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="T:CVKL.MemoryPool"/> class.
+ /// </summary>
+ /// <param name="dev">device</param>
+ /// <param name="memoryTypeIndex">Memory type index.</param>
+ /// <param name="size">Size</param>
public MemoryPool (Device dev, uint memoryTypeIndex, UInt64 size) {
this.dev = dev;
memInfo.allocationSize = size;
resource.bindMemory ();
}
-
-
- public void Defrag () {
-
+ public void Defrag () {
+ throw new NotImplementedException ();
}
public void Remove (Resource resource) {
}
resource.next = resource.previous = null;
}
-
public void Map (ulong size = Vk.WholeSize, ulong offset = 0) {
Utils.CheckResult (vkMapMemory (dev.VkDev, vkMemory, offset, size, 0, ref mappedPointer));
}
protected virtual void Dispose (bool disposing) {
if (!disposedValue) {
- if (!disposing)
+ if (disposing) {
+ //TODO:should automatically free resources here
+ } else
System.Diagnostics.Debug.WriteLine ("MemoryPool disposed by Finalizer.");
+
vkFreeMemory (dev.VkDev, vkMemory, IntPtr.Zero);
disposedValue = true;
}
using System;
-using System.Diagnostics;
-using System.IO;
-using System.Runtime.InteropServices;
using VK;
-using static VK.Vk;
-
namespace CVKL {
#if MEMORY_POOLS
+ /// <summary>
+ /// Resource manager is responsible for the memory allocations. It holds one pool for each memory type
+ /// </summary>
public class ResourceManager : IDisposable {
VkPhysicalDeviceMemoryProperties memoryProperties;
public MemoryPool[] memoryPools;
ulong[] reservedHeapMemory;
VkMemoryHeap getHeapFromMemoryIndex (uint i) => memoryProperties.memoryHeaps[memoryProperties.memoryTypes[i].heapIndex];
-
-
+ /// <summary>
+ /// Create a new resource manager that will create one memory pool for each kind of memory available on the device
+ /// </summary>
+ /// <param name="dev">Device</param>
+ /// <param name="defaultPoolsBlockDivisor">part of the whole available memory size to reserve by pools</param>
public ResourceManager (Device dev, ulong defaultPoolsBlockDivisor = 4) {
memoryProperties = dev.phy.memoryProperties;
memoryPools = new MemoryPool[memoryProperties.memoryTypeCount];
reservedHeapMemory[memoryProperties.memoryTypes[i].heapIndex] += size;
}
}
-
+ /// <summary>
+ /// Add one or more resources to the manager, pool will be choosen depending on the resource memory flags
+ /// </summary>
+ /// <param name="resources">Resource(s).</param>
public void Add (params Resource[] resources) {
foreach (Resource res in resources) {
res.updateMemoryRequirements ();
}
throw new InvalidOperationException ("Could not find a suitable memory type!");
}
-
+ /// <summary>
+ /// Dispose all memory pools held by this resource manager.
+ /// </summary>
public void Dispose () {
for (uint i = 0; i < memoryPools.Length; i++)
memoryPools[i].Dispose ();
using VK;
namespace CVKL {
+ /// <summary>
+ /// This class is a helper class for VkPipelineShaderStageCreateInfo creation.
+ /// </summary>
public class ShaderInfo : IDisposable {
public VkShaderStageFlags StageFlags;
public string SpirvPath;
StageFlags = _stageFlags;
SpirvPath = _spirvPath;
EntryPoint = new FixedUtf8String (_entryPoint);
- this.SpecializationInfo = specializationInfo;
+ SpecializationInfo = specializationInfo;
}
-
+ /// <summary>
+ /// Create the VkPipelineShaderStageCreateInfo structure. Note that the ShaderModule is created here and has to be destroy after the pipeline creation
+ /// </summary>
public VkPipelineShaderStageCreateInfo GetStageCreateInfo (Device dev) {
return new VkPipelineShaderStageCreateInfo {
sType = VkStructureType.PipelineShaderStageCreateInfo,
protected virtual void Dispose (bool disposing) {
if (!disposedValue) {
- if (disposing)
+ if (disposing) {
EntryPoint.Dispose ();
-
+ }
disposedValue = true;
}
}
//
// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Glfw;
/// Provide default swapchain with its command pool and buffers per image and the main present queue
/// </summary>
public abstract class VkWindow : IDisposable {
- static VkWindow currentWindow;
+ static Dictionary<IntPtr,VkWindow> windows = new Dictionary<IntPtr, VkWindow>();
IntPtr hWin;
/// </summary>
protected Camera camera = new Camera (Utils.DegreesToRadians (45f), 1f);
- uint width, height;
bool[] buttons = new bool[10];
public Modifier KeyModifiers = 0;
/// </summary>
public long UpdateFrequency = 200;
- public uint Width => width;
- public uint Height => height;
+ public uint Width { get; private set; }
+ public uint Height { get; private set; }
+ public string Title {
+ set {
+ Glfw3.SetWindowTitle (hWin, value);
+ }
+ }
public VkWindow (string name = "VkWindow", uint _width = 800, uint _height = 600, bool vSync = false) {
- currentWindow = this;
- width = _width;
- height = _height;
+ Width = _width;
+ Height = _height;
Glfw3.Init ();
Glfw3.WindowHint (WindowAttribute.ClientApi, 0);
Glfw3.WindowHint (WindowAttribute.Resizable, 1);
- hWin = Glfw3.CreateWindow ((int)width, (int)height, name, MonitorHandle.Zero, IntPtr.Zero);
+ hWin = Glfw3.CreateWindow ((int)Width, (int)Height, name, MonitorHandle.Zero, IntPtr.Zero);
if (hWin == IntPtr.Zero)
throw new Exception ("[GLFW3] Unable to create vulkan Window");
Glfw3.SetScrollCallback (hWin, HandleScrollDelegate);
Glfw3.SetCharCallback (hWin, HandleCharDelegate);
+ windows.Add (hWin, this);
+
initVulkan (vSync);
}
IntPtr currentCursor;
//activate the device to have effective queues created accordingly to what's available
dev.Activate (enabledFeatures, EnabledDeviceExtensions);
- swapChain = new SwapChain (presentQueue as PresentQueue, width, height, SwapChain.PREFERED_FORMAT,
+ swapChain = new SwapChain (presentQueue as PresentQueue, Width, Height, SwapChain.PREFERED_FORMAT,
vSync ? VkPresentModeKHR.FifoKHR : VkPresentModeKHR.MailboxKHR);
swapChain.Create ();
+ Width = swapChain.Width;
+ Height = swapChain.Height;
+
cmdPool = new CommandPool (dev, presentQueue.qFamIndex);
cmds = new CommandBuffer[swapChain.ImageCount];
drawComplete = new VkSemaphore[swapChain.ImageCount];
- for (int i = 0; i < swapChain.ImageCount; i++)
+ for (int i = 0; i < swapChain.ImageCount; i++) {
drawComplete[i] = dev.CreateSemaphore ();
+ drawComplete[i].SetDebugMarkerName (dev, "Semaphore DrawComplete" + i);
+ }
cmdPool.SetName ("main CmdPool");
- for (int i = 0; i < swapChain.ImageCount; i++)
- drawComplete[i].SetDebugMarkerName (dev, "Semaphore DrawComplete" + i);
}
/// <summary>
/// override this method to modify enabled features before device creation
switch (key) {
case Key.F4:
if (modifiers == Modifier.Alt)
- Glfw3.SetWindowShouldClose (currentWindow.hWin, 1);
+ Glfw3.SetWindowShouldClose (hWin, 1);
break;
case Key.Escape:
- Glfw3.SetWindowShouldClose (currentWindow.hWin, 1);
+ Glfw3.SetWindowShouldClose (hWin, 1);
break;
case Key.Up:
camera.Move (0, 0, 1);
#region events delegates
static void HandleWindowSizeDelegate (IntPtr window, int width, int height) { }
static void HandleCursorPosDelegate (IntPtr window, double xPosition, double yPosition) {
- currentWindow.onMouseMove (xPosition, yPosition);
- currentWindow.lastMouseX = xPosition;
- currentWindow.lastMouseY = yPosition;
+ windows[window].onMouseMove (xPosition, yPosition);
+ windows[window].lastMouseX = xPosition;
+ windows[window].lastMouseY = yPosition;
}
static void HandleMouseButtonDelegate (IntPtr window, Glfw.MouseButton button, InputAction action, Modifier mods) {
if (action == InputAction.Press) {
- currentWindow.buttons[(int)button] = true;
- currentWindow.onMouseButtonDown (button);
+ windows[window].buttons[(int)button] = true;
+ windows[window].onMouseButtonDown (button);
} else {
- currentWindow.buttons[(int)button] = false;
- currentWindow.onMouseButtonUp (button);
+ windows[window].buttons[(int)button] = false;
+ windows[window].onMouseButtonUp (button);
}
}
static void HandleScrollDelegate (IntPtr window, double xOffset, double yOffset) {
- currentWindow.onScroll (xOffset, yOffset);
+ windows[window].onScroll (xOffset, yOffset);
}
static void HandleKeyDelegate (IntPtr window, Key key, int scanCode, InputAction action, Modifier modifiers) {
- currentWindow.KeyModifiers = modifiers;
+ windows[window].KeyModifiers = modifiers;
if (action == InputAction.Press || action == InputAction.Repeat) {
- currentWindow.onKeyDown (key, scanCode, modifiers);
+ windows[window].onKeyDown (key, scanCode, modifiers);
} else {
- currentWindow.onKeyUp (key, scanCode, modifiers);
+ windows[window].onKeyUp (key, scanCode, modifiers);
}
}
static void HandleCharDelegate (IntPtr window, CodePoint codepoint) {
- currentWindow.onChar (codepoint);
+ windows[window].onChar (codepoint);
}
#endregion
public virtual void Update () { }
/// <summary>
- /// called when swapchain has been resized, override this method to resize your framebuffers coupled to the swapchain
+ /// called when swapchain has been resized, override this method to resize your framebuffers coupled to the swapchain.
+ /// The base method will update Window width and height with new swapchain's dimensions.
/// </summary>
- protected virtual void OnResize () { }
+ protected virtual void OnResize () {
+ Width = swapChain.Width;
+ Height = swapChain.Height;
+ }
#region IDisposable Support
}
public void DestroySemaphore (VkSemaphore semaphore) {
vkDestroySemaphore (dev, semaphore, IntPtr.Zero);
+ semaphore = 0;
}
public VkFence CreateFence (bool signaled = false) {
VkFence tmp;
Utils.CheckResult (vkCreateFence (dev, ref info, IntPtr.Zero, out tmp));
return tmp;
}
+ /// <summary>Destroy the fence.</summary>
+ /// <param name="fence">A valid fence handle.</param>
public void DestroyFence (VkFence fence) {
vkDestroyFence (dev, fence, IntPtr.Zero);
+ fence = 0;
}
public void WaitForFence (VkFence fence, ulong timeOut = UInt64.MaxValue) {
vkWaitForFences (dev, 1, ref fence, 1, timeOut);
public void DestroyShaderModule (VkShaderModule module) {
vkDestroyShaderModule (VkDev, module, IntPtr.Zero);
+ module = 0;
}
public void WaitIdle () {
Utils.CheckResult (vkDeviceWaitIdle (dev));
VkPresentInfoKHR present = VkPresentInfoKHR.New();
uint idx = swapChain.currentImageIndex;
- VkSwapchainKHR sc = swapChain.handle;
+ VkSwapchainKHR sc = swapChain.Handle;
present.swapchainCount = 1;
present.pSwapchains = sc.Pin();
present.waitSemaphoreCount = 1;
Samples = samples;
AddAttachment (colorFormat, (samples == VkSampleCountFlags.SampleCount1) ? VkImageLayout.PresentSrcKHR : VkImageLayout.ColorAttachmentOptimal, samples,
- loadOp, VkAttachmentStoreOp.Store, VkImageLayout.Undefined);
+ loadOp, VkAttachmentStoreOp.Store);
ClearValues.Add (new VkClearValue { color = new VkClearColorValue (0.0f, 0.0f, 0.0f) });
SubPass subpass0 = new SubPass ();
AddDependency (Vk.SubpassExternal, 0,
VkPipelineStageFlags.BottomOfPipe, VkPipelineStageFlags.ColorAttachmentOutput,
- VkAccessFlags.MemoryRead, VkAccessFlags.ColorAttachmentRead | VkAccessFlags.ColorAttachmentWrite);
+ VkAccessFlags.MemoryRead, VkAccessFlags.ColorAttachmentWrite);
AddDependency (0, Vk.SubpassExternal,
VkPipelineStageFlags.ColorAttachmentOutput, VkPipelineStageFlags.BottomOfPipe,
- VkAccessFlags.ColorAttachmentRead | VkAccessFlags.ColorAttachmentWrite, VkAccessFlags.MemoryRead);
+ VkAccessFlags.ColorAttachmentWrite, VkAccessFlags.MemoryRead);
}
/// <summary>
/// </summary>
public static VkImageUsageFlags IMAGES_USAGE = VkImageUsageFlags.ColorAttachment;
- internal VkSwapchainKHR handle;
+ public VkSwapchainKHR Handle { get; private set; }
internal uint currentImageIndex;
VkSwapchainCreateInfoKHR createInfos;
public Image[] images;
protected override VkDebugMarkerObjectNameInfoEXT DebugMarkerInfo
- => new VkDebugMarkerObjectNameInfoEXT (VkDebugReportObjectTypeEXT.SwapchainKhrEXT, handle.Handle);
-
+ => new VkDebugMarkerObjectNameInfoEXT (VkDebugReportObjectTypeEXT.SwapchainKhrEXT, Handle.Handle);
+ /// <summary>Swapchain images count.</summary>
public uint ImageCount => (uint)images?.Length;
- public uint Width => createInfos.imageExtent.width;
+ public uint Width => createInfos.imageExtent.width;
public uint Height => createInfos.imageExtent.height;
public VkFormat ColorFormat => createInfos.imageFormat;
public VkImageUsageFlags ImageUsage => createInfos.imageUsage;
}
base.Activate ();
}
-
+ /// <summary>
+ /// Create swapchain and populate images array
+ /// </summary>
public void Create () {
if (state != ActivableState.Activated)
Activate ();
createInfos.minImageCount = capabilities.minImageCount;
createInfos.preTransform = capabilities.currentTransform;
- createInfos.oldSwapchain = handle;
+ createInfos.oldSwapchain = Handle;
if (capabilities.currentExtent.width == 0xFFFFFFFF) {
if (createInfos.imageExtent.width < capabilities.minImageExtent.width)
createInfos.imageExtent = capabilities.currentExtent;
VkSwapchainKHR newSwapChain = Dev.CreateSwapChain (createInfos);
- if (handle.Handle != 0)
+ if (Handle.Handle != 0)
_destroy ();
- handle = newSwapChain;
+ Handle = newSwapChain;
- VkImage[] tmp = Dev.GetSwapChainImages (handle);
+ VkImage[] tmp = Dev.GetSwapChainImages (Handle);
images = new Image[tmp.Length];
for (int i = 0; i < tmp.Length; i++) {
images[i] = new Image (Dev, tmp[i], ColorFormat, ImageUsage, Width, Height);
images[i].Descriptor.imageView.SetDebugMarkerName (Dev, "SwapChain Img" + i + " view");
}
}
-
- public int GetNextImage () {
- VkResult res = vkAcquireNextImageKHR (Dev.VkDev, handle, UInt64.MaxValue, presentComplete, VkFence.Null, out currentImageIndex);
+ /// <summary>
+ /// Acquire next image, recreate swapchain if out of date or suboptimal error.
+ /// </summary>
+ /// <returns>Swapchain image index or -1 if failed</returns>
+ /// <param name="fence">Fence param of 'vkAcquireNextImageKHR'</param>
+ public int GetNextImage (VkFence fence = default(VkFence)) {
+ VkResult res = vkAcquireNextImageKHR (Dev.VkDev, Handle, UInt64.MaxValue, presentComplete, fence, out currentImageIndex);
if (res == VkResult.ErrorOutOfDateKHR || res == VkResult.SuboptimalKHR) {
Create ();
return -1;
for (int i = 0; i < ImageCount; i++)
images[i].Dispose ();
- Dev.DestroySwapChain (handle);
+ Dev.DestroySwapChain (Handle);
}
public override string ToString () {
- return string.Format ($"{base.ToString ()}[0x{handle.Handle.ToString ("x")}]");
+ return string.Format ($"{base.ToString ()}[0x{Handle.Handle.ToString ("x")}]");
}
#region IDisposable Support
-using System.Text;
+// Copyright (c) 2019 Andrew Armstrong/FacticiusVir
+// Copyright (c) 2019 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System.Runtime.InteropServices;
+using System.Text;
namespace Glfw
{
- /// <summary>
- /// Represents a native UTF32 codepoint.
- /// </summary>
- public struct CodePoint
+ /// <summary>
+ /// Represents a native UTF32 codepoint.
+ /// </summary>
+ [StructLayout (LayoutKind.Explicit)]
+ public struct CodePoint
{
- /// <summary>
- /// The numeric value of the codepoint.
- /// </summary>
- public readonly uint Value;
-
- /// <summary>
- /// Casts the codepoint to System.Char.
- /// </summary>
- /// <returns>
- /// The character representation of the codepoint.
- /// </returns>
- public unsafe char ToChar()
- {
- uint value = this.Value;
-
- char result;
-
- Encoding.UTF32.GetChars((byte*)&value, 4, &result, 1);
-
- return result;
- }
-
+ /// <summary>
+ /// The numeric value of the codepoint.
+ /// </summary>
+ [FieldOffset (0)] public readonly uint Value;
+ [FieldOffset (0)] readonly byte byte0;
+ [FieldOffset (1)] readonly byte byte1;
+ [FieldOffset (2)] readonly byte byte2;
+ [FieldOffset (3)] readonly byte byte3;
+ /// <summary>
+ /// Casts the codepoint to System.Char.
+ /// </summary>
+ /// <returns>
+ /// The character representation of the codepoint.
+ /// </returns>
+ public char ToChar() => Encoding.UTF32.GetChars (new byte[] { byte0, byte0, byte0, byte0 })[0];
/// <summary>
/// Converts the value of this instance to its equivalent string representation.
/// </summary>
/// <returns>
/// A string containing the character representation of the codepoint.
/// </returns>
- public override string ToString()
- {
- return this.ToChar().ToString();
- }
+ public override string ToString() => ToChar().ToString();
}
}
-using System;
+// Copyright (c) 2019 Andrew Armstrong/FacticiusVir
+// Copyright (c) 2019 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)using System;
+using System;
using System.Runtime.InteropServices;
namespace Glfw {
-namespace Glfw
+// Copyright (c) 2019 Andrew Armstrong/FacticiusVir
+// Copyright (c) 2019 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+namespace Glfw
{
/// <summary>
/// Indicates the general category of an error.
-using System;
-using System.Text;
+// Copyright (c) 2019 Andrew Armstrong/FacticiusVir
+// Copyright (c) 2019 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
using System.Runtime.InteropServices;
-namespace Glfw
-{
+namespace Glfw {
public enum CursorShape
{
Arrow = 0x00036001,
/// <summary>
/// Interop functions for the GLFW3 API.
/// </summary>
- public unsafe static class Glfw3
+ public static class Glfw3
{
/// <summary>
/// The base name for the GLFW3 library.
/// An array of extension names, or Null if an error occurred.
/// </returns>
[DllImport(GlfwDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "glfwGetRequiredInstanceExtensions")]
- public static extern byte** GetRequiredInstanceExtensions(out int count);
+ public static extern IntPtr GetRequiredInstanceExtensions(out int count);
/// <summary>
/// Sets the size callback of the specified window, which is called
/// if an error occurred.
/// </returns>
[DllImport(GlfwDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "glfwGetMonitors")]
- public static extern MonitorHandle* GetMonitors(out int count);
+ public static extern IntPtr GetMonitors(out int count);
/// <summary>
/// Returns the primary monitor. This is usually the monitor where
/// An array of video modes, or Null if an error occurred.
/// </returns>
[DllImport(GlfwDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "glfwGetVideoModes")]
- public static extern VideoMode* GetVideoModes(MonitorHandle monitor, out int count);
+ public static extern IntPtr GetVideoModes(MonitorHandle monitor, out int count);
/// <summary>
/// Returns the current video mode of the specified monitor. If you
/// </returns>
public static string[] GetRequiredInstanceExtensions()
{
- byte** namePointer = GetRequiredInstanceExtensions(out int count);
+ IntPtr names = GetRequiredInstanceExtensions(out int count);
var result = new string[count];
for (int nameIndex = 0; nameIndex < count; nameIndex++)
{
- result[nameIndex] = Marshal.PtrToStringAnsi(new System.IntPtr (namePointer[nameIndex]));
+ IntPtr name = Marshal.ReadIntPtr (names, nameIndex);
+ result[nameIndex] = Marshal.PtrToStringAnsi(name);
}
return result;
/// </returns>
public static MonitorHandle[] GetMonitors()
{
- MonitorHandle* monitorPointer = GetMonitors(out int count);
+ IntPtr monitors = GetMonitors(out int count);
var result = new MonitorHandle[count];
- for (int i = 0; i < count; i++)
- {
- result[i] = monitorPointer[i];
- }
+ for (int i = 0; i < count; i++)
+ result[i] = new MonitorHandle(Marshal.ReadIntPtr(monitors, i));
return result;
}
/// </returns>
public static VideoMode[] GetVideoModes(MonitorHandle monitor)
{
- VideoMode* videoModePointer = GetVideoModes(monitor, out int count);
+ IntPtr videoModes = GetVideoModes(monitor, out int count);
var result = new VideoMode[count];
- for (int i = 0; i < count; i++)
- {
- result[i] = videoModePointer[i];
- }
+ for (int i = 0; i < count; i++)
+ result[i] = Marshal.PtrToStructure<VideoMode>(Marshal.ReadIntPtr(videoModes, i));
return result;
}
-namespace Glfw
+// Copyright (c) 2019 Andrew Armstrong/FacticiusVir
+// Copyright (c) 2019 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+namespace Glfw
{
/// <summary>
/// Represents the action of an input key event or the state of a key.
-namespace Glfw
+// Copyright (c) 2019 Andrew Armstrong/FacticiusVir
+// Copyright (c) 2019 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+namespace Glfw
{
/// <summary>
/// Represents a key on a keyboard.
-using System;
+// Copyright (c) 2019 Andrew Armstrong/FacticiusVir
+// Copyright (c) 2019 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
namespace Glfw
{
-namespace Glfw
+// Copyright (c) 2019 Andrew Armstrong/FacticiusVir
+// Copyright (c) 2019 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+namespace Glfw
{
/// <summary>
/// Events that may be raised from a monitor callback.
-using System;
+// Copyright (c) 2019 Andrew Armstrong/FacticiusVir
+// Copyright (c) 2019 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
namespace Glfw
{
/// </summary>
public struct MonitorHandle
{
- internal MonitorHandle(System.IntPtr handle)
- {
- this.handle = handle;
- }
-
- private System.IntPtr handle;
+ readonly IntPtr handle;
- /// <summary>
- /// Gets the underlying native pointer to the monitor object.
- /// </summary>
- public IntPtr RawHandle
+ internal MonitorHandle(IntPtr handle)
{
- get
- {
- return this.handle;
- }
+ this.handle = handle;
}
-
+ /// <summary>
+ /// Gets the underlying native pointer to the monitor object.
+ /// </summary>
+ public IntPtr RawHandle => handle;
/// <summary>
/// A read-only field that represents a MonitorHandle that has been
/// inititalised to zero.
/// </summary>
- public static readonly MonitorHandle Zero = new MonitorHandle (System.IntPtr.Zero);
+ public static readonly MonitorHandle Zero = new MonitorHandle (IntPtr.Zero);
}
}
-namespace Glfw
+// Copyright (c) 2019 Andrew Armstrong/FacticiusVir
+// Copyright (c) 2019 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+namespace Glfw
{
/// <summary>
/// The index and name of mouse buttons for callbacks.
using System;
using System.Runtime.InteropServices;
+// Copyright (c) 2019 Andrew Armstrong/FacticiusVir
+// Copyright (c) 2019 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
namespace Glfw
{
/// <summary>
/// </summary>
public struct NativeString
{
- internal NativeString(System.IntPtr pointer)
+ internal NativeString(IntPtr pointer)
{
this.pointer = pointer;
}
- private System.IntPtr pointer;
-
- /// <summary>
- /// Gets the marshalled string value for this native string.
- /// </summary>
- public string Value
- {
- get
- {
- if (this.IsNull)
- {
- throw new NullReferenceException();
- }
-
- return Marshal.PtrToStringAnsi(this.pointer);
- }
- }
+ readonly IntPtr pointer;
+ /// <summary>
+ /// Gets the marshalled string value for this native string.
+ /// </summary>
+ public string Value => IsNull ? throw new NullReferenceException () : Marshal.PtrToStringAnsi (this.pointer);
/// <summary>
/// Gets a value indicating whether the pointer wrapped by this
/// instance is null.
/// </summary>
- public bool IsNull => this.pointer == System.IntPtr.Zero;
+ public bool IsNull => pointer == IntPtr.Zero;
/// <summary>
/// The underlying pointer wrapped by this instance.
/// </summary>
- public IntPtr RawPointer => this.pointer;
+ public IntPtr RawPointer => pointer;
public override string ToString()
{
- return this.Value;
+ return Value;
}
}
}
[StructLayout(LayoutKind.Sequential)]
public struct VideoMode
{
- private int width;
-
- private int height;
-
- private int redBits;
-
- private int greenBits;
-
- private int blueBits;
+ private readonly int width;
+ private readonly int height;
+ private readonly int redBits;
+ private readonly int greenBits;
+ private readonly int blueBits;
/// <summary>
/// The resolution, in screen coordinates, of the video mode.
-using System;
+// Copyright (c) 2019 Andrew Armstrong/FacticiusVir
+// Copyright (c) 2019 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
using System.Runtime.InteropServices;
namespace Glfw
/// </summary>
public struct VideoModePointer
{
- internal VideoModePointer(System.IntPtr pointer)
+ private readonly IntPtr pointer;
+ internal VideoModePointer (IntPtr pointer)
{
this.pointer = pointer;
}
-
- private System.IntPtr pointer;
-
/// <summary>
/// Gets the VideoMode value at the referenced memory location.
/// </summary>
- public VideoMode Value => this.IsNull ? throw new NullReferenceException() : Marshal.PtrToStructure<VideoMode>(this.pointer);
-
+ public VideoMode Value => IsNull ? throw new NullReferenceException() : Marshal.PtrToStructure<VideoMode>(pointer);
/// <summary>
/// Gets a value indicating whether the pointer wrapped by this
/// instance is null.
/// </summary>
- public bool IsNull => this.pointer == System.IntPtr.Zero;
-
+ public bool IsNull => pointer == IntPtr.Zero;
/// <summary>
/// The underlying pointer wrapped by this instance.
/// </summary>
- public IntPtr RawPointer => this.pointer;
+ public IntPtr RawPointer => pointer;
}
}
-namespace Glfw
+// Copyright (c) 2019 Andrew Armstrong/FacticiusVir
+// Copyright (c) 2019 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+namespace Glfw
{
/// <summary>
/// Attributes of a window or its framebuffer or context, that can be
-//
-// ktx.cs
+// Copyright (c) 2019 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
//
-// Author:
-// Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
-//
-// Copyright (c) 2019 Jean-Philippe Bruyère
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
using System;
using System.IO;