Root cause: driver returned format=58 (A2B10G10R10_PACK32) as fallback
when B8G8R8A8_SRGB wasn't found. 10-bit packed format has different
byte layout → vkCmdCopyImageToBuffer gives garbage when interpreted as
8-bit BGRA → acid colors in video.
Fix:
- QuerySurfaceFormat: try B8G8R8A8_UNORM first (NVIDIA supports it)
- Fallback: B8G8R8A8_SRGB, then first available
- Removed SRGB LUT conversion — UNORM data is already linear
- Raw BGRA bytes go directly to FFmpeg (pixel_format=bgra)
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)
1. No UI in video: end render pass before ImGui, capture, start new
render pass with loadOp=LOAD for ImGui, end again. Video captures
only the 3D scene.
2. Acid colors fixed: swapchain format changed from B8G8R8A8_SRGB to
B8G8R8A8_UNORM. SRGB format stores gamma-compressed values that
vkCmdCopyImageToBuffer reads raw — FFmpeg treated them as linear
causing acid colors. UNORM stores linear values, copy gives linear
data, FFmpeg bgra→yuv420p conversion is correct.
Swapchain format is B8G8R8A8_SRGB — vkCmdCopyImageToBuffer gives BGRA
data. Was converting BGRA→RGBA in C# and telling FFmpeg 'rgba' —
but SRGB data needs to stay as-is. Now:
- No BGRA→RGBA swap in ReadCapturedBuffer
- FFmpeg pixel_format=bgra (matches swapchain)
- FFmpeg handles SRGB→yuv420p conversion correctly
Root cause: CaptureFrame returned null and was assigned to CapturedFrame,
overwriting the data that ReadCapturedBuffer had just set at the start
of the same frame.
Fix: CaptureFrame is now void — only records GPU copy commands.
ReadCapturedBuffer runs at start of next frame (after WaitFrame/fence)
and sets CapturedFrame with actual pixel data.
Program.cs reads CapturedFrame after RenderWorld — now contains data
from the previous frame's GPU copy.
- Removed skipFrames logic — capture every frame for reliability
- FFmpeg framerate set to actual FPS (capped at 60) instead of hardcoded 30
- Added -vf fps=30 filter to downsample to 30fps in output
- First frame: CapturedFrame is null (no previous capture), skipped naturally
- Second frame onwards: data available from previous frame's GPU copy
0x2000 was VK_ACCESS_MEMORY_READ_BIT in Vulkan 1.0 but maps to
VK_ACCESS_2_HOST_READ_BIT in sync2. Replaced with 0x1000
(VK_ACCESS_2_TRANSFER_WRITE_BIT) for dst access on transfer layout.
Three validation errors fixed:
1. vkCmdCopyImageToBuffer inside render pass → moved AFTER vkCmdEndRendering
2. Swapchain images missing TRANSFER_SRC usage → added to swapchain creation
3. Wrong layout transitions → ColorAttachment→TransferSrc→copy→ColorAttachment
Architecture change:
- CaptureFrame now only records commands (returns null)
- ReadCapturedBuffer called at start of NEXT frame (after WaitFrame/fence)
GPU has finished by then, safe to map memory
- CapturedFrame available 1 frame late (acceptable for video)
- Image transitions: ColorAttachmentOptimal→TransferSrcOptimal→ColorAttachmentOptimal
(present transition still works because image is back in ColorAttachmentOptimal)
- All 3 lights: intensity 8.0, range 15.5 (was 12/8/8, 60/40/40)
- Copy Parameters: also logs to console for debugging
- Clipboard text via ImGui.SetClipboardText (standard API)