diff --git a/Videos/cortex_20260619_091427.mp4 b/Videos/cortex_20260619_091427.mp4 new file mode 100644 index 0000000..5a656a7 Binary files /dev/null and b/Videos/cortex_20260619_091427.mp4 differ diff --git a/src/Engine.Graphics.Vulkan/VulkanContext.cs b/src/Engine.Graphics.Vulkan/VulkanContext.cs index bf65fcb..f6d05dc 100644 --- a/src/Engine.Graphics.Vulkan/VulkanContext.cs +++ b/src/Engine.Graphics.Vulkan/VulkanContext.cs @@ -305,21 +305,19 @@ internal sealed unsafe class VulkanContext : IDisposable synchronization2 = VkBool32.True, }; - var cubeArrayFeatures = new VkPhysicalDeviceImageCubeArrayFeatures - { - sType = VkStructureType.PhysicalDeviceImageCubeArrayFeatures, - pNext = (nint)(&sync2Features), - imageCubeArray = VkBool32.True, - }; - var renderingFeatures = new VkPhysicalDeviceDynamicRenderingFeatures { sType = VkStructureType.PhysicalDeviceDynamicRenderingFeatures, - pNext = (nint)(&cubeArrayFeatures), + pNext = (nint)(&sync2Features), dynamicRendering = VkBool32.True, }; + var features = new VkPhysicalDeviceFeatures + { + imageCubeArray = VkBool32.True, + }; + var deviceInfo = new VkDeviceCreateInfo { sType = VkStructureType.DeviceCreateInfo, @@ -327,7 +325,7 @@ internal sealed unsafe class VulkanContext : IDisposable queueCreateInfoCount = 1, enabledExtensionCount = (uint)extNames.Length, ppEnabledExtensionNames = extPtrs, - pEnabledFeatures = null, + pEnabledFeatures = &features, pNext = (nint)(&renderingFeatures), }; @@ -361,12 +359,12 @@ internal sealed unsafe class VulkanContext : IDisposable var formats = stackalloc VkSurfaceFormatKHR[(int)formatCount]; Vk.vkGetPhysicalDeviceSurfaceFormatsKHR(PhysicalDevice, Surface, &formatCount, formats); - SurfaceFormat = VkFormat.B8G8R8A8Unorm; + SurfaceFormat = VkFormat.B8G8R8A8Srgb; SurfaceColorSpace = VkColorSpaceKHR.SrgbNonlinearKHR; for (uint i = 0; i < formatCount; i++) { - if (formats[(int)i].format == VkFormat.B8G8R8A8Unorm && + if (formats[(int)i].format == VkFormat.B8G8R8A8Srgb && formats[(int)i].colorSpace == VkColorSpaceKHR.SrgbNonlinearKHR) { SurfaceFormat = formats[(int)i].format; @@ -375,7 +373,7 @@ internal sealed unsafe class VulkanContext : IDisposable } } - if (SurfaceFormat == VkFormat.B8G8R8A8Unorm) + if (SurfaceFormat == VkFormat.B8G8R8A8Srgb) { SurfaceFormat = formats[0].format; SurfaceColorSpace = formats[0].colorSpace; diff --git a/src/Engine.Graphics.Vulkan/VulkanRenderer.cs b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs index 62b5681..72dae1e 100644 --- a/src/Engine.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs @@ -149,10 +149,35 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh Marshal.Copy((nint)pData, result, 0, (int)_screenshotBufferSize); Vk.vkUnmapMemory(_ctx.Device, _screenshotMemory); - // Data is BGRA from swapchain — no conversion needed, FFmpeg handles bgra + // Swapchain is B8G8R8A8_SRGB — raw bytes are SRGB-encoded + // Convert SRGB→linear for FFmpeg (which expects linear bgra) + for (int i = 0; i < result.Length; i += 4) + { + result[i] = SrgbToLinear(result[i]); // B + result[i + 1] = SrgbToLinear(result[i + 1]); // G + result[i + 2] = SrgbToLinear(result[i + 2]); // R + // result[i + 3] = A (keep as-is) + } + return result; } + private static readonly byte[] SrgbLut = BuildSrgbLut(); + + private static byte[] BuildSrgbLut() + { + var lut = new byte[256]; + for (int i = 0; i < 256; i++) + { + float s = i / 255.0f; + float l = s <= 0.04045f ? s / 12.92f : MathF.Pow((s + 0.055f) / 1.055f, 2.4f); + lut[i] = (byte)Math.Clamp(l * 255.0f, 0, 255); + } + return lut; + } + + private static byte SrgbToLinear(byte srgb) => SrgbLut[srgb]; + public void BeginImGuiFrame() { _imGui?.NewFrame();