1 /*
2  * This file is part of libplacebo.
3  *
4  * libplacebo is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * libplacebo is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "gpu.h"
19 #include "formats.h"
20 
tex_subresource(pl_tex tex)21 static inline UINT tex_subresource(pl_tex tex)
22 {
23     struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
24     return tex_p->array_slice >= 0 ? tex_p->array_slice : 0;
25 }
26 
tex_init(pl_gpu gpu,pl_tex tex)27 static bool tex_init(pl_gpu gpu, pl_tex tex)
28 {
29     struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
30     struct d3d11_ctx *ctx = p->ctx;
31     struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
32 
33     if (tex->params.sampleable || tex->params.storable) {
34         // The SRV format may be omitted when it matches the texture format, but
35         // for simplicity's sake we always set it. It will match the texture
36         // format for textures created with tex_create, but it can be different
37         // for video textures wrapped with pl_d3d11_wrap.
38         D3D11_SHADER_RESOURCE_VIEW_DESC srvdesc = {
39             .Format = fmt_to_dxgi(tex->params.format),
40         };
41         switch (pl_tex_params_dimension(tex->params)) {
42         case 1:
43             if (tex_p->array_slice >= 0) {
44                 srvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1DARRAY;
45                 srvdesc.Texture1DArray.MipLevels = 1;
46                 srvdesc.Texture1DArray.FirstArraySlice = tex_p->array_slice;
47                 srvdesc.Texture1DArray.ArraySize = 1;
48             } else {
49                 srvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1D;
50                 srvdesc.Texture1D.MipLevels = 1;
51             }
52             break;
53         case 2:
54             if (tex_p->array_slice >= 0) {
55                 srvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
56                 srvdesc.Texture2DArray.MipLevels = 1;
57                 srvdesc.Texture2DArray.FirstArraySlice = tex_p->array_slice;
58                 srvdesc.Texture2DArray.ArraySize = 1;
59             } else {
60                 srvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
61                 srvdesc.Texture2D.MipLevels = 1;
62             }
63             break;
64         case 3:
65             // D3D11 does not have Texture3D arrays
66             srvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D;
67             srvdesc.Texture3D.MipLevels = 1;
68             break;
69         }
70         D3D(ID3D11Device_CreateShaderResourceView(p->dev, tex_p->res, &srvdesc,
71                                                   &tex_p->srv));
72     }
73 
74     if (tex->params.renderable) {
75         D3D(ID3D11Device_CreateRenderTargetView(p->dev, tex_p->res, NULL,
76                                                 &tex_p->rtv));
77     }
78 
79     if (p->fl >= D3D_FEATURE_LEVEL_11_0 && tex->params.storable) {
80         D3D(ID3D11Device_CreateUnorderedAccessView(p->dev, tex_p->res, NULL,
81                                                    &tex_p->uav));
82     }
83 
84     return true;
85 error:
86     return false;
87 }
88 
pl_d3d11_tex_destroy(pl_gpu gpu,pl_tex tex)89 void pl_d3d11_tex_destroy(pl_gpu gpu, pl_tex tex)
90 {
91     struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
92     struct d3d11_ctx *ctx = p->ctx;
93     struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
94 
95     SAFE_RELEASE(tex_p->srv);
96     SAFE_RELEASE(tex_p->rtv);
97     SAFE_RELEASE(tex_p->uav);
98     SAFE_RELEASE(tex_p->res);
99     SAFE_RELEASE(tex_p->staging);
100 
101     pl_d3d11_flush_message_queue(ctx, "After texture destroy");
102 
103     pl_free((void *) tex);
104 }
105 
pl_d3d11_tex_create(pl_gpu gpu,const struct pl_tex_params * params)106 pl_tex pl_d3d11_tex_create(pl_gpu gpu, const struct pl_tex_params *params)
107 {
108     struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
109     struct d3d11_ctx *ctx = p->ctx;
110 
111     struct pl_tex *tex = pl_zalloc_obj(NULL, tex, struct pl_tex_d3d11);
112     tex->params = *params;
113     tex->params.initial_data = NULL;
114     tex->sampler_type = PL_SAMPLER_NORMAL;
115 
116     struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
117 
118     DXGI_FORMAT dxfmt = fmt_to_dxgi(params->format);
119 
120     D3D11_USAGE usage = D3D11_USAGE_DEFAULT;
121     D3D11_BIND_FLAG bind_flags = 0;
122 
123     if (p->fl >= D3D_FEATURE_LEVEL_11_0) {
124         // On >=FL11_0, blit emulation needs image storage
125         tex->params.storable |= params->blit_src || params->blit_dst;
126 
127         // Blit emulation can use a sampler for linear filtering during stretch
128         if ((tex->params.format->caps & PL_FMT_CAP_LINEAR) && params->blit_src)
129             tex->params.sampleable = true;
130     } else {
131         // On <FL11_0, blit emulation uses a render pass
132         tex->params.sampleable |= params->blit_src;
133         tex->params.renderable |= params->blit_dst;
134     }
135 
136     if (tex->params.sampleable)
137         bind_flags |= D3D11_BIND_SHADER_RESOURCE;
138     if (tex->params.renderable)
139         bind_flags |= D3D11_BIND_RENDER_TARGET;
140     if (p->fl >= D3D_FEATURE_LEVEL_11_0 && tex->params.storable)
141         bind_flags |= D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS;
142 
143     // Apparently IMMUTABLE textures are efficient, so try to infer whether we
144     // can use one
145     if (params->initial_data && !tex->params.renderable &&
146         !tex->params.storable && !params->host_writable)
147         usage = D3D11_USAGE_IMMUTABLE;
148 
149     // In FL9_x, resources with only D3D11_BIND_SHADER_RESOURCE can't be copied
150     // from GPU-accessible memory to CPU-accessible memory. The only other bind
151     // flag we set on this FL is D3D11_BIND_RENDER_TARGET, so set it.
152     if (p->fl <= D3D_FEATURE_LEVEL_9_3 && tex->params.host_readable)
153         bind_flags |= D3D11_BIND_RENDER_TARGET;
154 
155     // In FL9_x, when using DEFAULT or IMMUTABLE, BindFlags cannot be zero
156     if (p->fl <= D3D_FEATURE_LEVEL_9_3 && !bind_flags)
157         bind_flags |= D3D11_BIND_SHADER_RESOURCE;
158 
159     D3D11_SUBRESOURCE_DATA data;
160     D3D11_SUBRESOURCE_DATA *pdata = NULL;
161     if (params->initial_data) {
162         data = (D3D11_SUBRESOURCE_DATA) {
163             .pSysMem = params->initial_data,
164             .SysMemPitch = params->w * params->format->texel_size,
165         };
166         if (params->d)
167             data.SysMemSlicePitch = data.SysMemPitch * params->h;
168         pdata = &data;
169     }
170 
171     switch (pl_tex_params_dimension(*params)) {
172     case 1:;
173         D3D11_TEXTURE1D_DESC desc1d = {
174             .Width = params->w,
175             .MipLevels = 1,
176             .ArraySize = 1,
177             .Format = dxfmt,
178             .Usage = usage,
179             .BindFlags = bind_flags,
180         };
181         D3D(ID3D11Device_CreateTexture1D(p->dev, &desc1d, pdata, &tex_p->tex1d));
182         tex_p->res = (ID3D11Resource *)tex_p->tex1d;
183 
184         // Create a staging texture with CPU access for pl_tex_download()
185         if (params->host_readable) {
186             desc1d.BindFlags = 0;
187             desc1d.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
188             desc1d.Usage = D3D11_USAGE_STAGING;
189 
190             D3D(ID3D11Device_CreateTexture1D(p->dev, &desc1d, NULL,
191                                              &tex_p->staging1d));
192             tex_p->staging = (ID3D11Resource *) tex_p->staging1d;
193         }
194         break;
195     case 2:;
196         D3D11_TEXTURE2D_DESC desc2d = {
197             .Width = params->w,
198             .Height = params->h,
199             .MipLevels = 1,
200             .ArraySize = 1,
201             .SampleDesc.Count = 1,
202             .Format = dxfmt,
203             .Usage = usage,
204             .BindFlags = bind_flags,
205         };
206         D3D(ID3D11Device_CreateTexture2D(p->dev, &desc2d, pdata, &tex_p->tex2d));
207         tex_p->res = (ID3D11Resource *)tex_p->tex2d;
208 
209         // Create a staging texture with CPU access for pl_tex_download()
210         if (params->host_readable) {
211             desc2d.BindFlags = 0;
212             desc2d.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
213             desc2d.Usage = D3D11_USAGE_STAGING;
214 
215             D3D(ID3D11Device_CreateTexture2D(p->dev, &desc2d, NULL,
216                                              &tex_p->staging2d));
217             tex_p->staging = (ID3D11Resource *) tex_p->staging2d;
218         }
219         break;
220     case 3:;
221         D3D11_TEXTURE3D_DESC desc3d = {
222             .Width = params->w,
223             .Height = params->h,
224             .Depth = params->d,
225             .MipLevels = 1,
226             .Format = dxfmt,
227             .Usage = usage,
228             .BindFlags = bind_flags,
229         };
230         D3D(ID3D11Device_CreateTexture3D(p->dev, &desc3d, pdata, &tex_p->tex3d));
231         tex_p->res = (ID3D11Resource *)tex_p->tex3d;
232 
233         // Create a staging texture with CPU access for pl_tex_download()
234         if (params->host_readable) {
235             desc3d.BindFlags = 0;
236             desc3d.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
237             desc3d.Usage = D3D11_USAGE_STAGING;
238 
239             D3D(ID3D11Device_CreateTexture3D(p->dev, &desc3d, NULL,
240                                              &tex_p->staging3d));
241             tex_p->staging = (ID3D11Resource *) tex_p->staging3d;
242         }
243         break;
244     default:
245         pl_unreachable();
246     }
247 
248     tex_p->array_slice = -1;
249 
250     if (!tex_init(gpu, tex))
251         goto error;
252 
253     pl_d3d11_flush_message_queue(ctx, "After texture create");
254 
255     return tex;
256 
257 error:
258     pl_d3d11_tex_destroy(gpu, tex);
259     return NULL;
260 }
261 
pl_d3d11_wrap(pl_gpu gpu,const struct pl_d3d11_wrap_params * params)262 pl_tex pl_d3d11_wrap(pl_gpu gpu, const struct pl_d3d11_wrap_params *params)
263 {
264     struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
265     struct d3d11_ctx *ctx = p->ctx;
266 
267     struct pl_tex *tex = pl_zalloc_obj(NULL, tex, struct pl_tex_d3d11);
268     tex->sampler_type = PL_SAMPLER_NORMAL;
269 
270     struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
271 
272     DXGI_FORMAT fmt = DXGI_FORMAT_UNKNOWN;
273     D3D11_USAGE usage = D3D11_USAGE_DEFAULT;
274     D3D11_BIND_FLAG bind_flags = 0;
275     UINT mip_levels = 1;
276     UINT array_size = 1;
277     UINT sample_count = 1;
278 
279     D3D11_RESOURCE_DIMENSION type;
280     ID3D11Resource_GetType(params->tex, &type);
281 
282     switch (type) {
283     case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
284         D3D(ID3D11Resource_QueryInterface(params->tex, &IID_ID3D11Texture1D,
285                                           (void **) &tex_p->tex1d));
286         tex_p->res = (ID3D11Resource *) tex_p->tex1d;
287 
288         D3D11_TEXTURE1D_DESC desc1d;
289         ID3D11Texture1D_GetDesc(tex_p->tex1d, &desc1d);
290 
291         tex->params.w = desc1d.Width;
292         mip_levels = desc1d.MipLevels;
293         array_size = desc1d.ArraySize;
294         fmt = desc1d.Format;
295         usage = desc1d.Usage;
296         bind_flags = desc1d.BindFlags;
297         break;
298 
299     case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
300         D3D(ID3D11Resource_QueryInterface(params->tex, &IID_ID3D11Texture2D,
301                                           (void **) &tex_p->tex2d));
302         tex_p->res = (ID3D11Resource *) tex_p->tex2d;
303 
304         D3D11_TEXTURE2D_DESC desc2d;
305         ID3D11Texture2D_GetDesc(tex_p->tex2d, &desc2d);
306 
307         tex->params.w = desc2d.Width;
308         tex->params.h = desc2d.Height;
309         mip_levels = desc2d.MipLevels;
310         array_size = desc2d.ArraySize;
311         fmt = desc2d.Format;
312         sample_count = desc2d.SampleDesc.Count;
313         usage = desc2d.Usage;
314         bind_flags = desc2d.BindFlags;
315 
316         // Allow the format and size of 2D textures to be overridden to support
317         // shader views of video resources
318         if (params->fmt) {
319             fmt = params->fmt;
320             tex->params.w = params->w;
321             tex->params.h = params->h;
322         }
323 
324         break;
325 
326     case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
327         D3D(ID3D11Resource_QueryInterface(params->tex, &IID_ID3D11Texture3D,
328                                           (void **) &tex_p->tex3d));
329         tex_p->res = (ID3D11Resource *) tex_p->tex3d;
330 
331         D3D11_TEXTURE3D_DESC desc3d;
332         ID3D11Texture3D_GetDesc(tex_p->tex3d, &desc3d);
333 
334         tex->params.w = desc3d.Width;
335         tex->params.h = desc3d.Height;
336         tex->params.d = desc3d.Depth;
337         mip_levels = desc3d.MipLevels;
338         fmt = desc3d.Format;
339         usage = desc3d.Usage;
340         bind_flags = desc3d.BindFlags;
341         break;
342 
343     case D3D11_RESOURCE_DIMENSION_UNKNOWN:
344     case D3D11_RESOURCE_DIMENSION_BUFFER:
345         PL_ERR(gpu, "Resource is not suitable to wrap");
346         goto error;
347     }
348 
349     if (mip_levels != 1) {
350         PL_ERR(gpu, "Mipmapped textures not supported for wrapping");
351         goto error;
352     }
353     if (sample_count != 1) {
354         PL_ERR(gpu, "Multisampled textures not supported for wrapping");
355         goto error;
356     }
357     if (usage != D3D11_USAGE_DEFAULT) {
358         PL_ERR(gpu, "Resource is not D3D11_USAGE_DEFAULT");
359         goto error;
360     }
361 
362     if (array_size > 1) {
363         if (params->array_slice < 0 || params->array_slice >= array_size) {
364             PL_ERR(gpu, "array_slice out of range");
365             goto error;
366         }
367         tex_p->array_slice = params->array_slice;
368     } else {
369         tex_p->array_slice = -1;
370     }
371 
372     if (bind_flags & D3D11_BIND_SHADER_RESOURCE) {
373         tex->params.sampleable = true;
374 
375         // Blit emulation uses a render pass on <FL11_0
376         if (p->fl < D3D_FEATURE_LEVEL_11_0)
377             tex->params.blit_src = true;
378     }
379     if (bind_flags & D3D11_BIND_RENDER_TARGET) {
380         tex->params.renderable = true;
381 
382         // Blit emulation uses a render pass on <FL11_0
383         if (p->fl < D3D_FEATURE_LEVEL_11_0)
384             tex->params.blit_dst = true;
385     }
386     static const D3D11_BIND_FLAG storable_flags =
387         D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
388     if ((bind_flags & storable_flags) == storable_flags) {
389         tex->params.storable = true;
390 
391         // Blit emulation uses image storage on >=FL11_0. A feature level check
392         // isn't required because <FL11_0 doesn't have storable images.
393         tex->params.blit_src = tex->params.blit_dst = true;
394     }
395 
396     for (int i = 0; i < gpu->num_formats; i++) {
397         DXGI_FORMAT target_fmt = fmt_to_dxgi(gpu->formats[i]);
398         if (fmt == target_fmt) {
399             tex->params.format = gpu->formats[i];
400             break;
401         }
402     }
403     if (!tex->params.format) {
404         PL_ERR(gpu, "Could not find a suitable pl_fmt for wrapped resource");
405         goto error;
406     }
407 
408     if (!tex_init(gpu, tex))
409         goto error;
410 
411     pl_d3d11_flush_message_queue(ctx, "After texture wrap");
412 
413     return tex;
414 
415 error:
416     pl_d3d11_tex_destroy(gpu, tex);
417     return NULL;
418 }
419 
pl_d3d11_tex_invalidate(pl_gpu gpu,pl_tex tex)420 void pl_d3d11_tex_invalidate(pl_gpu gpu, pl_tex tex)
421 {
422     struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
423     struct d3d11_ctx *ctx = p->ctx;
424     struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
425 
426     // Resource discarding requires D3D11.1
427     if (!p->imm1)
428         return;
429 
430     // Prefer discarding a view to discarding the whole resource. The reason
431     // for this is that a pl_tex can refer to a single member of a texture
432     // array. Discarding the SRV, RTV or UAV should only discard that member.
433     if (tex_p->rtv) {
434         ID3D11DeviceContext1_DiscardView(p->imm1, (ID3D11View *) tex_p->rtv);
435     } else if (tex_p->uav) {
436         ID3D11DeviceContext1_DiscardView(p->imm1, (ID3D11View *) tex_p->uav);
437     } else if (tex_p->srv) {
438         ID3D11DeviceContext1_DiscardView(p->imm1, (ID3D11View *) tex_p->srv);
439     } else if (tex_p->array_slice < 0) {
440         // If there are no views, only discard if the ID3D11Resource is not a
441         // texture array
442         ID3D11DeviceContext1_DiscardResource(p->imm1, tex_p->res);
443     }
444 
445     pl_d3d11_flush_message_queue(ctx, "After texture invalidate");
446 }
447 
pl_d3d11_tex_clear_ex(pl_gpu gpu,pl_tex tex,const union pl_clear_color color)448 void pl_d3d11_tex_clear_ex(pl_gpu gpu, pl_tex tex,
449                            const union pl_clear_color color)
450 {
451     struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
452     struct d3d11_ctx *ctx = p->ctx;
453     struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
454 
455     if (tex->params.format->type == PL_FMT_UINT) {
456         if (tex_p->uav) {
457             ID3D11DeviceContext_ClearUnorderedAccessViewUint(p->imm, tex_p->uav,
458                                                              color.u);
459         } else {
460             float c[4] = { color.u[0], color.u[1], color.u[2], color.u[3] };
461             ID3D11DeviceContext_ClearRenderTargetView(p->imm, tex_p->rtv, c);
462         }
463 
464     } else if (tex->params.format->type == PL_FMT_SINT) {
465         if (tex_p->uav) {
466             ID3D11DeviceContext_ClearUnorderedAccessViewUint(p->imm, tex_p->uav,
467                                                              color.i);
468         } else {
469             float c[4] = { color.i[0], color.i[1], color.i[2], color.i[3] };
470             ID3D11DeviceContext_ClearRenderTargetView(p->imm, tex_p->rtv, c);
471         }
472 
473     } else if (tex_p->rtv) {
474         ID3D11DeviceContext_ClearRenderTargetView(p->imm, tex_p->rtv, color.f);
475     } else {
476         ID3D11DeviceContext_ClearUnorderedAccessViewFloat(p->imm, tex_p->uav, color.f);
477     }
478 
479     pl_d3d11_flush_message_queue(ctx, "After texture clear");
480 }
481 
482 #define pl_rect3d_to_box(rc)                             \
483     ((D3D11_BOX) {                                       \
484         .left = rc.x0, .top = rc.y0, .front = rc.z0,     \
485         .right = rc.x1, .bottom = rc.y1, .back = rc.z1,  \
486     })
487 
pl_d3d11_tex_blit(pl_gpu gpu,const struct pl_tex_blit_params * params)488 void pl_d3d11_tex_blit(pl_gpu gpu, const struct pl_tex_blit_params *params)
489 {
490     struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
491     struct d3d11_ctx *ctx = p->ctx;
492     struct pl_tex_d3d11 *src_p = PL_PRIV(params->src);
493     DXGI_FORMAT src_fmt = fmt_to_dxgi(params->src->params.format);
494     struct pl_tex_d3d11 *dst_p = PL_PRIV(params->dst);
495     DXGI_FORMAT dst_fmt = fmt_to_dxgi(params->dst->params.format);
496 
497     // If the blit operation doesn't require flipping, scaling or format
498     // conversion, we can use CopySubresourceRegion
499     struct pl_rect3d src_rc = params->src_rc, dst_rc = params->dst_rc;
500     if (pl_rect3d_eq(src_rc, dst_rc) && src_fmt == dst_fmt) {
501         struct pl_rect3d rc = params->src_rc;
502         pl_rect3d_normalize(&rc);
503 
504         ID3D11DeviceContext_CopySubresourceRegion(p->imm, dst_p->res,
505             tex_subresource(params->dst), rc.x0, rc.y0, rc.z0, src_p->res,
506             tex_subresource(params->src), &pl_rect3d_to_box(rc));
507     } else if (p->fl >= D3D_FEATURE_LEVEL_11_0) {
508         if (!pl_tex_blit_compute(gpu, p->dp, params))
509             PL_ERR(gpu, "Failed compute shader fallback blit");
510     } else {
511         pl_tex_blit_raster(gpu, p->dp, params);
512     }
513 
514     pl_d3d11_flush_message_queue(ctx, "After texture blit");
515 }
516 
pl_d3d11_tex_upload(pl_gpu gpu,const struct pl_tex_transfer_params * params)517 bool pl_d3d11_tex_upload(pl_gpu gpu, const struct pl_tex_transfer_params *params)
518 {
519     struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
520     struct d3d11_ctx *ctx = p->ctx;
521     pl_tex tex = params->tex;
522     struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
523 
524     UINT row_pitch = params->stride_w * tex->params.format->texel_size;
525     UINT depth_pitch = row_pitch * params->stride_h;
526 
527     pl_d3d11_timer_start(gpu, params->timer);
528 
529     ID3D11DeviceContext_UpdateSubresource(p->imm, tex_p->res,
530         tex_subresource(tex), &pl_rect3d_to_box(params->rc), params->ptr,
531         row_pitch, depth_pitch);
532 
533     pl_d3d11_timer_end(gpu, params->timer);
534     pl_d3d11_flush_message_queue(ctx, "After texture upload");
535 
536     return true;
537 }
538 
pl_d3d11_tex_download(pl_gpu gpu,const struct pl_tex_transfer_params * params)539 bool pl_d3d11_tex_download(pl_gpu gpu, const struct pl_tex_transfer_params *params)
540 {
541     struct pl_gpu_d3d11 *p = PL_PRIV(gpu);
542     struct d3d11_ctx *ctx = p->ctx;
543     const struct pl_tex *tex = params->tex;
544     struct pl_tex_d3d11 *tex_p = PL_PRIV(tex);
545 
546     if (!tex_p->staging)
547         return false;
548 
549     pl_d3d11_timer_start(gpu, params->timer);
550 
551     ID3D11DeviceContext_CopySubresourceRegion(p->imm,
552         (ID3D11Resource *) tex_p->staging, 0, params->rc.x0, params->rc.y0,
553         params->rc.z0, tex_p->res, tex_subresource(tex),
554         &pl_rect3d_to_box(params->rc));
555 
556     UINT row_pitch = params->stride_w * tex->params.format->texel_size;
557     UINT depth_pitch = row_pitch * params->stride_h;
558 
559     D3D11_MAPPED_SUBRESOURCE lock;
560     D3D(ID3D11DeviceContext_Map(p->imm, (ID3D11Resource *) tex_p->staging, 0,
561                                 D3D11_MAP_READ, 0, &lock));
562 
563     char *cdst = params->ptr;
564     char *csrc = lock.pData;
565     size_t line_size = pl_rect_w(params->rc) * tex->params.format->texel_size;
566     for (int z = 0; z < pl_rect_d(params->rc); z++) {
567         for (int y = 0; y < pl_rect_h(params->rc); y++) {
568             memcpy(cdst + z * depth_pitch + y * row_pitch,
569                    csrc + (params->rc.z0 + z) * lock.DepthPitch +
570                           (params->rc.y0 + y) * lock.RowPitch + params->rc.x0,
571                    line_size);
572         }
573     }
574 
575     ID3D11DeviceContext_Unmap(p->imm, (ID3D11Resource*)tex_p->staging, 0);
576 
577     pl_d3d11_timer_end(gpu, params->timer);
578     pl_d3d11_flush_message_queue(ctx, "After texture download");
579 
580     return true;
581 
582 error:
583     return false;
584 }
585