134134 TESTSRC2_AV1_10BIT ,
135135 TESTSRC2_ODD_HEIGHT_444 ,
136136 TESTSRC2_ODD_HEIGHT_AND_WIDTH_444 ,
137+ TESTSRC2_ODD_HEIGHT_AND_WIDTH_444_10BIT ,
137138 TESTSRC2_ODD_HEIGHT_AND_WIDTH_VP9 ,
138139 TESTSRC2_ODD_HEIGHT_AND_WIDTH_VP9_10BIT ,
139140 TESTSRC2_ODD_HEIGHT_VP9 ,
@@ -2300,7 +2301,9 @@ def test_nvdec_cpu_fallback_yuv444(self, tmp_path):
23002301 cpu_frames = cpu_decoder .get_frames_in_range (start = 0 , stop = num_frames ).data
23012302 cuda_frames = cuda_decoder .get_frames_in_range (start = 0 , stop = num_frames ).data
23022303
2303- torch .testing .assert_close (cpu_frames , cuda_frames .cpu (), rtol = 0 , atol = 0 )
2304+ # The CUDA path uploads these as yuv444p and color-converts them with
2305+ # our kernel, which truncates where swscale rounds.
2306+ torch .testing .assert_close (cpu_frames , cuda_frames .cpu (), rtol = 0 , atol = 1 )
23042307
23052308 @needs_cuda
23062309 def test_nvdec_cuda_interface_error (self ):
@@ -3500,6 +3503,7 @@ def pix_fmt(self, device):
35003503 TESTSRC2_ODD_WIDTH_VP9_10BIT ,
35013504 TESTSRC2_ODD_HEIGHT_VP9_10BIT ,
35023505 TESTSRC2_ODD_HEIGHT_AND_WIDTH_VP9_10BIT ,
3506+ TESTSRC2_ODD_HEIGHT_AND_WIDTH_444_10BIT ,
35033507)
35043508
35053509
@@ -3509,9 +3513,12 @@ def pix_fmt(self, device):
35093513_MATERIALIZE_VIDEOS = (
35103514 _MaterializeCase (NASA_VIDEO , 8 , "yuv420p" , "nv12" ), # even dims
35113515 _MaterializeCase (TESTSRC2_ODD_HEIGHT_AND_WIDTH_VP9 , 8 , "yuv420p" , "nv12" ), # odd
3512- # 4:4:4 (full-res chroma). NVDEC can't decode H264 4:4:4, so this one falls
3513- # back to the CPU and keeps its native format .
3516+ # 4:4:4 (full-res chroma). NVDEC can't decode H264 4:4:4, so these fall back
3517+ # to the CPU and are uploaded as 4:4:4 rather than have their chroma halved .
35143518 _MaterializeCase (TESTSRC2_ODD_HEIGHT_AND_WIDTH_444 , 8 , "yuv444p" , "yuv444p" ),
3519+ _MaterializeCase (
3520+ TESTSRC2_ODD_HEIGHT_AND_WIDTH_444_10BIT , 10 , "yuv444p10le" , "yuv444p16le"
3521+ ),
35153522 # HEVC 4:4:4, which NVDEC decodes natively into its YUV444 surfaces. The
35163523 # 16-bit one is the only 4:4:4 surface above 8 bits, so 10- and 12-bit
35173524 # sources both land in yuv444p16le.
@@ -3696,6 +3703,7 @@ def _assert_matches_video_decoder(got, ref, video):
36963703 TESTSRC2_ODD_WIDTH_444 ,
36973704 TESTSRC2_ODD_HEIGHT_444 ,
36983705 TESTSRC2_ODD_HEIGHT_AND_WIDTH_444 ,
3706+ TESTSRC2_ODD_HEIGHT_AND_WIDTH_444_10BIT ,
36993707 # HEVC 4:4:4: NVDEC decodes these natively instead.
37003708 TESTSRC2_444_8BIT_HEVC ,
37013709 TESTSRC2_444_10BIT_HEVC ,
@@ -3867,6 +3875,15 @@ def test_materialize_structure(self, case, device):
38673875 expected_dtype = torch .uint16 if raw .bit_depth > 8 else torch .uint8
38683876 assert all (plane .dtype == expected_dtype for plane in planes )
38693877
3878+ if device == "cuda" and expected_dtype == torch .uint16 :
3879+ # Whatever depth the format claims, a 16-bit CUDA surface holds the
3880+ # source's samples msb-aligned, with the unused low bits zeroed.
3881+ # That's what lets test_materialize_cuda_planes_match_cpu shift them
3882+ # back down by 16 - bit_depth and compare against the CPU planes.
3883+ unused_low_bits = (1 << (16 - case .bit_depth )) - 1
3884+ for plane in planes :
3885+ assert (plane .to (torch .int32 ) & unused_low_bits ).count_nonzero () == 0
3886+
38703887 Y , U , V = planes
38713888 height , width = converter .convert (frame ).data .shape [1 :]
38723889 assert Y .shape == (height , width )
@@ -4005,23 +4022,29 @@ def test_materialize_neutral_chroma_is_grayscale(self, video, device):
40054022 torch .testing .assert_close (g , b , atol = 1 , rtol = 0 )
40064023
40074024 @pytest .mark .needs_cuda
4008- @pytest .mark .parametrize ("video" , (H265_VIDEO , TESTSRC2_ODD_HEIGHT_AND_WIDTH_444 ))
4009- def test_materialize_cpu_fallback_stays_on_cpu (self , video ):
4010- # TODO_NOW: This may not be what we want. We probalby want
4011- # to output CUDA data. But how? Do we put the YUV420 on CUDA? Then we
4012- # need a specialized kernel to color-convert? Or we put those frames we
4013- # can have on NV12 - but for 444 it's a problem becaus ewe can't convert
4014- # them to NV12, so we'd need a 444 color-conversion kernel anyway. So
4015- # all frames would be NV12 except *some* (the 444 ones)?
4016- # Unclear what to do here honestly. Maybe outputting CPU frames is
4017- # actually justifiable?
4025+ @pytest .mark .parametrize (
4026+ "video, expected_pix_fmt" ,
4027+ (
4028+ # Too small for NVDEC.
4029+ (H265_VIDEO , "nv12" ),
4030+ # H264 4:4:4, which NVDEC can't decode. Uploading it as NV12 would
4031+ # halve its chroma resolution, so it stays 4:4:4.
4032+ (TESTSRC2_ODD_HEIGHT_AND_WIDTH_444 , "yuv444p" ),
4033+ (TESTSRC2_ODD_HEIGHT_AND_WIDTH_444_10BIT , "yuv444p16le" ),
4034+ ),
4035+ )
4036+ def test_materialize_cpu_fallback_is_on_cuda (self , video , expected_pix_fmt ):
4037+ # A CUDA PacketDecoder hands out CUDA frames even for the streams it has
4038+ # to decode on the CPU, and they're in an NVDEC surface format like any
4039+ # other CUDA frame.
4040+ assert VideoDecoder (video .path , device = "cuda" ).cpu_fallback
4041+
40184042 frame , _ = self ._first_frame (video .path , "cuda" )
4019- assert frame .device == "cpu "
4043+ assert frame .device == "cuda "
40204044
40214045 raw = frame .materialize ()
4022- planes , pix_fmt = raw .planes , raw .pix_fmt
4023- assert pix_fmt != "nv12"
4024- assert all (plane .device .type == "cpu" for plane in planes )
4046+ assert raw .pix_fmt == expected_pix_fmt
4047+ assert all (plane .device .type == "cuda" for plane in raw .planes )
40254048
40264049
40274050# Small helpers to avoid having to always specify the same skip marks and decode_fn
0 commit comments