first commit

This commit is contained in:
elwrcl committed 2026-08-23 04:59:41 +03:00
commit 0bbe6b34e6
54 files changed
+156785

No files matched your search

+964
View File
@@ -0,0 +1,964 @@
/* vulkan H.264 encode test for hasvk.
* ./hasvk_encode_test # flat-gray 176x144, dumps /tmp/hasvk_enc_out.h264
* HASVK_ENC_GRADIENT=1 ./hasvk_encode_test # luma gradient (real residuals)
* HASVK_ENC_GRADIENT=1 HASVK_ENC_CHROMA=1 ./... # + chroma gradient
* validate the output with ffmpeg:
* ffmpeg -i /tmp/hasvk_enc_out.h264 -f rawvideo -pix_fmt yuv420p out.yuv
* The stream prepends the driver's own SPS/PPS
* (vkGetEncodedVideoSessionParametersKHR) so it decodes standalone.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <vulkan/vulkan.h>
#include <vk_video/vulkan_video_codec_h264std.h>
#include <vk_video/vulkan_video_codec_h264std_encode.h>
#define CHECK(x) do { \
VkResult _r = (x); \
if (_r != VK_SUCCESS) { \
fprintf(stderr, "FAIL %s:%d: %s -> %d\n", __FILE__, __LINE__, #x, _r); \
exit(1); \
} \
} while (0)
#define WIDTH 176
#define HEIGHT 144
#define WIDTH_MBS (WIDTH / 16)
#define HEIGHT_MBS (HEIGHT / 16)
#define LUMA_SZ (WIDTH * HEIGHT)
#define CHROMA_SZ ((WIDTH / 2) * (HEIGHT / 2) * 2)
#define FRAME_SZ (LUMA_SZ + CHROMA_SZ)
static int
load_frame(const char *path, int idx, int i420, uint8_t *dst)
{
FILE *f = fopen(path, "rb");
if (!f) { fprintf(stderr, "cannot open '%s'\n", path); return 0; }
if (fseek(f, (long)idx * FRAME_SZ, SEEK_SET) != 0) {
fprintf(stderr, "'%s': cannot seek to frame %d\n", path, idx);
fclose(f); return 0;
}
uint8_t buf[FRAME_SZ];
size_t n = fread(buf, 1, FRAME_SZ, f);
fclose(f);
if (n != FRAME_SZ) {
fprintf(stderr, "'%s': frame %d short read (%zu of %d)\n", path, idx, n, FRAME_SZ);
return 0;
}
memcpy(dst, buf, LUMA_SZ);
if (i420) {
const uint8_t *cb = buf + LUMA_SZ, *cr = cb + CHROMA_SZ / 2;
for (int i = 0; i < CHROMA_SZ / 2; i++) {
dst[LUMA_SZ + i * 2 + 0] = cb[i];
dst[LUMA_SZ + i * 2 + 1] = cr[i];
}
} else {
memcpy(dst + LUMA_SZ, buf + LUMA_SZ, CHROMA_SZ);
}
return 1;
}
static void
shift_frame(const uint8_t *src, uint8_t *dst, int dx, int dy)
{
for (int y = 0; y < HEIGHT; y++) {
int sy = y - dy; sy = sy < 0 ? 0 : (sy >= HEIGHT ? HEIGHT - 1 : sy);
for (int x = 0; x < WIDTH; x++) {
int sx = x - dx; sx = sx < 0 ? 0 : (sx >= WIDTH ? WIDTH - 1 : sx);
dst[y * WIDTH + x] = src[sy * WIDTH + sx];
}
}
int cw = WIDTH / 2, ch = HEIGHT / 2, cdx = dx / 2, cdy = dy / 2;
for (int y = 0; y < ch; y++) {
int sy = y - cdy; sy = sy < 0 ? 0 : (sy >= ch ? ch - 1 : sy);
for (int x = 0; x < cw; x++) {
int sx = x - cdx; sx = sx < 0 ? 0 : (sx >= cw ? cw - 1 : sx);
dst[LUMA_SZ + (y * cw + x) * 2 + 0] = src[LUMA_SZ + (sy * cw + sx) * 2 + 0];
dst[LUMA_SZ + (y * cw + x) * 2 + 1] = src[LUMA_SZ + (sy * cw + sx) * 2 + 1];
}
}
}
static PFN_vkGetPhysicalDeviceVideoCapabilitiesKHR pGetPhysicalDeviceVideoCapabilitiesKHR;
static PFN_vkGetPhysicalDeviceVideoFormatPropertiesKHR pGetPhysicalDeviceVideoFormatPropertiesKHR;
static PFN_vkCreateVideoSessionKHR pCreateVideoSessionKHR;
static PFN_vkDestroyVideoSessionKHR pDestroyVideoSessionKHR;
static PFN_vkGetVideoSessionMemoryRequirementsKHR pGetVideoSessionMemoryRequirementsKHR;
static PFN_vkBindVideoSessionMemoryKHR pBindVideoSessionMemoryKHR;
static PFN_vkCreateVideoSessionParametersKHR pCreateVideoSessionParametersKHR;
static PFN_vkDestroyVideoSessionParametersKHR pDestroyVideoSessionParametersKHR;
static PFN_vkGetEncodedVideoSessionParametersKHR pGetEncodedVideoSessionParametersKHR;
static PFN_vkCmdBeginVideoCodingKHR pCmdBeginVideoCodingKHR;
static PFN_vkCmdEndVideoCodingKHR pCmdEndVideoCodingKHR;
static PFN_vkCmdEncodeVideoKHR pCmdEncodeVideoKHR;
static uint32_t
find_memory_type(VkPhysicalDeviceMemoryProperties *mp, uint32_t type_bits, VkMemoryPropertyFlags want)
{
for (uint32_t i = 0; i < mp->memoryTypeCount; i++) {
if ((type_bits & (1u << i)) &&
(mp->memoryTypes[i].propertyFlags & want) == want)
return i;
}
/* fall back: any type in the mask */
for (uint32_t i = 0; i < mp->memoryTypeCount; i++) {
if (type_bits & (1u << i))
return i;
}
fprintf(stderr, "FAIL: no matching memory type for bits 0x%x\n", type_bits);
exit(1);
}
static VkDeviceMemory
alloc_and_bind_buffer(VkDevice dev, VkPhysicalDeviceMemoryProperties *mp,
VkBuffer buf, VkMemoryPropertyFlags want)
{
VkMemoryRequirements req;
vkGetBufferMemoryRequirements(dev, buf, &req);
VkMemoryAllocateInfo ai = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = req.size,
.memoryTypeIndex = find_memory_type(mp, req.memoryTypeBits, want),
};
VkDeviceMemory mem;
CHECK(vkAllocateMemory(dev, &ai, NULL, &mem));
CHECK(vkBindBufferMemory(dev, buf, mem, 0));
return mem;
}
static VkDeviceMemory
alloc_and_bind_image(VkDevice dev, VkPhysicalDeviceMemoryProperties *mp, VkImage img)
{
VkMemoryRequirements req;
vkGetImageMemoryRequirements(dev, img, &req);
VkMemoryAllocateInfo ai = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = req.size,
.memoryTypeIndex = find_memory_type(mp, req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT),
};
VkDeviceMemory mem;
CHECK(vkAllocateMemory(dev, &ai, NULL, &mem));
CHECK(vkBindImageMemory(dev, img, mem, 0));
return mem;
}
int
main(void)
{
/* instance */
VkApplicationInfo app = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.apiVersion = VK_API_VERSION_1_3,
};
VkInstanceCreateInfo ici = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pApplicationInfo = &app,
};
VkInstance instance;
CHECK(vkCreateInstance(&ici, NULL, &instance));
uint32_t pdev_count = 0;
CHECK(vkEnumeratePhysicalDevices(instance, &pdev_count, NULL));
if (pdev_count == 0) { fprintf(stderr, "no physical devices\n"); return 1; }
VkPhysicalDevice *pdevs = calloc(pdev_count, sizeof(*pdevs));
CHECK(vkEnumeratePhysicalDevices(instance, &pdev_count, pdevs));
VkPhysicalDevice pdev = pdevs[0];
VkPhysicalDeviceProperties pprops;
vkGetPhysicalDeviceProperties(pdev, &pprops);
printf("Device: %s\n", pprops.deviceName);
VkPhysicalDeviceMemoryProperties mem_props;
vkGetPhysicalDeviceMemoryProperties(pdev, &mem_props);
/* find encode queue family */
uint32_t qf_count = 0;
vkGetPhysicalDeviceQueueFamilyProperties2(pdev, &qf_count, NULL);
VkQueueFamilyVideoPropertiesKHR *video_props = calloc(qf_count, sizeof(*video_props));
VkQueueFamilyProperties2 *qfs = calloc(qf_count, sizeof(*qfs));
for (uint32_t i = 0; i < qf_count; i++) {
video_props[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_VIDEO_PROPERTIES_KHR;
qfs[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2;
qfs[i].pNext = &video_props[i];
}
vkGetPhysicalDeviceQueueFamilyProperties2(pdev, &qf_count, qfs);
int32_t encode_qf = -1;
int32_t transfer_qf = -1;
for (uint32_t i = 0; i < qf_count; i++) {
if ((qfs[i].queueFamilyProperties.queueFlags & VK_QUEUE_VIDEO_ENCODE_BIT_KHR) &&
(video_props[i].videoCodecOperations & VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_KHR)) {
encode_qf = (int32_t)i;
}
if ((qfs[i].queueFamilyProperties.queueFlags & VK_QUEUE_TRANSFER_BIT) && transfer_qf < 0)
transfer_qf = (int32_t)i;
}
if (encode_qf < 0) { fprintf(stderr, "no H.264 encode queue family found\n"); return 1; }
if (transfer_qf < 0) { fprintf(stderr, "no transfer-capable queue family found\n"); return 1; }
printf("encode queue family = %d, transfer queue family = %d\n", encode_qf, transfer_qf);
/* ---- Device ---- */
const char *dev_exts[] = {
"VK_KHR_video_queue",
"VK_KHR_video_encode_queue",
"VK_KHR_video_encode_h264",
"VK_KHR_synchronization2",
};
float qprio = 1.0f;
VkDeviceQueueCreateInfo qcis[2] = {
{
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = (uint32_t)transfer_qf,
.queueCount = 1,
.pQueuePriorities = &qprio,
},
{
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = (uint32_t)encode_qf,
.queueCount = 1,
.pQueuePriorities = &qprio,
},
};
VkDeviceCreateInfo dci = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.queueCreateInfoCount = (encode_qf == transfer_qf) ? 1 : 2,
.pQueueCreateInfos = qcis,
.enabledExtensionCount = sizeof(dev_exts) / sizeof(dev_exts[0]),
.ppEnabledExtensionNames = dev_exts,
};
VkDevice dev;
CHECK(vkCreateDevice(pdev, &dci, NULL, &dev));
#define LOAD(name) p##name = (PFN_vk##name)vkGetDeviceProcAddr(dev, "vk" #name)
LOAD(GetPhysicalDeviceVideoCapabilitiesKHR);
LOAD(GetPhysicalDeviceVideoFormatPropertiesKHR);
LOAD(CreateVideoSessionKHR);
LOAD(DestroyVideoSessionKHR);
LOAD(GetVideoSessionMemoryRequirementsKHR);
LOAD(BindVideoSessionMemoryKHR);
LOAD(CreateVideoSessionParametersKHR);
LOAD(DestroyVideoSessionParametersKHR);
LOAD(GetEncodedVideoSessionParametersKHR);
LOAD(CmdBeginVideoCodingKHR);
LOAD(CmdEndVideoCodingKHR);
LOAD(CmdEncodeVideoKHR);
pGetPhysicalDeviceVideoCapabilitiesKHR =
(PFN_vkGetPhysicalDeviceVideoCapabilitiesKHR)vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceVideoCapabilitiesKHR");
pGetPhysicalDeviceVideoFormatPropertiesKHR =
(PFN_vkGetPhysicalDeviceVideoFormatPropertiesKHR)vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceVideoFormatPropertiesKHR");
#undef LOAD
VkQueue queue;
vkGetDeviceQueue(dev, (uint32_t)encode_qf, 0, &queue);
VkQueue transfer_queue;
vkGetDeviceQueue(dev, (uint32_t)transfer_qf, 0, &transfer_queue);
int use_cabac = getenv("HASVK_ENC_CABAC") ? 1 : 0;
int use_pframe = getenv("HASVK_ENC_PFRAME") ? 1 : 0;
/* H.264 encode, 4:2:0, 8-bit */
VkVideoEncodeH264ProfileInfoKHR h264_profile = {
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_PROFILE_INFO_KHR,
.stdProfileIdc = use_cabac ? STD_VIDEO_H264_PROFILE_IDC_MAIN
: STD_VIDEO_H264_PROFILE_IDC_BASELINE,
};
VkVideoProfileInfoKHR profile = {
.sType = VK_STRUCTURE_TYPE_VIDEO_PROFILE_INFO_KHR,
.pNext = &h264_profile,
.videoCodecOperation = VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_KHR,
.chromaSubsampling = VK_VIDEO_CHROMA_SUBSAMPLING_420_BIT_KHR,
.lumaBitDepth = VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR,
.chromaBitDepth = VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR,
};
VkVideoProfileListInfoKHR profile_list = {
.sType = VK_STRUCTURE_TYPE_VIDEO_PROFILE_LIST_INFO_KHR,
.profileCount = 1,
.pProfiles = &profile,
};
/* capability */
VkVideoEncodeH264CapabilitiesKHR h264_caps = { .sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_CAPABILITIES_KHR };
VkVideoEncodeCapabilitiesKHR enc_caps = { .sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_CAPABILITIES_KHR, .pNext = &h264_caps };
VkVideoCapabilitiesKHR caps = { .sType = VK_STRUCTURE_TYPE_VIDEO_CAPABILITIES_KHR, .pNext = &enc_caps };
CHECK(pGetPhysicalDeviceVideoCapabilitiesKHR(pdev, &profile, &caps));
printf("caps: minBitstreamBufferOffsetAlignment=%llu minBitstreamBufferSizeAlignment=%llu maxDpbSlots=%u\n",
(unsigned long long)caps.minBitstreamBufferOffsetAlignment,
(unsigned long long)caps.minBitstreamBufferSizeAlignment,
caps.maxDpbSlots);
printf("enc caps: supportedEncodeFeedbackFlags=0x%x rateControlModes=0x%x\n",
enc_caps.supportedEncodeFeedbackFlags, enc_caps.rateControlModes);
/* src/dpb images */
VkPhysicalDeviceVideoFormatInfoKHR fmt_info = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VIDEO_FORMAT_INFO_KHR,
.pNext = &profile_list,
.imageUsage = VK_IMAGE_USAGE_VIDEO_ENCODE_SRC_BIT_KHR | VK_IMAGE_USAGE_VIDEO_ENCODE_DPB_BIT_KHR,
};
uint32_t fmt_count = 0;
CHECK(pGetPhysicalDeviceVideoFormatPropertiesKHR(pdev, &fmt_info, &fmt_count, NULL));
if (fmt_count == 0) { fprintf(stderr, "no video formats reported\n"); return 1; }
VkVideoFormatPropertiesKHR *fmts = calloc(fmt_count, sizeof(*fmts));
for (uint32_t i = 0; i < fmt_count; i++)
fmts[i].sType = VK_STRUCTURE_TYPE_VIDEO_FORMAT_PROPERTIES_KHR;
CHECK(pGetPhysicalDeviceVideoFormatPropertiesKHR(pdev, &fmt_info, &fmt_count, fmts));
VkFormat pic_format = fmts[0].format;
printf("picture format = %d\n", pic_format);
/* video sessions */
VkExtensionProperties std_hdr = {
.extensionName = VK_STD_VULKAN_VIDEO_CODEC_H264_ENCODE_EXTENSION_NAME,
.specVersion = VK_STD_VULKAN_VIDEO_CODEC_H264_ENCODE_SPEC_VERSION,
};
VkVideoSessionCreateInfoKHR sess_ci = {
.sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_CREATE_INFO_KHR,
.queueFamilyIndex = (uint32_t)encode_qf,
.pVideoProfile = &profile,
.pictureFormat = pic_format,
.maxCodedExtent = { WIDTH, HEIGHT },
.referencePictureFormat = pic_format,
.maxDpbSlots = 1,
.maxActiveReferencePictures = 0,
.pStdHeaderVersion = &std_hdr,
};
VkVideoSessionKHR session;
CHECK(pCreateVideoSessionKHR(dev, &sess_ci, NULL, &session));
uint32_t req_count = 0;
CHECK(pGetVideoSessionMemoryRequirementsKHR(dev, session, &req_count, NULL));
printf("video session memory requirement count = %u\n", req_count);
VkVideoSessionMemoryRequirementsKHR *reqs = calloc(req_count, sizeof(*reqs));
for (uint32_t i = 0; i < req_count; i++)
reqs[i].sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_MEMORY_REQUIREMENTS_KHR;
CHECK(pGetVideoSessionMemoryRequirementsKHR(dev, session, &req_count, reqs));
VkDeviceMemory *sess_mems = calloc(req_count, sizeof(*sess_mems));
VkBindVideoSessionMemoryInfoKHR *binds = calloc(req_count, sizeof(*binds));
for (uint32_t i = 0; i < req_count; i++) {
VkMemoryAllocateInfo ai = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = reqs[i].memoryRequirements.size,
.memoryTypeIndex = find_memory_type(&mem_props, reqs[i].memoryRequirements.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT),
};
CHECK(vkAllocateMemory(dev, &ai, NULL, &sess_mems[i]));
binds[i] = (VkBindVideoSessionMemoryInfoKHR) {
.sType = VK_STRUCTURE_TYPE_BIND_VIDEO_SESSION_MEMORY_INFO_KHR,
.memoryBindIndex = reqs[i].memoryBindIndex,
.memory = sess_mems[i],
.memoryOffset = 0,
.memorySize = reqs[i].memoryRequirements.size,
};
printf(" bindIndex=%u size=%llu\n", reqs[i].memoryBindIndex,
(unsigned long long)reqs[i].memoryRequirements.size);
}
CHECK(pBindVideoSessionMemoryKHR(dev, session, req_count, binds));
/* SPS / PPS */
int crop_r = getenv("HASVK_ENC_CROP_R") ? atoi(getenv("HASVK_ENC_CROP_R")) : 0;
int crop_b = getenv("HASVK_ENC_CROP_B") ? atoi(getenv("HASVK_ENC_CROP_B")) : 0;
crop_r &= ~1; crop_b &= ~1;
StdVideoH264SequenceParameterSet sps = {
.profile_idc = use_cabac ? STD_VIDEO_H264_PROFILE_IDC_MAIN
: STD_VIDEO_H264_PROFILE_IDC_BASELINE,
.level_idc = getenv("HASVK_ENC_LEVEL0") ? STD_VIDEO_H264_LEVEL_IDC_1_0
: STD_VIDEO_H264_LEVEL_IDC_3_0,
.chroma_format_idc = STD_VIDEO_H264_CHROMA_FORMAT_IDC_420,
.seq_parameter_set_id = 0,
.log2_max_frame_num_minus4 = getenv("HASVK_ENC_LOG2MAXFN")
? atoi(getenv("HASVK_ENC_LOG2MAXFN")) : 0,
.pic_order_cnt_type = getenv("HASVK_ENC_POC2") ? STD_VIDEO_H264_POC_TYPE_2
: STD_VIDEO_H264_POC_TYPE_0,
.log2_max_pic_order_cnt_lsb_minus4 = 0,
.max_num_ref_frames = 0,
.pic_width_in_mbs_minus1 = WIDTH_MBS - 1,
.pic_height_in_map_units_minus1 = HEIGHT_MBS - 1,
.frame_crop_right_offset = (uint32_t)(crop_r / 2),
.frame_crop_bottom_offset = (uint32_t)(crop_b / 2),
};
sps.flags.frame_mbs_only_flag = 1;
sps.flags.direct_8x8_inference_flag = 1;
sps.flags.frame_cropping_flag = (crop_r || crop_b) ? 1 : 0;
StdVideoH264PictureParameterSet pps = {
.seq_parameter_set_id = 0,
.pic_parameter_set_id = 0,
.num_ref_idx_l0_default_active_minus1 = 0,
.num_ref_idx_l1_default_active_minus1 = 0,
.weighted_bipred_idc = STD_VIDEO_H264_WEIGHTED_BIPRED_IDC_DEFAULT,
.pic_init_qp_minus26 = 0, /* pic_init_qp = 26 */
.pic_init_qs_minus26 = 0,
.chroma_qp_index_offset = 0,
.second_chroma_qp_index_offset = 0,
};
pps.flags.deblocking_filter_control_present_flag =
getenv("HASVK_ENC_NODEBLOCKCTRL") ? 0 : 1;
pps.flags.entropy_coding_mode_flag = use_cabac ? 1 : 0;
VkVideoEncodeH264SessionParametersAddInfoKHR add_info = {
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_SESSION_PARAMETERS_ADD_INFO_KHR,
.stdSPSCount = 1,
.pStdSPSs = &sps,
.stdPPSCount = 1,
.pStdPPSs = &pps,
};
VkVideoEncodeH264SessionParametersCreateInfoKHR h264_params_ci = {
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_SESSION_PARAMETERS_CREATE_INFO_KHR,
.maxStdSPSCount = 1,
.maxStdPPSCount = 1,
.pParametersAddInfo = &add_info,
};
VkVideoSessionParametersCreateInfoKHR params_ci = {
.sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_PARAMETERS_CREATE_INFO_KHR,
.pNext = &h264_params_ci,
.videoSession = session,
};
VkVideoSessionParametersKHR params;
CHECK(pCreateVideoSessionParametersKHR(dev, &params_ci, NULL, &params));
static uint8_t hdr_buf[512];
size_t hdr_size = 0;
{
VkVideoEncodeH264SessionParametersGetInfoKHR h264_get = {
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_SESSION_PARAMETERS_GET_INFO_KHR,
.writeStdSPS = VK_TRUE,
.stdSPSId = 0,
.writeStdPPS = VK_TRUE,
.stdPPSId = 0,
};
VkVideoEncodeSessionParametersGetInfoKHR get_info = {
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_SESSION_PARAMETERS_GET_INFO_KHR,
.pNext = &h264_get,
.videoSessionParameters = params,
};
size_t data_size = 0;
VkResult gr = pGetEncodedVideoSessionParametersKHR(dev, &get_info, NULL,
&data_size, NULL);
printf("GetEncoded SPS+PPS: result=%d size=%zu\n", gr, data_size);
if (gr == VK_SUCCESS && data_size > 0 && data_size <= sizeof(hdr_buf)) {
gr = pGetEncodedVideoSessionParametersKHR(dev, &get_info, NULL,
&data_size, hdr_buf);
if (gr == VK_SUCCESS) hdr_size = data_size;
}
}
/* source + recon/DPB */
VkImageCreateInfo img_ci = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = &profile_list,
.imageType = VK_IMAGE_TYPE_2D,
.format = pic_format,
.extent = { WIDTH, HEIGHT, 1 },
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_VIDEO_ENCODE_SRC_BIT_KHR,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
VkImage src_image;
CHECK(vkCreateImage(dev, &img_ci, NULL, &src_image));
VkDeviceMemory src_image_mem = alloc_and_bind_image(dev, &mem_props, src_image);
VkImageCreateInfo dpb_ci = img_ci;
dpb_ci.usage = VK_IMAGE_USAGE_VIDEO_ENCODE_DPB_BIT_KHR;
VkImage dpb_image;
CHECK(vkCreateImage(dev, &dpb_ci, NULL, &dpb_image));
VkDeviceMemory dpb_image_mem = alloc_and_bind_image(dev, &mem_props, dpb_image);
VkImageViewCreateInfo src_view_ci = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = src_image,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = pic_format,
.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 },
};
VkImageView src_view;
CHECK(vkCreateImageView(dev, &src_view_ci, NULL, &src_view));
VkImageViewCreateInfo dpb_view_ci = src_view_ci;
dpb_view_ci.image = dpb_image;
VkImageView dpb_view;
CHECK(vkCreateImageView(dev, &dpb_view_ci, NULL, &dpb_view));
VkImage dpb_image2 = VK_NULL_HANDLE;
VkImageView dpb_view2 = VK_NULL_HANDLE;
if (use_pframe) {
CHECK(vkCreateImage(dev, &dpb_ci, NULL, &dpb_image2));
alloc_and_bind_image(dev, &mem_props, dpb_image2);
VkImageViewCreateInfo v = dpb_view_ci;
v.image = dpb_image2;
CHECK(vkCreateImageView(dev, &v, NULL, &dpb_view2));
}
int shift_px = getenv("HASVK_ENC_SHIFT") ? atoi(getenv("HASVK_ENC_SHIFT")) : 0;
int src_step = getenv("HASVK_ENC_SRC_STEP") ? atoi(getenv("HASVK_ENC_SRC_STEP")) : 0;
int two_src = use_pframe && (shift_px || src_step);
VkImage src_image2 = VK_NULL_HANDLE;
VkImageView src_view2 = VK_NULL_HANDLE;
if (two_src) {
CHECK(vkCreateImage(dev, &img_ci, NULL, &src_image2));
alloc_and_bind_image(dev, &mem_props, src_image2);
VkImageViewCreateInfo v = src_view_ci;
v.image = src_image2;
CHECK(vkCreateImageView(dev, &v, NULL, &src_view2));
}
/* fill source image with flat gray via staging buffer */
VkDeviceSize luma_size = (VkDeviceSize)WIDTH * HEIGHT;
VkDeviceSize chroma_size = (VkDeviceSize)(WIDTH / 2) * (HEIGHT / 2) * 2;
VkDeviceSize frame_size = luma_size + chroma_size;
VkDeviceSize staging_size = frame_size * (two_src ? 2 : 1);
VkBufferCreateInfo staging_ci = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = staging_size,
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
};
VkBuffer staging_buf;
CHECK(vkCreateBuffer(dev, &staging_ci, NULL, &staging_buf));
VkDeviceMemory staging_mem = alloc_and_bind_buffer(dev, &mem_props, staging_buf,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
void *staging_map;
CHECK(vkMapMemory(dev, staging_mem, 0, staging_size, 0, &staging_map));
const char *src_in = getenv("HASVK_ENC_SRC_IN");
int src_i420 = getenv("HASVK_ENC_SRC_I420") ? 1 : 0;
int src_frame = getenv("HASVK_ENC_SRC_FRAME") ? atoi(getenv("HASVK_ENC_SRC_FRAME")) : 0;
if (src_in) {
if (!load_frame(src_in, src_frame, src_i420, (uint8_t *)staging_map))
return 1;
} else if (getenv("HASVK_ENC_GRADIENT")) {
uint8_t *sm = (uint8_t *)staging_map;
for (uint32_t yy = 0; yy < HEIGHT; yy++)
for (uint32_t xx = 0; xx < WIDTH; xx++)
sm[yy * WIDTH + xx] = (uint8_t)((xx * 255) / (WIDTH - 1));
if (getenv("HASVK_ENC_CHROMA")) {
uint8_t *ch = sm + luma_size;
uint32_t cw = WIDTH / 2, chh = HEIGHT / 2;
for (uint32_t cy = 0; cy < chh; cy++)
for (uint32_t cx = 0; cx < cw; cx++) {
ch[(cy * cw + cx) * 2 + 0] = (uint8_t)((cx * 255) / (cw - 1));
ch[(cy * cw + cx) * 2 + 1] = (uint8_t)((cy * 255) / (chh - 1));
}
} else {
memset(sm + luma_size, 128, (size_t)chroma_size);
}
} else {
memset(staging_map, 128, (size_t)frame_size);
}
if (two_src) {
uint8_t *f0 = (uint8_t *)staging_map, *f1 = f0 + frame_size;
if (shift_px) {
shift_frame(f0, f1, shift_px, 0);
} else if (!src_in) {
fprintf(stderr, "HASVK_ENC_SRC_STEP needs HASVK_ENC_SRC_IN\n");
return 1;
} else if (!load_frame(src_in, src_frame + src_step, src_i420, f1)) {
return 1;
}
}
const char *src_out = getenv("HASVK_ENC_SRC_OUT");
if (src_out) {
const uint8_t *sm = (const uint8_t *)staging_map;
const uint8_t *ch = sm + luma_size;
uint32_t cw = WIDTH / 2, chh = HEIGHT / 2;
FILE *sf = fopen(src_out, "wb");
if (sf) {
fwrite(sm, 1, (size_t)luma_size, sf);
for (uint32_t i = 0; i < cw * chh; i++)
fputc(ch[i * 2 + 0], sf);
for (uint32_t i = 0; i < cw * chh; i++)
fputc(ch[i * 2 + 1], sf);
fclose(sf);
}
}
vkUnmapMemory(dev, staging_mem);
VkDeviceSize dst_align = caps.minBitstreamBufferSizeAlignment ? caps.minBitstreamBufferSizeAlignment : 4096;
VkDeviceSize dst_size = ((VkDeviceSize)1 << 20);
dst_size = (dst_size + dst_align - 1) & ~(dst_align - 1);
VkBufferCreateInfo dst_ci = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = &profile_list,
.size = dst_size,
.usage = VK_BUFFER_USAGE_VIDEO_ENCODE_DST_BIT_KHR,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
};
VkBuffer dst_buf;
CHECK(vkCreateBuffer(dev, &dst_ci, NULL, &dst_buf));
VkDeviceMemory dst_mem = alloc_and_bind_buffer(dev, &mem_props, dst_buf,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
void *dst_map;
CHECK(vkMapMemory(dev, dst_mem, 0, dst_size, 0, &dst_map));
memset(dst_map, 0xCD, (size_t)dst_size);
VkCommandPoolCreateInfo xfer_pool_ci = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = (uint32_t)transfer_qf,
};
VkCommandPool xfer_pool;
CHECK(vkCreateCommandPool(dev, &xfer_pool_ci, NULL, &xfer_pool));
VkCommandBufferAllocateInfo xfer_cb_ai = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = xfer_pool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
VkCommandBuffer xfer_cmd;
CHECK(vkAllocateCommandBuffers(dev, &xfer_cb_ai, &xfer_cmd));
VkCommandBufferBeginInfo xfer_begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
CHECK(vkBeginCommandBuffer(xfer_cmd, &xfer_begin_info));
VkImageMemoryBarrier to_xfer = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = src_image,
.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 },
};
VkImageMemoryBarrier to_xfer_both[2] = { to_xfer, to_xfer };
to_xfer_both[1].image = src_image2;
vkCmdPipelineBarrier(xfer_cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 0, NULL, 0, NULL, two_src ? 2 : 1, to_xfer_both);
VkBufferImageCopy regions[2] = {
{ /* luma plane */
.bufferOffset = 0,
.imageSubresource = { VK_IMAGE_ASPECT_PLANE_0_BIT, 0, 0, 1 },
.imageExtent = { WIDTH, HEIGHT, 1 },
},
{ /* chroma plane (interleaved UV, half res) */
.bufferOffset = luma_size,
.imageSubresource = { VK_IMAGE_ASPECT_PLANE_1_BIT, 0, 0, 1 },
.imageExtent = { WIDTH / 2, HEIGHT / 2, 1 },
},
};
vkCmdCopyBufferToImage(xfer_cmd, staging_buf, src_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 2, regions);
if (two_src) {
VkBufferImageCopy r2[2] = { regions[0], regions[1] };
r2[0].bufferOffset = frame_size;
r2[1].bufferOffset = frame_size + luma_size;
vkCmdCopyBufferToImage(xfer_cmd, staging_buf, src_image2, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 2, r2);
}
VkImageMemoryBarrier to_encode_src = to_xfer;
to_encode_src.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
to_encode_src.dstAccessMask = 0;
to_encode_src.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
to_encode_src.newLayout = VK_IMAGE_LAYOUT_VIDEO_ENCODE_SRC_KHR;
VkImageMemoryBarrier to_encode_dpb = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_VIDEO_ENCODE_DPB_KHR,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dpb_image,
.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 },
};
VkImageMemoryBarrier barriers2[4] = { to_encode_src, to_encode_dpb };
uint32_t barrier2_count = 2;
if (use_pframe) {
VkImageMemoryBarrier to_encode_dpb2 = to_encode_dpb;
to_encode_dpb2.image = dpb_image2;
barriers2[barrier2_count++] = to_encode_dpb2;
}
if (two_src) {
VkImageMemoryBarrier to_encode_src2 = to_encode_src;
to_encode_src2.image = src_image2;
barriers2[barrier2_count++] = to_encode_src2;
}
vkCmdPipelineBarrier(xfer_cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0, 0, NULL, 0, NULL, barrier2_count, barriers2);
CHECK(vkEndCommandBuffer(xfer_cmd));
/* HASVK_ENC_SEMSYNC */
int use_semsync = getenv("HASVK_ENC_SEMSYNC") ? 1 : 0;
VkSemaphore xfer_sem = VK_NULL_HANDLE;
if (use_semsync) {
VkSemaphoreCreateInfo sci = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
CHECK(vkCreateSemaphore(dev, &sci, NULL, &xfer_sem));
}
VkFenceCreateInfo xfer_fence_ci = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
VkFence xfer_fence;
CHECK(vkCreateFence(dev, &xfer_fence_ci, NULL, &xfer_fence));
VkSubmitInfo xfer_submit = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &xfer_cmd,
.signalSemaphoreCount = use_semsync ? 1u : 0u,
.pSignalSemaphores = use_semsync ? &xfer_sem : NULL,
};
printf("submitting transfer prep...\n");
fflush(stdout);
CHECK(vkQueueSubmit(transfer_queue, 1, &xfer_submit, xfer_fence));
if (!use_semsync) {
VkResult xfer_wait = vkWaitForFences(dev, 1, &xfer_fence, VK_TRUE, 10ull * 1000 * 1000 * 1000);
printf("transfer wait result = %d\n", xfer_wait);
if (xfer_wait != VK_SUCCESS) { fprintf(stderr, "transfer prep failed/hung\n"); return 3; }
} else {
printf("transfer submitted (semaphore sync, no CPU wait)\n");
}
VkCommandPoolCreateInfo pool_ci = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = (uint32_t)encode_qf,
};
VkCommandPool pool;
CHECK(vkCreateCommandPool(dev, &pool_ci, NULL, &pool));
VkCommandBufferAllocateInfo cb_ai = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = pool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
VkCommandBuffer cmd;
CHECK(vkAllocateCommandBuffers(dev, &cb_ai, &cmd));
VkQueryPoolVideoEncodeFeedbackCreateInfoKHR fb_ci = {
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_VIDEO_ENCODE_FEEDBACK_CREATE_INFO_KHR,
.pNext = &profile,
.encodeFeedbackFlags =
VK_VIDEO_ENCODE_FEEDBACK_BITSTREAM_BUFFER_OFFSET_BIT_KHR |
VK_VIDEO_ENCODE_FEEDBACK_BITSTREAM_BYTES_WRITTEN_BIT_KHR,
};
VkQueryPoolCreateInfo qp_ci = {
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
.pNext = &fb_ci,
.queryType = VK_QUERY_TYPE_VIDEO_ENCODE_FEEDBACK_KHR,
.queryCount = 2,
};
VkQueryPool qpool;
CHECK(vkCreateQueryPool(dev, &qp_ci, NULL, &qpool));
VkCommandBufferBeginInfo begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
CHECK(vkBeginCommandBuffer(cmd, &begin_info));
vkCmdResetQueryPool(cmd, qpool, 0, 2);
if (getenv("HASVK_BISECT_TRULY_EMPTY")) {
CHECK(vkEndCommandBuffer(cmd));
VkFenceCreateInfo fci = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
VkFence f;
CHECK(vkCreateFence(dev, &fci, NULL, &f));
VkSubmitInfo si = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &cmd };
printf("submitting TRULY EMPTY (no video calls at all)...\n");
fflush(stdout);
CHECK(vkQueueSubmit(queue, 1, &si, f));
VkResult r = vkWaitForFences(dev, 1, &f, VK_TRUE, 15ull * 1000 * 1000 * 1000);
printf("truly-empty wait result = %d\n", r);
return r == VK_SUCCESS ? 0 : 2;
}
int qp = getenv("HASVK_ENC_QP") ? atoi(getenv("HASVK_ENC_QP")) : 26;
uint64_t bitrate = getenv("HASVK_ENC_BITRATE")
? strtoull(getenv("HASVK_ENC_BITRATE"), NULL, 10) : 0;
if (qp < 0) qp = 0;
if (qp > 51) qp = 51;
int num_slices = getenv("HASVK_ENC_SLICES") ? atoi(getenv("HASVK_ENC_SLICES")) : 1;
if (num_slices < 1) num_slices = 1;
if (num_slices > HEIGHT_MBS) num_slices = HEIGHT_MBS;
VkVideoPictureResourceInfoKHR src_res = {
.sType = VK_STRUCTURE_TYPE_VIDEO_PICTURE_RESOURCE_INFO_KHR,
.codedExtent = { WIDTH, HEIGHT }, .baseArrayLayer = 0, .imageViewBinding = src_view,
};
VkVideoPictureResourceInfoKHR src_res2 = src_res;
src_res2.imageViewBinding = two_src ? src_view2 : src_view;
VkVideoPictureResourceInfoKHR dpb_res0 = {
.sType = VK_STRUCTURE_TYPE_VIDEO_PICTURE_RESOURCE_INFO_KHR,
.codedExtent = { WIDTH, HEIGHT }, .baseArrayLayer = 0, .imageViewBinding = dpb_view,
};
VkVideoPictureResourceInfoKHR dpb_res1 = {
.sType = VK_STRUCTURE_TYPE_VIDEO_PICTURE_RESOURCE_INFO_KHR,
.codedExtent = { WIDTH, HEIGHT }, .baseArrayLayer = 0, .imageViewBinding = dpb_view2,
};
int num_frames = use_pframe ? 2 : 1;
VkDeviceSize base_off = getenv("HASVK_ENC_DSTOFF")
? (VkDeviceSize)atoi(getenv("HASVK_ENC_DSTOFF")) : 0;
VkDeviceSize frame_dst_offset[2] = { base_off, dst_size / 2 };
for (int frame = 0; frame < num_frames; frame++) {
bool is_p_frame = (frame == 1) && !getenv("HASVK_ENC_FORCE_I");
VkVideoReferenceSlotInfoKHR begin_slots[2];
uint32_t begin_slot_count = 0;
if (is_p_frame) {
begin_slots[begin_slot_count++] = (VkVideoReferenceSlotInfoKHR){
.sType = VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_INFO_KHR,
.slotIndex = 0, .pPictureResource = &dpb_res0 };
begin_slots[begin_slot_count++] = (VkVideoReferenceSlotInfoKHR){
.sType = VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_INFO_KHR,
.slotIndex = 1, .pPictureResource = &dpb_res1 };
} else {
begin_slots[begin_slot_count++] = (VkVideoReferenceSlotInfoKHR){
.sType = VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_INFO_KHR,
.slotIndex = 0, .pPictureResource = &dpb_res0 };
}
VkVideoEncodeH264RateControlLayerInfoKHR h264_rc_layer = {
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_RATE_CONTROL_LAYER_INFO_KHR,
};
VkVideoEncodeRateControlLayerInfoKHR rc_layer = {
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_RATE_CONTROL_LAYER_INFO_KHR,
.pNext = &h264_rc_layer,
.averageBitrate = bitrate,
.maxBitrate = bitrate,
.frameRateNumerator = 30,
.frameRateDenominator = 1,
};
VkVideoEncodeRateControlInfoKHR rc_info = {
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_RATE_CONTROL_INFO_KHR,
.rateControlMode = getenv("HASVK_ENC_VBR")
? VK_VIDEO_ENCODE_RATE_CONTROL_MODE_VBR_BIT_KHR
: VK_VIDEO_ENCODE_RATE_CONTROL_MODE_CBR_BIT_KHR,
.layerCount = 1,
.pLayers = &rc_layer,
};
VkVideoBeginCodingInfoKHR begin_coding = {
.sType = VK_STRUCTURE_TYPE_VIDEO_BEGIN_CODING_INFO_KHR,
.pNext = bitrate ? &rc_info : NULL,
.videoSession = session,
.videoSessionParameters = params,
.referenceSlotCount = begin_slot_count,
.pReferenceSlots = begin_slots,
};
pCmdBeginVideoCodingKHR(cmd, &begin_coding);
StdVideoEncodeH264PictureInfo std_pic_info = {
.seq_parameter_set_id = 0,
.pic_parameter_set_id = 0,
.idr_pic_id = 0,
.primary_pic_type = is_p_frame ? STD_VIDEO_H264_PICTURE_TYPE_P
: STD_VIDEO_H264_PICTURE_TYPE_IDR,
.frame_num = (uint16_t)frame,
.PicOrderCnt = (int32_t)(frame * 2),
};
std_pic_info.flags.IdrPicFlag = is_p_frame ? 0 : 1;
std_pic_info.flags.is_reference = 1;
StdVideoEncodeH264SliceHeader std_slice_hdr[HEIGHT_MBS];
VkVideoEncodeH264NaluSliceInfoKHR nalu_slice[HEIGHT_MBS];
for (int s = 0; s < num_slices; s++) {
uint32_t start_row = (uint32_t)s * HEIGHT_MBS / num_slices;
std_slice_hdr[s] = (StdVideoEncodeH264SliceHeader){
.first_mb_in_slice = start_row * WIDTH_MBS,
.slice_type = is_p_frame ? STD_VIDEO_H264_SLICE_TYPE_P
: STD_VIDEO_H264_SLICE_TYPE_I,
.slice_qp_delta = qp - 26,
.cabac_init_idc = STD_VIDEO_H264_CABAC_INIT_IDC_0,
/* value 1 ("_ENABLED") == H.264 disable_deblocking_filter_idc==1. */
.disable_deblocking_filter_idc = STD_VIDEO_H264_DISABLE_DEBLOCKING_FILTER_IDC_ENABLED,
};
nalu_slice[s] = (VkVideoEncodeH264NaluSliceInfoKHR){
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_NALU_SLICE_INFO_KHR,
.constantQp = qp,
.pStdSliceHeader = &std_slice_hdr[s],
};
}
VkVideoEncodeH264PictureInfoKHR h264_pic_info = {
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_PICTURE_INFO_KHR,
.naluSliceEntryCount = (uint32_t)num_slices,
.pNaluSliceEntries = nalu_slice,
.pStdPictureInfo = &std_pic_info,
};
VkVideoReferenceSlotInfoKHR setup_slot = {
.sType = VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_INFO_KHR,
.slotIndex = is_p_frame ? 1 : 0,
.pPictureResource = is_p_frame ? &dpb_res1 : &dpb_res0,
};
VkVideoReferenceSlotInfoKHR ref_slot = {
.sType = VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_INFO_KHR,
.slotIndex = 0,
.pPictureResource = &dpb_res0,
};
VkVideoEncodeInfoKHR encode_info = {
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_INFO_KHR,
.pNext = &h264_pic_info,
.dstBuffer = dst_buf,
.dstBufferOffset = frame_dst_offset[frame],
.dstBufferRange = dst_size / 2,
.srcPictureResource = is_p_frame ? src_res2 : src_res,
.pSetupReferenceSlot = &setup_slot,
.referenceSlotCount = is_p_frame ? 1 : 0,
.pReferenceSlots = is_p_frame ? &ref_slot : NULL,
};
vkCmdBeginQuery(cmd, qpool, frame, 0);
pCmdEncodeVideoKHR(cmd, &encode_info);
vkCmdEndQuery(cmd, qpool, frame);
VkVideoEndCodingInfoKHR end_coding = { .sType = VK_STRUCTURE_TYPE_VIDEO_END_CODING_INFO_KHR };
pCmdEndVideoCodingKHR(cmd, &end_coding);
}
CHECK(vkEndCommandBuffer(cmd));
VkFenceCreateInfo fence_ci = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
VkFence fence;
CHECK(vkCreateFence(dev, &fence_ci, NULL, &fence));
VkPipelineStageFlags enc_wait_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkSubmitInfo submit = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &cmd,
.waitSemaphoreCount = use_semsync ? 1u : 0u,
.pWaitSemaphores = use_semsync ? &xfer_sem : NULL,
.pWaitDstStageMask = use_semsync ? &enc_wait_stage : NULL,
};
printf("submitting...\n");
fflush(stdout);
CHECK(vkQueueSubmit(queue, 1, &submit, fence));
VkResult wait_result = vkWaitForFences(dev, 1, &fence, VK_TRUE, 15ull * 1000 * 1000 * 1000);
printf("wait result = %d (%s)\n", wait_result,
wait_result == VK_SUCCESS ? "SUCCESS" :
wait_result == VK_TIMEOUT ? "TIMEOUT" :
wait_result == VK_ERROR_DEVICE_LOST ? "DEVICE_LOST" : "OTHER");
if (wait_result == VK_SUCCESS) {
const uint8_t *bytes = (const uint8_t *)dst_map;
const char *out_path = getenv("HASVK_ENC_OUT");
if (!out_path) out_path = "/tmp/hasvk_enc_out.h264";
FILE *of = fopen(out_path, "wb");
if (of && hdr_size > 0)
fwrite(hdr_buf, 1, hdr_size, of);
for (int f = 0; f < num_frames; f++) {
struct { uint32_t offset; uint32_t bytesWritten; } fb = { 0, 0 };
VkResult qr = vkGetQueryPoolResults(dev, qpool, f, 1, sizeof(fb), &fb,
sizeof(fb), VK_QUERY_RESULT_WAIT_BIT);
printf("frame %d (%s): bytesWritten=%u (query result %d)\n",
f, f == 0 ? "I" : "P", fb.bytesWritten, qr);
if (of && qr == VK_SUCCESS)
fwrite(bytes + frame_dst_offset[f], 1, fb.bytesWritten, of);
}
if (of) {
fclose(of);
printf("wrote %zu (SPS/PPS) + %d frame(s) to %s\n",
hdr_size, num_frames, out_path);
}
}
vkUnmapMemory(dev, dst_mem);
printf("done.\n");
return wait_result == VK_SUCCESS ? 0 : 2;
}