mirror of
https://github.com/elwrcl/intel-hard-lab.git
synced 2026-10-31 07:48:02 +03:00
965 lines
40 KiB
C
965 lines
40 KiB
C
/* 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, ¶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;
|
|
}
|