/* 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 #include #include #include #include #include #include #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, ¶ms_ci, NULL, ¶ms)); 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; }