1 /*
2 * Copyright © Microsoft Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24 #include "d3d12_video_dec_references_mgr.h"
25 #include "d3d12_video_dec_h264.h"
26 #include "d3d12_video_texture_array_dpb_manager.h"
27 #include "d3d12_video_array_of_textures_dpb_manager.h"
28 #include "d3d12_screen.h"
29 #include "d3d12_resource.h"
30 #include "d3d12_video_buffer.h"
31 #include <algorithm>
32 #include <string>
33
34 //----------------------------------------------------------------------------------------------------------------------------------
35 static uint16_t
GetInvalidReferenceIndex(d3d12_video_decode_profile_type DecodeProfileType)36 GetInvalidReferenceIndex(d3d12_video_decode_profile_type DecodeProfileType)
37 {
38 assert(DecodeProfileType <= d3d12_video_decode_profile_type_max_valid);
39
40 switch (DecodeProfileType) {
41 case d3d12_video_decode_profile_type_h264:
42 return DXVA_H264_INVALID_PICTURE_INDEX;
43 default:
44 return 0;
45 };
46 }
47
48 //----------------------------------------------------------------------------------------------------------------------------------
49 ///
50 /// This should always be a clear (non ref only) texture, to be presented downstream as the decoded texture
51 /// Please see get_reference_only_output for the current frame recon pic ref only allocation
52 ///
53 void
get_current_frame_decode_output_texture(struct pipe_video_buffer * pCurrentDecodeTarget,ID3D12Resource ** ppOutTexture2D,uint32_t * pOutSubresourceIndex)54 d3d12_video_decoder_references_manager::get_current_frame_decode_output_texture(struct pipe_video_buffer * pCurrentDecodeTarget,
55 ID3D12Resource **ppOutTexture2D,
56 uint32_t * pOutSubresourceIndex)
57 {
58 // First try to find if there's an existing entry for this pCurrentDecodeTarget already in the DPB
59 // For interlaced scenarios, multiple end_frame calls will need to reference the same texture for top/bottom
60 assert(m_DecodeTargetToOriginalIndex7Bits.count(pCurrentDecodeTarget) > 0); // Needs to already have a Index7Bits assigned for current pic params
61 uint16_t remappedIdx = find_remapped_index(m_DecodeTargetToOriginalIndex7Bits[pCurrentDecodeTarget]);
62
63 if(remappedIdx != m_invalidIndex) { // If it already has a remapped index in use, reuse that allocation
64 // return the existing allocation for this decode target
65 d3d12_video_reconstructed_picture reconPicture = m_upD3D12TexturesStorageManager->get_reference_frame(remappedIdx);
66 *ppOutTexture2D = reconPicture.pReconstructedPicture;
67 *pOutSubresourceIndex = reconPicture.ReconstructedPictureSubresource;
68 } else {
69 if (is_reference_only()) {
70 // When using clear DPB references (not ReferenceOnly) the decode output allocations come from
71 // m_upD3D12TexturesStorageManager as decode output == reconpic decode output Otherwise, when ReferenceOnly is
72 // true, both the reference frames in the DPB and the current frame reconpic output must be REFERENCE_ONLY, all
73 // the allocations are stored in m_upD3D12TexturesStorageManager but we need a +1 allocation without the
74 // REFERENCE_FRAME to use as clear decoded output. In this case d3d12_video_decoder_references_manager allocates
75 // and provides m_pClearDecodedOutputTexture Please note that m_pClearDecodedOutputTexture needs to be copied/read
76 // by the client before calling end_frame again, as the allocation will be reused for the next frame.
77
78 if (m_pClearDecodedOutputTexture == nullptr) {
79 D3D12_HEAP_PROPERTIES Properties =
80 CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT, m_dpbDescriptor.m_NodeMask, m_dpbDescriptor.m_NodeMask);
81 CD3DX12_RESOURCE_DESC resDesc = CD3DX12_RESOURCE_DESC::Tex2D(m_dpbDescriptor.Format,
82 m_dpbDescriptor.Width,
83 m_dpbDescriptor.Height,
84 1,
85 1,
86 1,
87 0,
88 D3D12_RESOURCE_FLAG_NONE);
89
90 HRESULT hr = m_pD3D12Screen->dev->CreateCommittedResource(&Properties,
91 D3D12_HEAP_FLAG_NONE,
92 &resDesc,
93 D3D12_RESOURCE_STATE_COMMON,
94 nullptr,
95 IID_PPV_ARGS(m_pClearDecodedOutputTexture.GetAddressOf()));
96 if (FAILED(hr)) {
97 debug_printf("CreateCommittedResource failed with HR %x\n", hr);
98 assert(false);
99 }
100 }
101
102 *ppOutTexture2D = m_pClearDecodedOutputTexture.Get();
103 *pOutSubresourceIndex = 0;
104 } else {
105 if(is_array_of_textures()) {
106 // In non ref picture and non texarray mode, we can just use the underlying allocation in pCurrentDecodeTarget
107 // and avoid an extra copy after decoding the frame.
108 assert(is_pipe_buffer_underlying_output_decode_allocation());
109
110 auto vidBuffer = (struct d3d12_video_buffer *)(pCurrentDecodeTarget);
111 *ppOutTexture2D = d3d12_resource_resource(vidBuffer->texture);
112 *pOutSubresourceIndex = 0;
113 #if DEBUG
114 D3D12_RESOURCE_DESC desc = GetDesc(*ppOutTexture2D);
115 assert(desc.DepthOrArraySize == 1);
116 // if the underlying resource is a texture array at some point (if the impl. changes)
117 // we need to also return the correct underlying subresource in *pOutSubresourceIndex = <subres>
118 #endif
119
120 } else {
121 // The DPB Storage only has standard (without the ref only flags) allocations, directly use one of those.
122 d3d12_video_reconstructed_picture pFreshAllocation =
123 m_upD3D12TexturesStorageManager->get_new_tracked_picture_allocation();
124 *ppOutTexture2D = pFreshAllocation.pReconstructedPicture;
125 *pOutSubresourceIndex = pFreshAllocation.ReconstructedPictureSubresource;
126 }
127
128 }
129 }
130 }
131
132 //----------------------------------------------------------------------------------------------------------------------------------
133 _Use_decl_annotations_ void
get_reference_only_output(struct pipe_video_buffer * pCurrentDecodeTarget,ID3D12Resource ** ppOutputReference,uint32_t * pOutputSubresource,bool & outNeedsTransitionToDecodeWrite)134 d3d12_video_decoder_references_manager::get_reference_only_output(
135 struct pipe_video_buffer * pCurrentDecodeTarget,
136 ID3D12Resource **ppOutputReference, // out -> new reference slot assigned or nullptr
137 uint32_t * pOutputSubresource, // out -> new reference slot assigned or nullptr
138 bool &outNeedsTransitionToDecodeWrite // out -> indicates if output resource argument has to be transitioned to
139 // D3D12_RESOURCE_STATE_VIDEO_DECODE_READ by the caller
140 )
141 {
142 assert(is_reference_only());
143
144 // First try to find if there's an existing entry for this pCurrentDecodeTarget already in the DPB
145 // For interlaced scenarios, multiple end_frame calls will need to reference the same texture for top/bottom
146 assert(m_DecodeTargetToOriginalIndex7Bits.count(pCurrentDecodeTarget) > 0); // Needs to already have a Index7Bits assigned for current pic params
147 uint16_t remappedIdx = find_remapped_index(m_DecodeTargetToOriginalIndex7Bits[pCurrentDecodeTarget]);
148
149 if(remappedIdx != m_invalidIndex) { // If it already has a remapped index in use, reuse that allocation
150 // return the existing allocation for this decode target
151 d3d12_video_reconstructed_picture reconPicture = m_upD3D12TexturesStorageManager->get_reference_frame(remappedIdx);
152 *ppOutputReference = reconPicture.pReconstructedPicture;
153 *pOutputSubresource = reconPicture.ReconstructedPictureSubresource;
154 outNeedsTransitionToDecodeWrite = true;
155 } else {
156 // The DPB Storage only has REFERENCE_ONLY allocations, use one of those.
157 d3d12_video_reconstructed_picture pFreshAllocation =
158 m_upD3D12TexturesStorageManager->get_new_tracked_picture_allocation();
159 *ppOutputReference = pFreshAllocation.pReconstructedPicture;
160 *pOutputSubresource = pFreshAllocation.ReconstructedPictureSubresource;
161 outNeedsTransitionToDecodeWrite = true;
162 }
163 }
164
165 //----------------------------------------------------------------------------------------------------------------------------------
166 D3D12_VIDEO_DECODE_REFERENCE_FRAMES
get_current_reference_frames()167 d3d12_video_decoder_references_manager::get_current_reference_frames()
168 {
169 d3d12_video_reference_frames args = m_upD3D12TexturesStorageManager->get_current_reference_frames();
170
171
172 // Convert generic IUnknown into the actual decoder heap object
173 m_ppHeaps.resize(args.NumTexture2Ds, nullptr);
174 HRESULT hr = S_OK;
175 for (uint32_t i = 0; i < args.NumTexture2Ds; i++) {
176 if (args.ppHeaps[i]) {
177 hr = args.ppHeaps[i]->QueryInterface(IID_PPV_ARGS(&m_ppHeaps[i]));
178 assert(SUCCEEDED(hr));
179 } else {
180 m_ppHeaps[i] = nullptr;
181 }
182 }
183
184 D3D12_VIDEO_DECODE_REFERENCE_FRAMES retVal = {
185 args.NumTexture2Ds,
186 args.ppTexture2Ds,
187 args.pSubresources,
188 m_ppHeaps.data(),
189 };
190
191 return retVal;
192 }
193
194 //----------------------------------------------------------------------------------------------------------------------------------
195 _Use_decl_annotations_
d3d12_video_decoder_references_manager(const struct d3d12_screen * pD3D12Screen,uint32_t NodeMask,d3d12_video_decode_profile_type DecodeProfileType,d3d12_video_decode_dpb_descriptor m_dpbDescriptor)196 d3d12_video_decoder_references_manager::d3d12_video_decoder_references_manager(
197 const struct d3d12_screen * pD3D12Screen,
198 uint32_t NodeMask,
199 d3d12_video_decode_profile_type DecodeProfileType,
200 d3d12_video_decode_dpb_descriptor m_dpbDescriptor)
201 : m_DecodeTargetToOriginalIndex7Bits({ }),
202 m_CurrentIndex7BitsAvailable(0),
203 m_pD3D12Screen(pD3D12Screen),
204 m_invalidIndex(GetInvalidReferenceIndex(DecodeProfileType)),
205 m_dpbDescriptor(m_dpbDescriptor),
206 m_formatInfo({ m_dpbDescriptor.Format })
207 {
208 HRESULT hr = m_pD3D12Screen->dev->CheckFeatureSupport(D3D12_FEATURE_FORMAT_INFO, &m_formatInfo, sizeof(m_formatInfo));
209 assert(SUCCEEDED(hr));
210 D3D12_VIDEO_ENCODER_PICTURE_RESOLUTION_DESC targetFrameResolution = { static_cast<uint32_t>(m_dpbDescriptor.Width),
211 m_dpbDescriptor.Height };
212 D3D12_RESOURCE_FLAGS resourceAllocFlags =
213 m_dpbDescriptor.fReferenceOnly ?
214 (D3D12_RESOURCE_FLAG_VIDEO_DECODE_REFERENCE_ONLY | D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE) :
215 D3D12_RESOURCE_FLAG_NONE;
216
217 if (m_dpbDescriptor.fArrayOfTexture) {
218 // If all subresources are 0, the DPB is loaded with an array of individual textures, the D3D Encode API expects
219 // pSubresources to be null in this case The D3D Decode API expects it to be non-null even with all zeroes.
220 bool setNullSubresourcesOnAllZero = false;
221 m_upD3D12TexturesStorageManager =
222 std::make_unique<d3d12_array_of_textures_dpb_manager>(m_dpbDescriptor.dpbSize,
223 m_pD3D12Screen->dev,
224 m_dpbDescriptor.Format,
225 targetFrameResolution,
226 resourceAllocFlags,
227 setNullSubresourcesOnAllZero,
228 m_dpbDescriptor.m_NodeMask,
229 !is_pipe_buffer_underlying_output_decode_allocation());
230 } else {
231 m_upD3D12TexturesStorageManager = std::make_unique<d3d12_texture_array_dpb_manager>(m_dpbDescriptor.dpbSize,
232 m_pD3D12Screen->dev,
233 m_dpbDescriptor.Format,
234 targetFrameResolution,
235 resourceAllocFlags,
236 m_dpbDescriptor.m_NodeMask);
237 }
238
239 m_referenceDXVAIndices.resize(m_dpbDescriptor.dpbSize);
240
241 d3d12_video_reconstructed_picture reconPicture = { nullptr, 0, nullptr };
242
243 for (uint32_t dpbIdx = 0; dpbIdx < m_dpbDescriptor.dpbSize; dpbIdx++) {
244 m_upD3D12TexturesStorageManager->insert_reference_frame(reconPicture, dpbIdx);
245 }
246
247 mark_all_references_as_unused();
248 release_unused_references_texture_memory();
249 }
250
251 //----------------------------------------------------------------------------------------------------------------------------------
252 uint16_t
find_remapped_index(uint16_t originalIndex)253 d3d12_video_decoder_references_manager::find_remapped_index(uint16_t originalIndex)
254 {
255 // Check if the index is already mapped.
256 for (uint16_t remappedIndex = 0; remappedIndex < m_dpbDescriptor.dpbSize; remappedIndex++) {
257 if (m_referenceDXVAIndices[remappedIndex].originalIndex == originalIndex) {
258 return remappedIndex;
259 }
260 }
261
262 return m_invalidIndex;
263 }
264
265 //----------------------------------------------------------------------------------------------------------------------------------
266 uint16_t
update_entry(uint16_t index,ID3D12Resource * & pOutputReference,uint32_t & OutputSubresource,bool & outNeedsTransitionToDecodeRead)267 d3d12_video_decoder_references_manager::update_entry(
268 uint16_t index, // in
269 ID3D12Resource *&pOutputReference, // out -> new reference slot assigned or nullptr
270 uint32_t & OutputSubresource, // out -> new reference slot assigned or 0
271 bool &outNeedsTransitionToDecodeRead // out -> indicates if output resource argument has to be transitioned to
272 // D3D12_RESOURCE_STATE_VIDEO_DECODE_READ by the caller
273 )
274 {
275 uint16_t remappedIndex = m_invalidIndex;
276 outNeedsTransitionToDecodeRead = false;
277
278 if (index != m_invalidIndex) {
279 remappedIndex = find_remapped_index(index);
280
281 outNeedsTransitionToDecodeRead = true;
282 if (remappedIndex == m_invalidIndex || remappedIndex == m_currentOutputIndex) {
283 debug_printf("[d3d12_video_decoder_references_manager] update_entry - Invalid Reference Index\n");
284
285 remappedIndex = m_currentOutputIndex;
286 outNeedsTransitionToDecodeRead = false;
287 }
288
289 d3d12_video_reconstructed_picture reconPicture =
290 m_upD3D12TexturesStorageManager->get_reference_frame(remappedIndex);
291 pOutputReference = outNeedsTransitionToDecodeRead ? reconPicture.pReconstructedPicture : nullptr;
292 OutputSubresource = outNeedsTransitionToDecodeRead ? reconPicture.ReconstructedPictureSubresource : 0u;
293 }
294
295 return remappedIndex;
296 }
297
298 //----------------------------------------------------------------------------------------------------------------------------------
299 _Use_decl_annotations_ uint16_t
store_future_reference(uint16_t index,ComPtr<ID3D12VideoDecoderHeap> & decoderHeap,ID3D12Resource * pTexture2D,uint32_t subresourceIndex)300 d3d12_video_decoder_references_manager::store_future_reference(uint16_t index,
301 ComPtr<ID3D12VideoDecoderHeap> &decoderHeap,
302 ID3D12Resource * pTexture2D,
303 uint32_t subresourceIndex)
304 {
305 // Check if the index was in use.
306 uint16_t remappedIndex = find_remapped_index(index);
307
308 if (remappedIndex == m_invalidIndex) {
309 // The current output index was not used last frame. Get an unused entry.
310 remappedIndex = find_remapped_index(m_invalidIndex);
311 }
312
313 if (remappedIndex == m_invalidIndex) {
314 debug_printf(
315 "[d3d12_video_decoder_references_manager] d3d12_video_decoder_references_manager - Decode - No available "
316 "reference map entry for output.\n");
317 assert(false);
318 }
319
320 // Set the index as the key in this map entry.
321 m_referenceDXVAIndices[remappedIndex].originalIndex = index;
322 IUnknown *pUnkHeap = nullptr;
323 HRESULT hr = decoderHeap.Get()->QueryInterface(IID_PPV_ARGS(&pUnkHeap));
324 assert(SUCCEEDED(hr));
325 d3d12_video_reconstructed_picture reconPic = { pTexture2D, subresourceIndex, pUnkHeap };
326
327 m_upD3D12TexturesStorageManager->assign_reference_frame(reconPic, remappedIndex);
328
329 // Store the index to use for error handling when caller specifies and invalid reference index.
330 m_currentOutputIndex = remappedIndex;
331 m_currentSubresourceIndex = subresourceIndex;
332 m_currentResource = pTexture2D;
333
334 return remappedIndex;
335 }
336
337 //----------------------------------------------------------------------------------------------------------------------------------
338 void
mark_reference_in_use(uint16_t index)339 d3d12_video_decoder_references_manager::mark_reference_in_use(uint16_t index)
340 {
341 if (index != m_invalidIndex) {
342 uint16_t remappedIndex = find_remapped_index(index);
343 if (remappedIndex != m_invalidIndex) {
344 m_referenceDXVAIndices[remappedIndex].fUsed = true;
345 }
346 }
347 }
348
349 //----------------------------------------------------------------------------------------------------------------------------------
350 void
release_unused_references_texture_memory()351 d3d12_video_decoder_references_manager::release_unused_references_texture_memory()
352 {
353 for (uint32_t index = 0; index < m_dpbDescriptor.dpbSize; index++) {
354 if (!m_referenceDXVAIndices[index].fUsed) {
355 d3d12_video_reconstructed_picture reconPicture = m_upD3D12TexturesStorageManager->get_reference_frame(index);
356 if (reconPicture.pReconstructedPicture != nullptr) {
357 bool wasTracked = m_upD3D12TexturesStorageManager->untrack_reconstructed_picture_allocation(reconPicture);
358 // Untrack this resource, will mark it as free un the underlying storage buffer pool
359 // if not tracked, must be due to no-copies allocation
360 assert (wasTracked || is_pipe_buffer_underlying_output_decode_allocation());
361
362 d3d12_video_reconstructed_picture nullReconPic = { nullptr, 0, nullptr };
363
364 // Mark the unused refpic as null/empty in the DPB
365 m_upD3D12TexturesStorageManager->assign_reference_frame(nullReconPic, index);
366
367 // Remove the entry in m_DecodeTargetToOriginalIndex7Bits
368 auto value = m_referenceDXVAIndices[index].originalIndex;
369 auto it = std::find_if(m_DecodeTargetToOriginalIndex7Bits.begin(), m_DecodeTargetToOriginalIndex7Bits.end(),
370 [&value](const std::pair< struct pipe_video_buffer*, uint8_t > &p) {
371 return p.second == value;
372 });
373
374 assert(it != m_DecodeTargetToOriginalIndex7Bits.end());
375
376 m_DecodeTargetToOriginalIndex7Bits.erase(it);
377 }
378
379
380 m_referenceDXVAIndices[index].originalIndex = m_invalidIndex;
381 }
382 }
383 }
384
385 //----------------------------------------------------------------------------------------------------------------------------------
386 void
mark_all_references_as_unused()387 d3d12_video_decoder_references_manager::mark_all_references_as_unused()
388 {
389 for (uint32_t index = 0; index < m_dpbDescriptor.dpbSize; index++) {
390 m_referenceDXVAIndices[index].fUsed = false;
391 }
392 }
393
394 //----------------------------------------------------------------------------------------------------------------------------------
395 void
print_dpb()396 d3d12_video_decoder_references_manager::print_dpb()
397 {
398 // Resource backing storage always has to match dpbsize
399 if(!is_pipe_buffer_underlying_output_decode_allocation()) {
400 assert(m_upD3D12TexturesStorageManager->get_number_of_tracked_allocations() == m_dpbDescriptor.dpbSize);
401 }
402
403 // get_current_reference_frames query-interfaces the pVideoHeap's.
404 D3D12_VIDEO_DECODE_REFERENCE_FRAMES curRefFrames = get_current_reference_frames();
405 std::string dpbContents;
406 for (uint32_t dpbResIdx = 0;dpbResIdx < curRefFrames.NumTexture2Ds;dpbResIdx++) {
407 dpbContents += "\t{ DPBidx: ";
408 dpbContents += std::to_string(dpbResIdx);
409 dpbContents += " - ResourcePtr: ";
410 char strBufTex[256];
411 memset(&strBufTex, '\0', 256);
412 sprintf(strBufTex, "%p", curRefFrames.ppTexture2Ds[dpbResIdx]);
413 dpbContents += std::string(strBufTex);
414 dpbContents += " - SubresourceIdx: ";
415 dpbContents += (curRefFrames.pSubresources ? std::to_string(curRefFrames.pSubresources[dpbResIdx]) : "0");
416 dpbContents += " - DecoderHeapPtr: ";
417 char strBufHeap[256];
418 memset(&strBufHeap, '\0', 256);
419 if(curRefFrames.ppHeaps && curRefFrames.ppHeaps[dpbResIdx]) {
420 sprintf(strBufHeap, "%p", curRefFrames.ppHeaps[dpbResIdx]);
421 dpbContents += std::string(strBufHeap);
422 } else {
423 dpbContents += "(nil)";
424 }
425 dpbContents += " - Slot type: ";
426 dpbContents += ((m_currentResource == curRefFrames.ppTexture2Ds[dpbResIdx]) && (m_currentSubresourceIndex == curRefFrames.pSubresources[dpbResIdx])) ? "Current decoded frame output" : "Reference frame";
427 dpbContents += " - DXVA_PicParams Reference Index: ";
428 dpbContents += (m_referenceDXVAIndices[dpbResIdx].originalIndex != m_invalidIndex) ? std::to_string(m_referenceDXVAIndices[dpbResIdx].originalIndex) : "DXVA_UNUSED_PICENTRY";
429 dpbContents += "}\n";
430 }
431
432 debug_printf("[D3D12 Video Decoder Picture Manager] Decode session information:\n"
433 "\tDPB Maximum Size (max_ref_count + one_slot_curpic): %d\n"
434 "\tDXGI_FORMAT: %d\n"
435 "\tTexture resolution: (%" PRIu64 ", %d)\n"
436 "\tD3D12_RESOURCE_FLAG_VIDEO_DECODE_REFERENCE_ONLY enforced: %d\n"
437 "\tAllocation Mode: %s\n"
438 "\n ----------------------\n\tCurrent frame information:\n"
439 "\tD3D12_VIDEO_DECODE_REFERENCE_FRAMES.NumTexture2Ds: %d\n"
440 "\tDPB Contents Table:\n%s",
441 m_upD3D12TexturesStorageManager->get_number_of_tracked_allocations(),
442 m_dpbDescriptor.Format,
443 m_dpbDescriptor.Width,
444 m_dpbDescriptor.Height,
445 m_dpbDescriptor.fReferenceOnly,
446 (m_dpbDescriptor.fArrayOfTexture ? "ArrayOfTextures" : "TextureArray"),
447 m_upD3D12TexturesStorageManager->get_number_of_pics_in_dpb(),
448 dpbContents.c_str());
449 }
450