aboutsummaryrefslogtreecommitdiffhomepage
path: root/subprojects/packagefiles/ffmpeg-nvenc-runtime-api-version.patch
blob: eb3bd5fc644a83d2040618ef3ee5fceb13987137 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
--- a/libavcodec/nvenc.c
+++ b/libavcodec/nvenc.c
@@ -235,7 +235,15 @@
 
 static void nvenc_print_driver_requirement(AVCodecContext *avctx, int level)
 {
-#if NVENCAPI_CHECK_VERSION(13, 2)
+/* With runtime API version negotiation the build-time header version no
+ * longer dictates the required driver; the runtime floor is API 11.1. */
+#if 1
+# if defined(_WIN32) || defined(__CYGWIN__)
+    const char *minver = "471.41";
+# else
+    const char *minver = "470.57.02";
+# endif
+#elif NVENCAPI_CHECK_VERSION(13, 2)
     const char *minver = "(unknown)";
 #elif NVENCAPI_CHECK_VERSION(13, 1)
     const char *minver = "610.00";
@@ -279,6 +287,193 @@
 #define to_nv_color_trc(n)      (uint32_t)(n)
 #endif
 
+/*
+ * Runtime NVENC API version negotiation, see nvenc.h.
+ *
+ * Each row holds the exact NV_ENC_*_VER constants of one SDK's nvEncodeAPI.h.
+ * A struct version word is composed as
+ *     major | (minor << 24) | (struct_revision << 16) | (0x7 << 28)
+ * optionally OR'd with (1u << 31); the driver validates the whole word, so
+ * presenting a row of these values is indistinguishable from an FFmpeg that
+ * was actually compiled against that SDK's headers.
+ */
+#define NVENC_API_WORD(major, minor) \
+    ((uint32_t)(major) | ((uint32_t)(minor) << 24))
+#define NVENC_STRUCT_VER(major, minor, rev) \
+    (NVENC_API_WORD(major, minor) | ((uint32_t)(rev) << 16) | (0x7u << 28))
+#define NVENC_STRUCT_VER_RES(major, minor, rev) \
+    (NVENC_STRUCT_VER(major, minor, rev) | (1u << 31))
+
+// Newest first; negotiation picks the first row the driver supports.
+static const NvencApiVersion nvenc_api_versions[] = {
+    { // SDK 13.0, driver >= 570.0 (Linux) / 570.0 (Windows)
+        .packed                  = NVENC_API_PACKED(13, 0),
+        .api                     = NVENC_API_WORD(13, 0),
+        .function_list           = NVENC_STRUCT_VER(13, 0, 2),
+        .open_session_params     = NVENC_STRUCT_VER(13, 0, 1),
+        .caps_param              = NVENC_STRUCT_VER(13, 0, 1),
+        .initialize_params       = NVENC_STRUCT_VER_RES(13, 0, 7),
+        .config                  = NVENC_STRUCT_VER_RES(13, 0, 9),
+        .preset_config           = NVENC_STRUCT_VER_RES(13, 0, 5),
+        .pic_params              = NVENC_STRUCT_VER_RES(13, 0, 7),
+        .lock_bitstream          = NVENC_STRUCT_VER_RES(13, 0, 2),
+        .lock_input_buffer       = NVENC_STRUCT_VER(13, 0, 1),
+        .map_input_resource      = NVENC_STRUCT_VER(13, 0, 4),
+        .register_resource       = NVENC_STRUCT_VER(13, 0, 5),
+        .create_input_buffer     = NVENC_STRUCT_VER(13, 0, 2),
+        .create_bitstream_buffer = NVENC_STRUCT_VER(13, 0, 1),
+        .reconfigure_params      = NVENC_STRUCT_VER_RES(13, 0, 2),
+        .sequence_param_payload  = NVENC_STRUCT_VER(13, 0, 1),
+    },
+    { // SDK 12.2, driver >= 550.54.14 (Linux) / 551.76 (Windows)
+        .packed                  = NVENC_API_PACKED(12, 2),
+        .api                     = NVENC_API_WORD(12, 2),
+        .function_list           = NVENC_STRUCT_VER(12, 2, 2),
+        .open_session_params     = NVENC_STRUCT_VER(12, 2, 1),
+        .caps_param              = NVENC_STRUCT_VER(12, 2, 1),
+        .initialize_params       = NVENC_STRUCT_VER_RES(12, 2, 7),
+        .config                  = NVENC_STRUCT_VER_RES(12, 2, 9),
+        .preset_config           = NVENC_STRUCT_VER_RES(12, 2, 5),
+        .pic_params              = NVENC_STRUCT_VER_RES(12, 2, 7),
+        .lock_bitstream          = NVENC_STRUCT_VER_RES(12, 2, 2),
+        .lock_input_buffer       = NVENC_STRUCT_VER(12, 2, 1),
+        .map_input_resource      = NVENC_STRUCT_VER(12, 2, 4),
+        .register_resource       = NVENC_STRUCT_VER(12, 2, 5),
+        .create_input_buffer     = NVENC_STRUCT_VER(12, 2, 2),
+        .create_bitstream_buffer = NVENC_STRUCT_VER(12, 2, 1),
+        .reconfigure_params      = NVENC_STRUCT_VER_RES(12, 2, 2),
+        .sequence_param_payload  = NVENC_STRUCT_VER(12, 2, 1),
+    },
+    { // SDK 12.1, driver >= 530.41.03 (Linux) / 531.61 (Windows)
+        .packed                  = NVENC_API_PACKED(12, 1),
+        .api                     = NVENC_API_WORD(12, 1),
+        .function_list           = NVENC_STRUCT_VER(12, 1, 2),
+        .open_session_params     = NVENC_STRUCT_VER(12, 1, 1),
+        .caps_param              = NVENC_STRUCT_VER(12, 1, 1),
+        .initialize_params       = NVENC_STRUCT_VER_RES(12, 1, 6),
+        .config                  = NVENC_STRUCT_VER_RES(12, 1, 8),
+        .preset_config           = NVENC_STRUCT_VER_RES(12, 1, 4),
+        .pic_params              = NVENC_STRUCT_VER_RES(12, 1, 6),
+        .lock_bitstream          = NVENC_STRUCT_VER_RES(12, 1, 1),
+        .lock_input_buffer       = NVENC_STRUCT_VER(12, 1, 1),
+        .map_input_resource      = NVENC_STRUCT_VER(12, 1, 4),
+        .register_resource       = NVENC_STRUCT_VER(12, 1, 4),
+        .create_input_buffer     = NVENC_STRUCT_VER(12, 1, 1),
+        .create_bitstream_buffer = NVENC_STRUCT_VER(12, 1, 1),
+        .reconfigure_params      = NVENC_STRUCT_VER_RES(12, 1, 1),
+        .sequence_param_payload  = NVENC_STRUCT_VER(12, 1, 1),
+    },
+    { // SDK 12.0, driver >= 520.56.06 (Linux) / 522.25 (Windows)
+        .packed                  = NVENC_API_PACKED(12, 0),
+        .api                     = NVENC_API_WORD(12, 0),
+        .function_list           = NVENC_STRUCT_VER(12, 0, 2),
+        .open_session_params     = NVENC_STRUCT_VER(12, 0, 1),
+        .caps_param              = NVENC_STRUCT_VER(12, 0, 1),
+        .initialize_params       = NVENC_STRUCT_VER_RES(12, 0, 5),
+        .config                  = NVENC_STRUCT_VER_RES(12, 0, 8),
+        .preset_config           = NVENC_STRUCT_VER_RES(12, 0, 4),
+        .pic_params              = NVENC_STRUCT_VER_RES(12, 0, 6),
+        .lock_bitstream          = NVENC_STRUCT_VER(12, 0, 2),
+        .lock_input_buffer       = NVENC_STRUCT_VER(12, 0, 1),
+        .map_input_resource      = NVENC_STRUCT_VER(12, 0, 4),
+        .register_resource       = NVENC_STRUCT_VER(12, 0, 4),
+        .create_input_buffer     = NVENC_STRUCT_VER(12, 0, 1),
+        .create_bitstream_buffer = NVENC_STRUCT_VER(12, 0, 1),
+        .reconfigure_params      = NVENC_STRUCT_VER_RES(12, 0, 1),
+        .sequence_param_payload  = NVENC_STRUCT_VER(12, 0, 1),
+    },
+    { // SDK 11.1, driver >= 470.57.02 (Linux) / 471.41 (Windows) - Kepler's last branch
+        .packed                  = NVENC_API_PACKED(11, 1),
+        .api                     = NVENC_API_WORD(11, 1),
+        .function_list           = NVENC_STRUCT_VER(11, 1, 2),
+        .open_session_params     = NVENC_STRUCT_VER(11, 1, 1),
+        .caps_param              = NVENC_STRUCT_VER(11, 1, 1),
+        .initialize_params       = NVENC_STRUCT_VER_RES(11, 1, 5),
+        .config                  = NVENC_STRUCT_VER_RES(11, 1, 7),
+        .preset_config           = NVENC_STRUCT_VER_RES(11, 1, 4),
+        .pic_params              = NVENC_STRUCT_VER_RES(11, 1, 4),
+        .lock_bitstream          = NVENC_STRUCT_VER(11, 1, 1),
+        .lock_input_buffer       = NVENC_STRUCT_VER(11, 1, 1),
+        .map_input_resource      = NVENC_STRUCT_VER(11, 1, 4),
+        .register_resource       = NVENC_STRUCT_VER(11, 1, 3),
+        .create_input_buffer     = NVENC_STRUCT_VER(11, 1, 1),
+        .create_bitstream_buffer = NVENC_STRUCT_VER(11, 1, 1),
+        .reconfigure_params      = NVENC_STRUCT_VER_RES(11, 1, 1),
+        .sequence_param_payload  = NVENC_STRUCT_VER(11, 1, 1),
+    },
+};
+
+/* The newest row must be exactly what this build would present anyway; if any
+ * of these fire, the headers changed and the whole table needs re-validation. */
+_Static_assert(NVENC_API_WORD(13, 0)          == NVENCAPI_VERSION,                          "nvenc api version table outdated");
+_Static_assert(NVENC_STRUCT_VER(13, 0, 2)     == NV_ENCODE_API_FUNCTION_LIST_VER,           "nvenc api version table outdated");
+_Static_assert(NVENC_STRUCT_VER(13, 0, 1)     == NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER,  "nvenc api version table outdated");
+_Static_assert(NVENC_STRUCT_VER(13, 0, 1)     == NV_ENC_CAPS_PARAM_VER,                     "nvenc api version table outdated");
+_Static_assert(NVENC_STRUCT_VER_RES(13, 0, 7) == NV_ENC_INITIALIZE_PARAMS_VER,              "nvenc api version table outdated");
+_Static_assert(NVENC_STRUCT_VER_RES(13, 0, 9) == NV_ENC_CONFIG_VER,                         "nvenc api version table outdated");
+_Static_assert(NVENC_STRUCT_VER_RES(13, 0, 5) == NV_ENC_PRESET_CONFIG_VER,                  "nvenc api version table outdated");
+_Static_assert(NVENC_STRUCT_VER_RES(13, 0, 7) == NV_ENC_PIC_PARAMS_VER,                     "nvenc api version table outdated");
+_Static_assert(NVENC_STRUCT_VER_RES(13, 0, 2) == NV_ENC_LOCK_BITSTREAM_VER,                 "nvenc api version table outdated");
+_Static_assert(NVENC_STRUCT_VER(13, 0, 1)     == NV_ENC_LOCK_INPUT_BUFFER_VER,              "nvenc api version table outdated");
+_Static_assert(NVENC_STRUCT_VER(13, 0, 4)     == NV_ENC_MAP_INPUT_RESOURCE_VER,             "nvenc api version table outdated");
+_Static_assert(NVENC_STRUCT_VER(13, 0, 5)     == NV_ENC_REGISTER_RESOURCE_VER,              "nvenc api version table outdated");
+_Static_assert(NVENC_STRUCT_VER(13, 0, 2)     == NV_ENC_CREATE_INPUT_BUFFER_VER,            "nvenc api version table outdated");
+_Static_assert(NVENC_STRUCT_VER(13, 0, 1)     == NV_ENC_CREATE_BITSTREAM_BUFFER_VER,        "nvenc api version table outdated");
+_Static_assert(NVENC_STRUCT_VER_RES(13, 0, 2) == NV_ENC_RECONFIGURE_PARAMS_VER,             "nvenc api version table outdated");
+_Static_assert(NVENC_STRUCT_VER(13, 0, 1)     == NV_ENC_SEQUENCE_PARAM_PAYLOAD_VER,         "nvenc api version table outdated");
+
+/* Layout guards for the compatibility padding after NV_ENC_INITIALIZE_PARAMS /
+ * NV_ENC_RECONFIGURE_PARAMS / NV_ENC_LOCK_BITSTREAM (their pre-12.2 layouts
+ * are up to 8 bytes larger: 1808, 1824 and 1552 bytes respectively) and for
+ * the legacy bit-depth pokes below. */
+_Static_assert(sizeof(NV_ENC_INITIALIZE_PARAMS)  == 1800, "unexpected NV_ENC_INITIALIZE_PARAMS layout");
+_Static_assert(sizeof(NV_ENC_RECONFIGURE_PARAMS) == 1816, "unexpected NV_ENC_RECONFIGURE_PARAMS layout");
+_Static_assert(sizeof(NV_ENC_LOCK_BITSTREAM)     == 1544, "unexpected NV_ENC_LOCK_BITSTREAM layout");
+_Static_assert(offsetof(NV_ENC_CONFIG_HEVC, tier) == 4 && offsetof(NV_ENC_CONFIG_HEVC, idrPeriod) == 20,
+               "unexpected NV_ENC_CONFIG_HEVC layout");
+_Static_assert(offsetof(NV_ENC_CONFIG_AV1, tier) == 4 && offsetof(NV_ENC_CONFIG_AV1, idrPeriod) == 20,
+               "unexpected NV_ENC_CONFIG_AV1 layout");
+
+/*
+ * SDK 12.2 removed the NV_ENC_CONFIG_HEVC.pixelBitDepthMinus8 and
+ * NV_ENC_CONFIG_AV1.inputPixelBitDepthMinus8/pixelBitDepthMinus8 bitfields
+ * and replaced them with the inputBitDepth/outputBitDepth enums that live at
+ * different offsets. Drivers negotiated to API < 12.2 still read the old bit
+ * positions (reserved-and-zero in the compiled 12.2+ layout), so 10 bit
+ * encoding on those drivers has to set the old bits by hand.
+ *
+ * Verified against the SDK 11.1/12.0/12.1 headers: both codec configs keep
+ * their flag bitfields in the uint32 word at byte offset 16 (between
+ * maxCUSize/maxPartSize at 12 and idrPeriod at 20, anchored by the asserts
+ * above). Within that word:
+ *   NV_ENC_CONFIG_HEVC: pixelBitDepthMinus8 = bits 11-13
+ *   NV_ENC_CONFIG_AV1:  inputPixelBitDepthMinus8 = bits 12-14,
+ *                       pixelBitDepthMinus8      = bits 15-17
+ */
+static void nvenc_legacy_bitfield_poke(void *codec_config, size_t word_offset,
+                                       unsigned bit_offset, unsigned width, uint32_t value)
+{
+    uint32_t word;
+    memcpy(&word, (uint8_t*)codec_config + word_offset, sizeof(word));
+    word &= ~(((1u << width) - 1) << bit_offset);
+    word |= (value & ((1u << width) - 1)) << bit_offset;
+    memcpy((uint8_t*)codec_config + word_offset, &word, sizeof(word));
+}
+
+static void nvenc_set_legacy_hevc_bit_depth(NV_ENC_CONFIG_HEVC *hevc, uint32_t depth_minus8)
+{
+    nvenc_legacy_bitfield_poke(hevc, 16, 11, 3, depth_minus8);
+}
+
+#if CONFIG_AV1_NVENC_ENCODER
+static void nvenc_set_legacy_av1_bit_depth(NV_ENC_CONFIG_AV1 *av1,
+                                           uint32_t input_depth_minus8, uint32_t output_depth_minus8)
+{
+    nvenc_legacy_bitfield_poke(av1, 16, 12, 3, input_depth_minus8);
+    nvenc_legacy_bitfield_poke(av1, 16, 15, 3, output_depth_minus8);
+}
+#endif
+
 static av_cold int nvenc_load_libraries(AVCodecContext *avctx)
 {
     NvencContext *ctx            = avctx->priv_data;
@@ -303,16 +498,35 @@
 
     av_log(avctx, AV_LOG_VERBOSE, "Loaded Nvenc version %d.%d\n", nvenc_max_ver >> 4, nvenc_max_ver & 0xf);
 
-    if ((NVENCAPI_MAJOR_VERSION << 4 | NVENCAPI_MINOR_VERSION) > nvenc_max_ver) {
+    /* Negotiate: present the newest API version both the driver and this
+     * build support instead of failing on drivers older than the headers. */
+    ctx->api_vers = NULL;
+    for (int i = 0; i < (int)FF_ARRAY_ELEMS(nvenc_api_versions); i++) {
+        if (nvenc_api_versions[i].packed <= nvenc_max_ver) {
+            ctx->api_vers = &nvenc_api_versions[i];
+            break;
+        }
+    }
+
+    if (!ctx->api_vers) {
         av_log(avctx, AV_LOG_ERROR, "Driver does not support the required nvenc API version. "
                "Required: %d.%d Found: %d.%d\n",
-               NVENCAPI_MAJOR_VERSION, NVENCAPI_MINOR_VERSION,
+               11, 1,
                nvenc_max_ver >> 4, nvenc_max_ver & 0xf);
         nvenc_print_driver_requirement(avctx, AV_LOG_ERROR);
         return AVERROR(ENOSYS);
     }
 
-    dl_fn->nvenc_funcs.version = NV_ENCODE_API_FUNCTION_LIST_VER;
+    ctx->api_version = ctx->api_vers->packed;
+
+    if (ctx->api_version < NVENC_API_PACKED(NVENCAPI_MAJOR_VERSION, NVENCAPI_MINOR_VERSION))
+        av_log(avctx, AV_LOG_VERBOSE, "Driver supports nvenc API up to %d.%d (compiled for %d.%d), "
+               "using API %d.%d; features requiring a newer driver are disabled\n",
+               nvenc_max_ver >> 4, nvenc_max_ver & 0xf,
+               NVENCAPI_MAJOR_VERSION, NVENCAPI_MINOR_VERSION,
+               ctx->api_version >> 4, ctx->api_version & 0xf);
+
+    dl_fn->nvenc_funcs.version = ctx->api_vers->function_list;
 
     err = dl_fn->nvenc_dl->NvEncodeAPICreateInstance(&dl_fn->nvenc_funcs);
     if (err != NV_ENC_SUCCESS)
@@ -353,8 +567,8 @@
     NV_ENCODE_API_FUNCTION_LIST *p_nvenc = &ctx->nvenc_dload_funcs.nvenc_funcs;
     NVENCSTATUS ret;
 
-    params.version    = NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER;
-    params.apiVersion = NVENCAPI_VERSION;
+    params.version    = ctx->api_vers->open_session_params;
+    params.apiVersion = ctx->api_vers->api;
     if (ctx->d3d11_device) {
         params.device     = ctx->d3d11_device;
         params.deviceType = NV_ENC_DEVICE_TYPE_DIRECTX;
@@ -415,7 +629,7 @@
     NV_ENC_CAPS_PARAM params        = { 0 };
     int ret, val = 0;
 
-    params.version     = NV_ENC_CAPS_PARAM_VER;
+    params.version     = ctx->api_vers->caps_param;
     params.capsToQuery = cap;
 
     ret = p_nvenc->nvEncGetEncodeCaps(ctx->nvencoder, ctx->init_encode_params.encodeGUID, &params, &val);
@@ -430,6 +644,15 @@
     NvencContext *ctx = avctx->priv_data;
     int tmp, ret;
 
+#if CONFIG_AV1_NVENC_ENCODER
+    if (avctx->codec->id == AV_CODEC_ID_AV1 && !NVENC_API_AT_LEAST(ctx, 12, 0)) {
+        av_log(avctx, AV_LOG_ERROR, "AV1 encoding needs nvenc API 12.0, but the driver "
+               "only supports %d.%d. Driver 520.56.06 or newer is required for AV1.\n",
+               ctx->api_version >> 4, ctx->api_version & 0xf);
+        return AVERROR(ENOSYS);
+    }
+#endif
+
     ret = nvenc_check_codec_support(avctx);
     if (ret < 0) {
         av_log(avctx, AV_LOG_WARNING, "Codec not supported\n");
@@ -1088,7 +1311,10 @@
                     ctx->encode_config.rcParams.lookaheadDepth, ctx->rc_lookahead);
 
 #ifdef NVENC_HAVE_LOOKAHEAD_LEVEL
-            if (ctx->lookahead_level >= 0) {
+            if (ctx->lookahead_level >= 0 && !NVENC_API_AT_LEAST(ctx, 12, 2)) {
+                av_log(avctx, AV_LOG_WARNING, "Lookahead level needs nvenc API 12.2 "
+                       "(driver 550.54.14+), ignoring.\n");
+            } else if (ctx->lookahead_level >= 0) {
                 switch (ctx->lookahead_level) {
                     case NV_ENC_LOOKAHEAD_LEVEL_0:
                     case NV_ENC_LOOKAHEAD_LEVEL_1:
@@ -1274,8 +1500,12 @@
     h264->level = ctx->level;
 
 #ifdef NVENC_HAVE_NEW_BIT_DEPTH_API
-    h264->inputBitDepth = IS_10BIT(ctx->data_pix_fmt) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
-    h264->outputBitDepth = (IS_10BIT(ctx->data_pix_fmt) || ctx->highbitdepth) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
+    /* Pre-12.2 drivers read reserved space at these offsets; H264 on such
+     * drivers is always 8 bit (10 bit was rejected by the caps check). */
+    if (NVENC_API_AT_LEAST(ctx, 12, 2)) {
+        h264->inputBitDepth = IS_10BIT(ctx->data_pix_fmt) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
+        h264->outputBitDepth = (IS_10BIT(ctx->data_pix_fmt) || ctx->highbitdepth) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
+    }
 #endif
 
     if (ctx->coder >= 0)
@@ -1307,6 +1537,13 @@
 #endif
 
 #ifdef NVENC_HAVE_TIME_CODE
+    /* On by default; quietly drop it on old drivers like a pre-12.2 build
+     * (this also disables the per-frame timeCode fill). */
+    if (ctx->s12m_tc && !NVENC_API_AT_LEAST(ctx, 12, 2)) {
+        av_log(avctx, AV_LOG_VERBOSE, "S12M timecodes need nvenc API 12.2, disabling.\n");
+        ctx->s12m_tc = 0;
+    }
+
     if (ctx->s12m_tc)
         h264->enableTimeCode = 1;
 #endif
@@ -1358,18 +1595,21 @@
         hevc->intraRefreshCnt = cc->gopLength - 1;
         cc->gopLength = NVENC_INFINITE_GOPLENGTH;
 #ifdef NVENC_HAVE_HEVC_OUTPUT_RECOVERY_POINT_SEI
-        hevc->outputRecoveryPointSEI = 1;
+        if (NVENC_API_AT_LEAST(ctx, 12, 0))
+            hevc->outputRecoveryPointSEI = 1;
 #endif
         hevc->singleSliceIntraRefresh = ctx->single_slice_intra_refresh;
     }
 
 #ifdef NVENC_HAVE_HEVC_AND_AV1_MASTERING_METADATA
-    ctx->mdm = hevc->outputMasteringDisplay = !!av_frame_side_data_get(avctx->decoded_side_data,
-                                                                       avctx->nb_decoded_side_data,
-                                                                       AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
-    ctx->cll = hevc->outputMaxCll           = !!av_frame_side_data_get(avctx->decoded_side_data,
-                                                                       avctx->nb_decoded_side_data,
-                                                                       AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
+    if (NVENC_API_AT_LEAST(ctx, 13, 0)) {
+        ctx->mdm = hevc->outputMasteringDisplay = !!av_frame_side_data_get(avctx->decoded_side_data,
+                                                                           avctx->nb_decoded_side_data,
+                                                                           AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
+        ctx->cll = hevc->outputMaxCll           = !!av_frame_side_data_get(avctx->decoded_side_data,
+                                                                           avctx->nb_decoded_side_data,
+                                                                           AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
+    }
 #endif
 
     if (ctx->constrained_encoding)
@@ -1464,8 +1704,18 @@
     hevc->chromaFormatIDC = IS_YUV444(ctx->data_pix_fmt) ? 3 : IS_YUV422(ctx->data_pix_fmt) ? 2 : 1;
 
 #ifdef NVENC_HAVE_NEW_BIT_DEPTH_API
-    hevc->inputBitDepth = IS_10BIT(ctx->data_pix_fmt) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
-    hevc->outputBitDepth = (IS_10BIT(ctx->data_pix_fmt) || ctx->highbitdepth) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
+    if (NVENC_API_AT_LEAST(ctx, 12, 2)) {
+        hevc->inputBitDepth = IS_10BIT(ctx->data_pix_fmt) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
+        hevc->outputBitDepth = (IS_10BIT(ctx->data_pix_fmt) || ctx->highbitdepth) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
+    } else {
+        if (ctx->highbitdepth && !IS_10BIT(ctx->data_pix_fmt)) {
+            av_log(avctx, AV_LOG_ERROR, "8 -> 10 bit HEVC encoding needs nvenc API 12.2 "
+                   "(driver 550.54.14+).\n");
+            return AVERROR(ENOSYS);
+        }
+        // The old pixelBitDepthMinus8 bitfield read by pre-12.2 drivers
+        nvenc_set_legacy_hevc_bit_depth(hevc, IS_10BIT(ctx->data_pix_fmt) ? 2 : 0);
+    }
 #else
     hevc->pixelBitDepthMinus8 = IS_10BIT(ctx->data_pix_fmt) ? 2 : 0;
 #endif
@@ -1569,20 +1819,28 @@
     av1->chromaFormatIDC = IS_YUV444(ctx->data_pix_fmt) ? 3 : 1;
 
 #ifdef NVENC_HAVE_NEW_BIT_DEPTH_API
-    av1->inputBitDepth = IS_10BIT(ctx->data_pix_fmt) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
-    av1->outputBitDepth = (IS_10BIT(ctx->data_pix_fmt) || ctx->highbitdepth) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
+    if (NVENC_API_AT_LEAST(ctx, 12, 2)) {
+        av1->inputBitDepth = IS_10BIT(ctx->data_pix_fmt) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
+        av1->outputBitDepth = (IS_10BIT(ctx->data_pix_fmt) || ctx->highbitdepth) ? NV_ENC_BIT_DEPTH_10 : NV_ENC_BIT_DEPTH_8;
+    } else {
+        // The old bitfields read by 12.0/12.1 drivers
+        nvenc_set_legacy_av1_bit_depth(av1, IS_10BIT(ctx->data_pix_fmt) ? 2 : 0,
+                                       (IS_10BIT(ctx->data_pix_fmt) || ctx->highbitdepth) ? 2 : 0);
+    }
 #else
     av1->inputPixelBitDepthMinus8 = IS_10BIT(ctx->data_pix_fmt) ? 2 : 0;
     av1->pixelBitDepthMinus8 = (IS_10BIT(ctx->data_pix_fmt) || ctx->highbitdepth) ? 2 : 0;
 #endif
 
 #ifdef NVENC_HAVE_HEVC_AND_AV1_MASTERING_METADATA
-    ctx->mdm = av1->outputMasteringDisplay = !!av_frame_side_data_get(avctx->decoded_side_data,
-                                                                      avctx->nb_decoded_side_data,
-                                                                      AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
-    ctx->cll = av1->outputMaxCll           = !!av_frame_side_data_get(avctx->decoded_side_data,
-                                                                      avctx->nb_decoded_side_data,
-                                                                      AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
+    if (NVENC_API_AT_LEAST(ctx, 13, 0)) {
+        ctx->mdm = av1->outputMasteringDisplay = !!av_frame_side_data_get(avctx->decoded_side_data,
+                                                                          avctx->nb_decoded_side_data,
+                                                                          AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
+        ctx->cll = av1->outputMaxCll           = !!av_frame_side_data_get(avctx->decoded_side_data,
+                                                                          avctx->nb_decoded_side_data,
+                                                                          AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
+    }
 #endif
 
     if (ctx->b_ref_mode >= 0)
@@ -1676,16 +1934,16 @@
     int res = 0;
     int dw, dh;
 
-    ctx->encode_config.version = NV_ENC_CONFIG_VER;
-    ctx->init_encode_params.version = NV_ENC_INITIALIZE_PARAMS_VER;
+    ctx->encode_config.version = ctx->api_vers->config;
+    ctx->init_encode_params.version = ctx->api_vers->initialize_params;
 
     ctx->init_encode_params.encodeHeight = avctx->height;
     ctx->init_encode_params.encodeWidth = avctx->width;
 
     ctx->init_encode_params.encodeConfig = &ctx->encode_config;
 
-    preset_config.version = NV_ENC_PRESET_CONFIG_VER;
-    preset_config.presetCfg.version = NV_ENC_CONFIG_VER;
+    preset_config.version = ctx->api_vers->preset_config;
+    preset_config.presetCfg.version = ctx->api_vers->config;
 
     ctx->init_encode_params.tuningInfo = ctx->tuning_info;
 
@@ -1694,6 +1952,16 @@
     else if (ctx->flags & NVENC_LOWLATENCY)
         ctx->init_encode_params.tuningInfo = NV_ENC_TUNING_INFO_LOW_LATENCY;
 
+#ifdef NVENC_HAVE_UHQ_TUNING
+    if (ctx->init_encode_params.tuningInfo == NV_ENC_TUNING_INFO_ULTRA_HIGH_QUALITY &&
+        (!NVENC_API_AT_LEAST(ctx, 12, 2) ||
+         (avctx->codec->id == AV_CODEC_ID_AV1 && !NVENC_API_AT_LEAST(ctx, 13, 0)))) {
+        av_log(avctx, AV_LOG_ERROR, "UHQ tuning needs nvenc API 12.2 (driver 550.54.14+), "
+               "13.0 (driver 570+) for AV1.\n");
+        return AVERROR(ENOSYS);
+    }
+#endif
+
     nv_status = p_nvenc->nvEncGetEncodePresetConfigEx(ctx->nvencoder,
         ctx->init_encode_params.encodeGUID,
         ctx->init_encode_params.presetGUID,
@@ -1704,7 +1972,7 @@
 
     memcpy(&ctx->encode_config, &preset_config.presetCfg, sizeof(ctx->encode_config));
 
-    ctx->encode_config.version = NV_ENC_CONFIG_VER;
+    ctx->encode_config.version = ctx->api_vers->config;
 
     compute_dar(avctx, &dw, &dh);
     ctx->init_encode_params.darHeight = dh;
@@ -1735,11 +2003,16 @@
         ctx->init_encode_params.enableWeightedPrediction = 1;
 
 #ifdef NVENC_HAVE_SPLIT_FRAME_ENCODING
-    ctx->init_encode_params.splitEncodeMode = ctx->split_encode_mode;
+    if (NVENC_API_AT_LEAST(ctx, 12, 1)) {
+        ctx->init_encode_params.splitEncodeMode = ctx->split_encode_mode;
 
-    if (ctx->split_encode_mode != NV_ENC_SPLIT_DISABLE_MODE) {
-        if (avctx->codec->id == AV_CODEC_ID_HEVC && ctx->weighted_pred == 1)
-            av_log(avctx, AV_LOG_WARNING, "Split encoding not supported with weighted prediction enabled.\n");
+        if (ctx->split_encode_mode != NV_ENC_SPLIT_DISABLE_MODE) {
+            if (avctx->codec->id == AV_CODEC_ID_HEVC && ctx->weighted_pred == 1)
+                av_log(avctx, AV_LOG_WARNING, "Split encoding not supported with weighted prediction enabled.\n");
+        }
+    } else if (ctx->split_encode_mode != NV_ENC_SPLIT_AUTO_MODE) {
+        av_log(avctx, AV_LOG_WARNING, "Split frame encoding needs nvenc API 12.1 "
+               "(driver 530.41.03+), ignoring.\n");
     }
 #endif
 
@@ -1881,7 +2154,7 @@
 
     NVENCSTATUS nv_status;
     NV_ENC_CREATE_BITSTREAM_BUFFER allocOut = { 0 };
-    allocOut.version = NV_ENC_CREATE_BITSTREAM_BUFFER_VER;
+    allocOut.version = ctx->api_vers->create_bitstream_buffer;
 
     if (avctx->pix_fmt == AV_PIX_FMT_CUDA || avctx->pix_fmt == AV_PIX_FMT_D3D11) {
         ctx->surfaces[idx].in_ref = av_frame_alloc();
@@ -1897,7 +2170,7 @@
             return AVERROR(EINVAL);
         }
 
-        allocSurf.version = NV_ENC_CREATE_INPUT_BUFFER_VER;
+        allocSurf.version = ctx->api_vers->create_input_buffer;
         allocSurf.width = avctx->width;
         allocSurf.height = avctx->height;
         allocSurf.bufferFmt = ctx->surfaces[idx].format;
@@ -1985,7 +2258,7 @@
     char tmpHeader[NV_MAX_SEQ_HDR_LEN];
 
     NV_ENC_SEQUENCE_PARAM_PAYLOAD payload = { 0 };
-    payload.version = NV_ENC_SEQUENCE_PARAM_PAYLOAD_VER;
+    payload.version = ctx->api_vers->sequence_param_payload;
 
     payload.spsppsBuffer = tmpHeader;
     payload.inBufferSize = sizeof(tmpHeader);
@@ -2017,7 +2290,7 @@
 
     /* the encoder has to be flushed before it can be closed */
     if (ctx->nvencoder) {
-        NV_ENC_PIC_PARAMS params        = { .version        = NV_ENC_PIC_PARAMS_VER,
+        NV_ENC_PIC_PARAMS params        = { .version        = ctx->api_vers->pic_params,
                                             .encodePicFlags = NV_ENC_PIC_FLAG_EOS };
 
         res = nvenc_push_context(avctx);
@@ -2241,7 +2514,7 @@
     if (idx < 0)
         return idx;
 
-    reg.version            = NV_ENC_REGISTER_RESOURCE_VER;
+    reg.version            = ctx->api_vers->register_resource;
     reg.width              = frames_ctx->width;
     reg.height             = frames_ctx->height;
     reg.pitch              = frame->linesize[0];
@@ -2296,7 +2569,7 @@
             return res;
 
         if (!ctx->registered_frames[reg_idx].mapped) {
-            ctx->registered_frames[reg_idx].in_map.version = NV_ENC_MAP_INPUT_RESOURCE_VER;
+            ctx->registered_frames[reg_idx].in_map.version = ctx->api_vers->map_input_resource;
             ctx->registered_frames[reg_idx].in_map.registeredResource = ctx->registered_frames[reg_idx].regptr;
             nv_status = p_nvenc->nvEncMapInputResource(ctx->nvencoder, &ctx->registered_frames[reg_idx].in_map);
             if (nv_status != NV_ENC_SUCCESS) {
@@ -2316,7 +2589,7 @@
     } else {
         NV_ENC_LOCK_INPUT_BUFFER lockBufferParams = { 0 };
 
-        lockBufferParams.version = NV_ENC_LOCK_INPUT_BUFFER_VER;
+        lockBufferParams.version = ctx->api_vers->lock_input_buffer;
         lockBufferParams.inputBuffer = nvenc_frame->input_surface;
 
         nv_status = p_nvenc->nvEncLockInputBuffer(ctx->nvencoder, &lockBufferParams);
@@ -2569,31 +2842,38 @@
     NvencDynLoadFunctions *dl_fn = &ctx->nvenc_dload_funcs;
     NV_ENCODE_API_FUNCTION_LIST *p_nvenc = &dl_fn->nvenc_funcs;
 
-    NV_ENC_LOCK_BITSTREAM lock_params = { 0 };
+    /* The SDK 12.1 NV_ENC_LOCK_BITSTREAM layout is 8 bytes larger than the
+     * 12.2+ layout this is compiled against; keep zeroed spare space after it
+     * for drivers negotiated to API <= 12.1. */
+    struct {
+        NV_ENC_LOCK_BITSTREAM params;
+        uint32_t compat_pad[2];
+    } lock_params_padded = { 0 };
+    NV_ENC_LOCK_BITSTREAM *lock_params = &lock_params_padded.params;
     NVENCSTATUS nv_status;
     int res = 0;
 
     enum AVPictureType pict_type;
 
-    lock_params.version = NV_ENC_LOCK_BITSTREAM_VER;
+    lock_params->version = ctx->api_vers->lock_bitstream;
 
-    lock_params.doNotWait = 0;
-    lock_params.outputBitstream = tmpoutsurf->output_surface;
+    lock_params->doNotWait = 0;
+    lock_params->outputBitstream = tmpoutsurf->output_surface;
 
-    nv_status = p_nvenc->nvEncLockBitstream(ctx->nvencoder, &lock_params);
+    nv_status = p_nvenc->nvEncLockBitstream(ctx->nvencoder, lock_params);
     if (nv_status != NV_ENC_SUCCESS) {
         res = nvenc_print_error(avctx, nv_status, "Failed locking bitstream buffer");
         goto error;
     }
 
-    res = ff_get_encode_buffer(avctx, pkt, lock_params.bitstreamSizeInBytes, 0);
+    res = ff_get_encode_buffer(avctx, pkt, lock_params->bitstreamSizeInBytes, 0);
 
     if (res < 0) {
         p_nvenc->nvEncUnlockBitstream(ctx->nvencoder, tmpoutsurf->output_surface);
         goto error;
     }
 
-    memcpy(pkt->data, lock_params.bitstreamBufferPtr, lock_params.bitstreamSizeInBytes);
+    memcpy(pkt->data, lock_params->bitstreamBufferPtr, lock_params->bitstreamSizeInBytes);
 
     nv_status = p_nvenc->nvEncUnlockBitstream(ctx->nvencoder, tmpoutsurf->output_surface);
     if (nv_status != NV_ENC_SUCCESS) {
@@ -2620,7 +2900,7 @@
         tmpoutsurf->input_surface = NULL;
     }
 
-    switch (lock_params.pictureType) {
+    switch (lock_params->pictureType) {
     case NV_ENC_PIC_TYPE_IDR:
         pkt->flags |= AV_PKT_FLAG_KEY;
         av_fallthrough;
@@ -2644,13 +2924,13 @@
     }
 
     ff_encode_add_stats_side_data(pkt,
-        (lock_params.frameAvgQP - 1) * FF_QP2LAMBDA, NULL, 0, pict_type);
+        (lock_params->frameAvgQP - 1) * FF_QP2LAMBDA, NULL, 0, pict_type);
 
-    res = nvenc_set_timestamp(avctx, &lock_params, pkt);
+    res = nvenc_set_timestamp(avctx, lock_params, pkt);
     if (res < 0)
         goto error2;
 
-    res = nvenc_retrieve_frame_data(avctx, &lock_params, pkt);
+    res = nvenc_retrieve_frame_data(avctx, lock_params, pkt);
     if (res < 0)
         goto error2;
 
@@ -2793,14 +3073,21 @@
     NV_ENCODE_API_FUNCTION_LIST *p_nvenc = &ctx->nvenc_dload_funcs.nvenc_funcs;
     NVENCSTATUS ret;
 
-    NV_ENC_RECONFIGURE_PARAMS params = { 0 };
+    /* The pre-12.2 NV_ENC_RECONFIGURE_PARAMS layout is 8 bytes larger than
+     * the 12.2+ layout this is compiled against; keep zeroed spare space
+     * after it for drivers negotiated to API < 12.2. */
+    struct {
+        NV_ENC_RECONFIGURE_PARAMS params;
+        uint32_t compat_pad[2];
+    } params_padded = { 0 };
+    NV_ENC_RECONFIGURE_PARAMS *params = &params_padded.params;
     int needs_reconfig = 0;
     int needs_encode_config = 0;
     int reconfig_bitrate = 0, reconfig_dar = 0;
     int dw, dh;
 
-    params.version = NV_ENC_RECONFIGURE_PARAMS_VER;
-    params.reInitEncodeParams = ctx->init_encode_params;
+    params->version = ctx->api_vers->reconfigure_params;
+    params->reInitEncodeParams = ctx->init_encode_params;
 
     compute_dar(avctx, &dw, &dh);
     if (dw != ctx->init_encode_params.darWidth || dh != ctx->init_encode_params.darHeight) {
@@ -2809,47 +3096,59 @@
                ctx->init_encode_params.darWidth,
                ctx->init_encode_params.darHeight, dw, dh);
 
-        params.reInitEncodeParams.darHeight = dh;
-        params.reInitEncodeParams.darWidth = dw;
+        params->reInitEncodeParams.darHeight = dh;
+        params->reInitEncodeParams.darWidth = dw;
 
         needs_reconfig = 1;
         reconfig_dar = 1;
     }
 
     if (ctx->rc != NV_ENC_PARAMS_RC_CONSTQP && ctx->support_dyn_bitrate) {
-        if (avctx->bit_rate > 0 && params.reInitEncodeParams.encodeConfig->rcParams.averageBitRate != avctx->bit_rate) {
+        if (avctx->bit_rate > 0 && params->reInitEncodeParams.encodeConfig->rcParams.averageBitRate != avctx->bit_rate) {
             av_log(avctx, AV_LOG_VERBOSE,
                    "avg bitrate change: %d -> %d\n",
-                   params.reInitEncodeParams.encodeConfig->rcParams.averageBitRate,
+                   params->reInitEncodeParams.encodeConfig->rcParams.averageBitRate,
                    (uint32_t)avctx->bit_rate);
 
-            params.reInitEncodeParams.encodeConfig->rcParams.averageBitRate = avctx->bit_rate;
+            params->reInitEncodeParams.encodeConfig->rcParams.averageBitRate = avctx->bit_rate;
             reconfig_bitrate = 1;
         }
 
         if (avctx->rc_max_rate > 0 && ctx->encode_config.rcParams.maxBitRate != avctx->rc_max_rate) {
             av_log(avctx, AV_LOG_VERBOSE,
                    "max bitrate change: %d -> %d\n",
-                   params.reInitEncodeParams.encodeConfig->rcParams.maxBitRate,
+                   params->reInitEncodeParams.encodeConfig->rcParams.maxBitRate,
                    (uint32_t)avctx->rc_max_rate);
 
-            params.reInitEncodeParams.encodeConfig->rcParams.maxBitRate = avctx->rc_max_rate;
+            params->reInitEncodeParams.encodeConfig->rcParams.maxBitRate = avctx->rc_max_rate;
             reconfig_bitrate = 1;
         }
 
         if (avctx->rc_buffer_size > 0 && ctx->encode_config.rcParams.vbvBufferSize != avctx->rc_buffer_size) {
             av_log(avctx, AV_LOG_VERBOSE,
                    "vbv buffer size change: %d -> %d\n",
-                   params.reInitEncodeParams.encodeConfig->rcParams.vbvBufferSize,
+                   params->reInitEncodeParams.encodeConfig->rcParams.vbvBufferSize,
                    avctx->rc_buffer_size);
 
-            params.reInitEncodeParams.encodeConfig->rcParams.vbvBufferSize = avctx->rc_buffer_size;
+            params->reInitEncodeParams.encodeConfig->rcParams.vbvBufferSize = avctx->rc_buffer_size;
             reconfig_bitrate = 1;
         }
 
         if (reconfig_bitrate) {
-            params.resetEncoder = 1;
-            params.forceIDR = 1;
+            if (NVENC_API_AT_LEAST(ctx, 12, 2)) {
+                params->resetEncoder = 1;
+                params->forceIDR = 1;
+            } else {
+                /* SDK 12.2 inserted a reserved word at offset 4 of
+                 * NV_ENC_RECONFIGURE_PARAMS, shifting the trailing
+                 * resetEncoder/forceIDR bits from byte 1816 to 1808. Pre-12.2
+                 * drivers read them at 1816, which is compat_pad[0] of the
+                 * padded wrapper (verified against SDK 11.1-12.1 headers:
+                 * resetEncoder = bit 0, forceIDR = bit 1). The compiled
+                 * bitfields must stay 0: byte 1808 lies in reserved space of
+                 * the embedded pre-12.2 reInitEncodeParams. */
+                params_padded.compat_pad[0] = 0x1 | 0x2;
+            }
 
             needs_encode_config = 1;
             needs_reconfig = 1;
@@ -2857,10 +3156,10 @@
     }
 
     if (!needs_encode_config)
-        params.reInitEncodeParams.encodeConfig = NULL;
+        params->reInitEncodeParams.encodeConfig = NULL;
 
     if (needs_reconfig) {
-        ret = p_nvenc->nvEncReconfigureEncoder(ctx->nvencoder, &params);
+        ret = p_nvenc->nvEncReconfigureEncoder(ctx->nvencoder, params);
         if (ret != NV_ENC_SUCCESS) {
             nvenc_print_error(avctx, ret, "failed to reconfigure nvenc");
         } else {
@@ -2870,9 +3169,9 @@
             }
 
             if (reconfig_bitrate) {
-                ctx->encode_config.rcParams.averageBitRate = params.reInitEncodeParams.encodeConfig->rcParams.averageBitRate;
-                ctx->encode_config.rcParams.maxBitRate = params.reInitEncodeParams.encodeConfig->rcParams.maxBitRate;
-                ctx->encode_config.rcParams.vbvBufferSize = params.reInitEncodeParams.encodeConfig->rcParams.vbvBufferSize;
+                ctx->encode_config.rcParams.averageBitRate = params->reInitEncodeParams.encodeConfig->rcParams.averageBitRate;
+                ctx->encode_config.rcParams.maxBitRate = params->reInitEncodeParams.encodeConfig->rcParams.maxBitRate;
+                ctx->encode_config.rcParams.vbvBufferSize = params->reInitEncodeParams.encodeConfig->rcParams.vbvBufferSize;
             }
 
         }
@@ -2971,7 +3270,7 @@
     NV_ENCODE_API_FUNCTION_LIST *p_nvenc = &dl_fn->nvenc_funcs;
 
     NV_ENC_PIC_PARAMS pic_params = { 0 };
-    pic_params.version = NV_ENC_PIC_PARAMS_VER;
+    pic_params.version = ctx->api_vers->pic_params;
 
     if ((!ctx->cu_context && !ctx->d3d11_device) || !ctx->nvencoder)
         return AVERROR(EINVAL);
--- a/libavcodec/nvenc.h
+++ b/libavcodec/nvenc.h
@@ -80,6 +80,54 @@
 #define NVENC_HAVE_AV1_HGOP_SUPPORT
 #endif
 
+/*
+ * Runtime NVENC API version negotiation.
+ *
+ * The NVENC driver rejects clients that present a newer API version than it
+ * supports, so an FFmpeg built against new SDK headers normally refuses to
+ * run on older drivers. Instead of failing, we query the driver's maximum
+ * supported API version at runtime and present struct version words (and the
+ * session apiVersion) matching what that driver expects, while disabling
+ * features that need a newer API. This keeps one binary working from driver
+ * 470.57.02 (API 11.1, the last driver branch for Kepler / first-generation
+ * NVENC GPUs) up to current drivers, including AV1 support on drivers that
+ * have it.
+ *
+ * The struct version table and the legacy bit-depth field positions below
+ * were derived from and verified against the exact layouts of nv-codec-headers
+ * n11.1.5.3, n12.0.16.1, n12.1.14.0, n12.2.72.0 and n13.0.19.0. All struct
+ * fields FFmpeg touches sit at identical offsets in all of those versions
+ * (new fields are only ever carved out of reserved space). Building against
+ * any other headers invalidates those assumptions, so refuse it.
+ */
+#if !NVENCAPI_CHECK_VERSION(13, 0) || NVENCAPI_CHECK_VERSION(13, 1)
+#error "The nvenc runtime API negotiation patch requires nv-codec-headers 13.0.x. Re-verify the struct version table and legacy bit-depth field layout before moving to other headers."
+#endif
+
+typedef struct NvencApiVersion {
+    uint32_t packed;                   // (major << 4) | minor, the NvEncodeAPIGetMaxSupportedVersion encoding
+    uint32_t api;                      // NVENCAPI_VERSION of that SDK: major | (minor << 24)
+    uint32_t function_list;            // NV_ENCODE_API_FUNCTION_LIST_VER
+    uint32_t open_session_params;      // NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER
+    uint32_t caps_param;               // NV_ENC_CAPS_PARAM_VER
+    uint32_t initialize_params;        // NV_ENC_INITIALIZE_PARAMS_VER
+    uint32_t config;                   // NV_ENC_CONFIG_VER
+    uint32_t preset_config;            // NV_ENC_PRESET_CONFIG_VER
+    uint32_t pic_params;               // NV_ENC_PIC_PARAMS_VER
+    uint32_t lock_bitstream;           // NV_ENC_LOCK_BITSTREAM_VER
+    uint32_t lock_input_buffer;        // NV_ENC_LOCK_INPUT_BUFFER_VER
+    uint32_t map_input_resource;       // NV_ENC_MAP_INPUT_RESOURCE_VER
+    uint32_t register_resource;        // NV_ENC_REGISTER_RESOURCE_VER
+    uint32_t create_input_buffer;      // NV_ENC_CREATE_INPUT_BUFFER_VER
+    uint32_t create_bitstream_buffer;  // NV_ENC_CREATE_BITSTREAM_BUFFER_VER
+    uint32_t reconfigure_params;       // NV_ENC_RECONFIGURE_PARAMS_VER
+    uint32_t sequence_param_payload;   // NV_ENC_SEQUENCE_PARAM_PAYLOAD_VER
+} NvencApiVersion;
+
+#define NVENC_API_PACKED(major, minor) (((uint32_t)(major) << 4) | (minor))
+#define NVENC_API_AT_LEAST(ctx, major, minor) \
+    ((ctx)->api_version >= NVENC_API_PACKED(major, minor))
+
 typedef struct NvencSurface
 {
     NV_ENC_INPUT_PTR input_surface;
@@ -172,6 +220,11 @@
     NvencDynLoadFunctions nvenc_dload_funcs;
 
     NV_ENC_INITIALIZE_PARAMS init_encode_params;
+    /* The pre-12.2 NV_ENC_INITIALIZE_PARAMS layout is 8 bytes larger than the
+     * 12.2+ layout this is compiled against. Drivers negotiated to API < 12.2
+     * read that larger footprint, so keep zeroed spare space directly after
+     * the struct. Must stay right after init_encode_params and stay zero. */
+    uint32_t init_encode_params_compat_pad[2];
     NV_ENC_CONFIG encode_config;
     CUcontext cu_context;
     CUcontext cu_context_internal;
@@ -275,6 +328,11 @@
     int cbr_padding;
     int multiview, multiview_supported;
     int display_sei_sent;
+
+    /* Runtime-negotiated NVENC API version: the newest version supported by
+     * both the driver and this build. Set in nvenc_load_libraries. */
+    const NvencApiVersion *api_vers;
+    uint32_t api_version;              // negotiated, (major << 4) | minor
 } NvencContext;
 
 int ff_nvenc_encode_init(AVCodecContext *avctx);