fix: imageCubeArray via core features + SRGB swapchain + LUT color conversion
Three fixes: 1. imageCubeArray: enabled via VkPhysicalDeviceFeatures (core 1.0 feature) instead of VkPhysicalDeviceImageCubeArrayFeatures (wrong sType in pNext chain was interpreted as VkExternalMemoryImageCreateInfoNV) 2. Swapchain format: reverted to B8G8R8A8_SRGB (was UNORM which driver didn't support → fell back to A2B10G10R10 10-bit format=58 → wrong pixel layout for capture) 3. Video colors: SRGB→linear conversion via 256-entry LUT in ReadCapturedBuffer. SRGB swapchain gives gamma-compressed bytes, FFmpeg expects linear. LUT converts each B/G/R channel using standard SRGB formula: s<=0.04045 ? s/12.92 : ((s+0.055)/1.055)^2.4 Also: video captures before ImGui (no UI in recording)
This commit is contained in:
Binary file not shown.
@@ -305,21 +305,19 @@ internal sealed unsafe class VulkanContext : IDisposable
|
|||||||
synchronization2 = VkBool32.True,
|
synchronization2 = VkBool32.True,
|
||||||
};
|
};
|
||||||
|
|
||||||
var cubeArrayFeatures = new VkPhysicalDeviceImageCubeArrayFeatures
|
|
||||||
{
|
|
||||||
sType = VkStructureType.PhysicalDeviceImageCubeArrayFeatures,
|
|
||||||
pNext = (nint)(&sync2Features),
|
|
||||||
imageCubeArray = VkBool32.True,
|
|
||||||
};
|
|
||||||
|
|
||||||
var renderingFeatures = new VkPhysicalDeviceDynamicRenderingFeatures
|
var renderingFeatures = new VkPhysicalDeviceDynamicRenderingFeatures
|
||||||
{
|
{
|
||||||
sType = VkStructureType.PhysicalDeviceDynamicRenderingFeatures,
|
sType = VkStructureType.PhysicalDeviceDynamicRenderingFeatures,
|
||||||
pNext = (nint)(&cubeArrayFeatures),
|
pNext = (nint)(&sync2Features),
|
||||||
dynamicRendering = VkBool32.True,
|
dynamicRendering = VkBool32.True,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var features = new VkPhysicalDeviceFeatures
|
||||||
|
{
|
||||||
|
imageCubeArray = VkBool32.True,
|
||||||
|
};
|
||||||
|
|
||||||
var deviceInfo = new VkDeviceCreateInfo
|
var deviceInfo = new VkDeviceCreateInfo
|
||||||
{
|
{
|
||||||
sType = VkStructureType.DeviceCreateInfo,
|
sType = VkStructureType.DeviceCreateInfo,
|
||||||
@@ -327,7 +325,7 @@ internal sealed unsafe class VulkanContext : IDisposable
|
|||||||
queueCreateInfoCount = 1,
|
queueCreateInfoCount = 1,
|
||||||
enabledExtensionCount = (uint)extNames.Length,
|
enabledExtensionCount = (uint)extNames.Length,
|
||||||
ppEnabledExtensionNames = extPtrs,
|
ppEnabledExtensionNames = extPtrs,
|
||||||
pEnabledFeatures = null,
|
pEnabledFeatures = &features,
|
||||||
pNext = (nint)(&renderingFeatures),
|
pNext = (nint)(&renderingFeatures),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -361,12 +359,12 @@ internal sealed unsafe class VulkanContext : IDisposable
|
|||||||
var formats = stackalloc VkSurfaceFormatKHR[(int)formatCount];
|
var formats = stackalloc VkSurfaceFormatKHR[(int)formatCount];
|
||||||
Vk.vkGetPhysicalDeviceSurfaceFormatsKHR(PhysicalDevice, Surface, &formatCount, formats);
|
Vk.vkGetPhysicalDeviceSurfaceFormatsKHR(PhysicalDevice, Surface, &formatCount, formats);
|
||||||
|
|
||||||
SurfaceFormat = VkFormat.B8G8R8A8Unorm;
|
SurfaceFormat = VkFormat.B8G8R8A8Srgb;
|
||||||
SurfaceColorSpace = VkColorSpaceKHR.SrgbNonlinearKHR;
|
SurfaceColorSpace = VkColorSpaceKHR.SrgbNonlinearKHR;
|
||||||
|
|
||||||
for (uint i = 0; i < formatCount; i++)
|
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)
|
formats[(int)i].colorSpace == VkColorSpaceKHR.SrgbNonlinearKHR)
|
||||||
{
|
{
|
||||||
SurfaceFormat = formats[(int)i].format;
|
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;
|
SurfaceFormat = formats[0].format;
|
||||||
SurfaceColorSpace = formats[0].colorSpace;
|
SurfaceColorSpace = formats[0].colorSpace;
|
||||||
|
|||||||
@@ -149,10 +149,35 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
|||||||
Marshal.Copy((nint)pData, result, 0, (int)_screenshotBufferSize);
|
Marshal.Copy((nint)pData, result, 0, (int)_screenshotBufferSize);
|
||||||
Vk.vkUnmapMemory(_ctx.Device, _screenshotMemory);
|
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;
|
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()
|
public void BeginImGuiFrame()
|
||||||
{
|
{
|
||||||
_imGui?.NewFrame();
|
_imGui?.NewFrame();
|
||||||
|
|||||||
Reference in New Issue
Block a user