d3d11_quad: add macros to simplify the width/height handling
[vlc.git] / modules / video_output / win32 / d3d11_quad.c
1 /*****************************************************************************
2  * d3d11_quad.c: Direct3D11 Quad handling
3  *****************************************************************************
4  * Copyright (C) 2017-2018 VLC authors and VideoLAN
5  *
6  * Authors: Steve Lhomme <robux4@gmail.com>
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 2.1 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21  *****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif
26
27 #include <assert.h>
28 #include <vlc_common.h>
29
30 #if !defined(_WIN32_WINNT) || _WIN32_WINNT < _WIN32_WINNT_WIN7
31 # undef _WIN32_WINNT
32 # define _WIN32_WINNT _WIN32_WINNT_WIN7
33 #endif
34
35 #define COBJMACROS
36 #include <d3d11.h>
37
38 #include "d3d11_quad.h"
39
40 #define SPHERE_SLICES 128
41 #define nbLatBands SPHERE_SLICES
42 #define nbLonBands SPHERE_SLICES
43
44 void D3D11_RenderQuad(d3d11_device_t *d3d_dev, d3d_quad_t *quad, d3d_vshader_t *vsshader,
45                       ID3D11ShaderResourceView *resourceView[D3D11_MAX_SHADER_VIEW],
46                       ID3D11RenderTargetView *d3drenderTargetView[D3D11_MAX_SHADER_VIEW])
47 {
48     UINT offset = 0;
49
50     /* Render the quad */
51     ID3D11DeviceContext_IASetPrimitiveTopology(d3d_dev->d3dcontext, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
52
53     /* vertex shader */
54     ID3D11DeviceContext_IASetInputLayout(d3d_dev->d3dcontext, vsshader->layout);
55     ID3D11DeviceContext_IASetVertexBuffers(d3d_dev->d3dcontext, 0, 1, &quad->pVertexBuffer, &quad->vertexStride, &offset);
56     ID3D11DeviceContext_IASetIndexBuffer(d3d_dev->d3dcontext, quad->pIndexBuffer, DXGI_FORMAT_R16_UINT, 0);
57     if ( quad->pVertexShaderConstants )
58         ID3D11DeviceContext_VSSetConstantBuffers(d3d_dev->d3dcontext, 0, 1, &quad->pVertexShaderConstants);
59
60     ID3D11DeviceContext_VSSetShader(d3d_dev->d3dcontext, vsshader->shader, NULL, 0);
61
62     if (quad->d3dsampState[0])
63         ID3D11DeviceContext_PSSetSamplers(d3d_dev->d3dcontext, 0, 2, quad->d3dsampState);
64
65     /* pixel shader */
66     ID3D11DeviceContext_PSSetConstantBuffers(d3d_dev->d3dcontext, 0, ARRAY_SIZE(quad->pPixelShaderConstants), quad->pPixelShaderConstants);
67     assert(quad->resourceCount <= D3D11_MAX_SHADER_VIEW);
68     ID3D11DeviceContext_PSSetShaderResources(d3d_dev->d3dcontext, 0, quad->resourceCount, resourceView);
69
70     for (size_t i=0; i<D3D11_MAX_SHADER_VIEW; i++)
71     {
72         if (!d3drenderTargetView[i])
73             break;
74
75         ID3D11DeviceContext_PSSetShader(d3d_dev->d3dcontext, quad->d3dpixelShader[i], NULL, 0);
76
77         ID3D11DeviceContext_RSSetViewports(d3d_dev->d3dcontext, 1, &quad->cropViewport[i]);
78
79         ID3D11DeviceContext_OMSetRenderTargets(d3d_dev->d3dcontext, 1, &d3drenderTargetView[i], NULL);
80
81         ID3D11DeviceContext_DrawIndexed(d3d_dev->d3dcontext, quad->indexCount, 0, 0);
82     }
83 }
84
85 static bool AllocQuadVertices(vlc_object_t *o, d3d11_device_t *d3d_dev, d3d_quad_t *quad, video_projection_mode_t projection)
86 {
87     HRESULT hr;
88
89     switch (projection)
90     {
91     case PROJECTION_MODE_RECTANGULAR:
92         quad->vertexCount = 4;
93         quad->indexCount = 2 * 3;
94         break;
95     case PROJECTION_MODE_EQUIRECTANGULAR:
96         quad->vertexCount = (SPHERE_SLICES + 1) * (SPHERE_SLICES + 1);
97         quad->indexCount = nbLatBands * nbLonBands * 2 * 3;
98         break;
99     case PROJECTION_MODE_CUBEMAP_LAYOUT_STANDARD:
100         quad->vertexCount = 4 * 6;
101         quad->indexCount = 6 * 2 * 3;
102         break;
103     default:
104         msg_Warn(o, "Projection mode %d not handled", projection);
105         return false;
106     }
107
108     quad->vertexStride = sizeof(d3d_vertex_t);
109
110     D3D11_BUFFER_DESC bd;
111     memset(&bd, 0, sizeof(bd));
112     bd.Usage = D3D11_USAGE_DYNAMIC;
113     bd.ByteWidth = quad->vertexStride * quad->vertexCount;
114     bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
115     bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
116
117     hr = ID3D11Device_CreateBuffer(d3d_dev->d3ddevice, &bd, NULL, &quad->pVertexBuffer);
118     if(FAILED(hr)) {
119       msg_Err(o, "Failed to create vertex buffer. (hr=%lX)", hr);
120       return false;
121     }
122
123     /* create the index of the vertices */
124     D3D11_BUFFER_DESC quadDesc = {
125         .Usage = D3D11_USAGE_DYNAMIC,
126         .ByteWidth = sizeof(WORD) * quad->indexCount,
127         .BindFlags = D3D11_BIND_INDEX_BUFFER,
128         .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE,
129     };
130
131     hr = ID3D11Device_CreateBuffer(d3d_dev->d3ddevice, &quadDesc, NULL, &quad->pIndexBuffer);
132     if(FAILED(hr)) {
133         msg_Err(o, "Could not create the quad indices. (hr=0x%lX)", hr);
134         ID3D11Buffer_Release(quad->pVertexBuffer);
135         quad->pVertexBuffer = NULL;
136         return false;
137     }
138
139     return true;
140 }
141
142 void D3D11_ReleaseQuad(d3d_quad_t *quad)
143 {
144     if (quad->pPixelShaderConstants[PS_CONST_LUMI_BOUNDS])
145     {
146         ID3D11Buffer_Release(quad->pPixelShaderConstants[PS_CONST_LUMI_BOUNDS]);
147         quad->pPixelShaderConstants[PS_CONST_LUMI_BOUNDS] = NULL;
148     }
149     if (quad->pPixelShaderConstants[PS_CONST_COLORSPACE])
150     {
151         ID3D11Buffer_Release(quad->pPixelShaderConstants[PS_CONST_COLORSPACE]);
152         quad->pPixelShaderConstants[PS_CONST_COLORSPACE] = NULL;
153     }
154     if (quad->pVertexBuffer)
155     {
156         ID3D11Buffer_Release(quad->pVertexBuffer);
157         quad->pVertexBuffer = NULL;
158     }
159     if (quad->pIndexBuffer)
160     {
161         ID3D11Buffer_Release(quad->pIndexBuffer);
162         quad->pIndexBuffer = NULL;
163     }
164     if (quad->pVertexShaderConstants)
165     {
166         ID3D11Buffer_Release(quad->pVertexShaderConstants);
167         quad->pVertexShaderConstants = NULL;
168     }
169     for (size_t i=0; i<D3D11_MAX_SHADER_VIEW; i++)
170     {
171         if (quad->d3dpixelShader[i])
172         {
173             ID3D11PixelShader_Release(quad->d3dpixelShader[i]);
174             quad->d3dpixelShader[i] = NULL;
175         }
176     }
177     for (size_t i=0; i<2; i++)
178     {
179         if (quad->d3dsampState[i])
180         {
181             ID3D11SamplerState_Release(quad->d3dsampState[i]);
182             quad->d3dsampState[i] = NULL;
183         }
184     }
185     ReleasePictureSys(&quad->picSys);
186 }
187
188 /**
189  * Compute the vertex ordering needed to rotate the video. Without
190  * rotation, the vertices of the rectangle are defined in a counterclockwise
191  * order. This function computes a remapping of the coordinates to
192  * implement the rotation, given fixed texture coordinates.
193  * The unrotated order is the following:
194  * 3--2
195  * |  |
196  * 0--1
197  * For a 180 degrees rotation it should like this:
198  * 1--0
199  * |  |
200  * 2--3
201  * Vertex 0 should be assigned coordinates at index 2 from the
202  * unrotated order and so on, thus yielding order: 2 3 0 1.
203  */
204 static void orientationVertexOrder(video_orientation_t orientation, int vertex_order[static 4])
205 {
206     switch (orientation) {
207         case ORIENT_ROTATED_90:
208             vertex_order[0] = 3;
209             vertex_order[1] = 0;
210             vertex_order[2] = 1;
211             vertex_order[3] = 2;
212             break;
213         case ORIENT_ROTATED_270:
214             vertex_order[0] = 1;
215             vertex_order[1] = 2;
216             vertex_order[2] = 3;
217             vertex_order[3] = 0;
218             break;
219         case ORIENT_ROTATED_180:
220             vertex_order[0] = 2;
221             vertex_order[1] = 3;
222             vertex_order[2] = 0;
223             vertex_order[3] = 1;
224             break;
225         case ORIENT_TRANSPOSED:
226             vertex_order[0] = 2;
227             vertex_order[1] = 1;
228             vertex_order[2] = 0;
229             vertex_order[3] = 3;
230             break;
231         case ORIENT_HFLIPPED:
232             vertex_order[0] = 1;
233             vertex_order[1] = 0;
234             vertex_order[2] = 3;
235             vertex_order[3] = 2;
236             break;
237         case ORIENT_VFLIPPED:
238             vertex_order[0] = 3;
239             vertex_order[1] = 2;
240             vertex_order[2] = 1;
241             vertex_order[3] = 0;
242             break;
243         case ORIENT_ANTI_TRANSPOSED: /* transpose + vflip */
244             vertex_order[0] = 0;
245             vertex_order[1] = 3;
246             vertex_order[2] = 2;
247             vertex_order[3] = 1;
248             break;
249        default:
250             vertex_order[0] = 0;
251             vertex_order[1] = 1;
252             vertex_order[2] = 2;
253             vertex_order[3] = 3;
254             break;
255     }
256 }
257
258 static void SetupQuadFlat(d3d_vertex_t *dst_data, const RECT *output,
259                           const d3d_quad_t *quad,
260                           WORD *triangle_pos, video_orientation_t orientation)
261 {
262     unsigned int src_width = quad->i_width;
263     unsigned int src_height = quad->i_height;
264     float MidX,MidY;
265
266     float top, bottom, left, right;
267     /* find the middle of the visible part of the texture, it will be a 0,0
268      * the rest of the visible area must correspond to -1,1 */
269     switch (orientation)
270     {
271     case ORIENT_ROTATED_90: /* 90° anti clockwise */
272         /* right/top aligned */
273         MidY = (output->left + output->right) / 2.f;
274         MidX = (output->top + output->bottom) / 2.f;
275         top    =  MidY / (MidY - output->top);
276         bottom = -(src_height - MidX) / (MidX - output->top);
277         left   =  (MidX - src_height) / (MidX - output->left);
278         right  =                 MidX / (MidX - (src_width - output->right));
279         break;
280     case ORIENT_ROTATED_180: /* 180° */
281         /* right/top aligned */
282         MidY = (output->top + output->bottom) / 2.f;
283         MidX = (output->left + output->right) / 2.f;
284         top    =  (src_height - MidY) / (output->bottom - MidY);
285         bottom = -MidY / (MidY - output->top);
286         left   = -MidX / (MidX - output->left);
287         right  =  (src_width  - MidX) / (output->right - MidX);
288         break;
289     case ORIENT_ROTATED_270: /* 90° clockwise */
290         /* right/top aligned */
291         MidY = (output->left + output->right) / 2.f;
292         MidX = (output->top + output->bottom) / 2.f;
293         top    =  (src_width  - MidX) / (output->right - MidX);
294         bottom = -MidY / (MidY - output->top);
295         left   = -MidX / (MidX - output->left);
296         right  =  (src_height - MidY) / (output->bottom - MidY);
297         break;
298     case ORIENT_ANTI_TRANSPOSED:
299         MidY = (output->left + output->right) / 2.f;
300         MidX = (output->top + output->bottom) / 2.f;
301         top    =  (src_width  - MidX) / (output->right - MidX);
302         bottom = -MidY / (MidY - output->top);
303         left   = -(src_height - MidY) / (output->bottom - MidY);
304         right  =  MidX / (MidX - output->left);
305         break;
306     case ORIENT_TRANSPOSED:
307         MidY = (output->left + output->right) / 2.f;
308         MidX = (output->top + output->bottom) / 2.f;
309         top    =  (src_width  - MidX) / (output->right - MidX);
310         bottom = -MidY / (MidY - output->top);
311         left   = -MidX / (MidX - output->left);
312         right  =  (src_height - MidY) / (output->bottom - MidY);
313         break;
314     case ORIENT_VFLIPPED:
315         MidY = (output->top + output->bottom) / 2.f;
316         MidX = (output->left + output->right) / 2.f;
317         top    =  (src_height - MidY) / (output->bottom - MidY);
318         bottom = -MidY / (MidY - output->top);
319         left   = -MidX / (MidX - output->left);
320         right  =  (src_width  - MidX) / (output->right - MidX);
321         break;
322     case ORIENT_HFLIPPED:
323         MidY = (output->top + output->bottom) / 2.f;
324         MidX = (output->left + output->right) / 2.f;
325         top    =  MidY / (MidY - output->top);
326         bottom = -(src_height - MidY) / (output->bottom - MidY);
327         left   = -(src_width  - MidX) / (output->right - MidX);
328         right  =  MidX / (MidX - output->left);
329         break;
330     case ORIENT_NORMAL:
331     default:
332         /* left/top aligned */
333         MidY = (output->top + output->bottom) / 2.f;
334         MidX = (output->left + output->right) / 2.f;
335         top    =  MidY / (MidY - output->top);
336         bottom = -(src_height - MidY) / (output->bottom - MidY);
337         left   = -MidX / (MidX - output->left);
338         right  =  (src_width  - MidX) / (output->right - MidX);
339         break;
340     }
341
342     const float vertices_coords[4][2] = {
343         { left,  bottom },
344         { right, bottom },
345         { right, top    },
346         { left,  top    },
347     };
348
349     /* Compute index remapping necessary to implement the rotation. */
350     int vertex_order[4];
351     orientationVertexOrder(orientation, vertex_order);
352
353     for (int i = 0; i < 4; ++i) {
354         dst_data[i].position.x  = vertices_coords[vertex_order[i]][0];
355         dst_data[i].position.y  = vertices_coords[vertex_order[i]][1];
356     }
357
358     // bottom left
359     dst_data[0].position.z = 0.0f;
360     dst_data[0].texture.x = 0.0f;
361     dst_data[0].texture.y = 1.0f;
362
363     // bottom right
364     dst_data[1].position.z = 0.0f;
365     dst_data[1].texture.x = 1.0f;
366     dst_data[1].texture.y = 1.0f;
367
368     // top right
369     dst_data[2].position.z = 0.0f;
370     dst_data[2].texture.x = 1.0f;
371     dst_data[2].texture.y = 0.0f;
372
373     // top left
374     dst_data[3].position.z = 0.0f;
375     dst_data[3].texture.x = 0.0f;
376     dst_data[3].texture.y = 0.0f;
377
378     /* Make sure surfaces are facing the right way */
379     if( orientation == ORIENT_TOP_RIGHT || orientation == ORIENT_BOTTOM_LEFT
380      || orientation == ORIENT_LEFT_TOP  || orientation == ORIENT_RIGHT_BOTTOM )
381     {
382         triangle_pos[0] = 0;
383         triangle_pos[1] = 1;
384         triangle_pos[2] = 3;
385
386         triangle_pos[3] = 3;
387         triangle_pos[4] = 1;
388         triangle_pos[5] = 2;
389     }
390     else
391     {
392         triangle_pos[0] = 3;
393         triangle_pos[1] = 1;
394         triangle_pos[2] = 0;
395
396         triangle_pos[3] = 2;
397         triangle_pos[4] = 1;
398         triangle_pos[5] = 3;
399     }
400 }
401
402 static void SetupQuadSphere(d3d_vertex_t *dst_data, const RECT *output,
403                             const d3d_quad_t *quad, WORD *triangle_pos)
404 {
405     const float scaleX = (float)(output->right  - output->left) / quad->i_width;
406     const float scaleY = (float)(output->bottom - output->top)   / quad->i_height;
407     for (unsigned lat = 0; lat <= nbLatBands; lat++) {
408         float theta = lat * (float) M_PI / nbLatBands;
409         float sinTheta, cosTheta;
410
411         sincosf(theta, &sinTheta, &cosTheta);
412
413         for (unsigned lon = 0; lon <= nbLonBands; lon++) {
414             float phi = lon * 2 * (float) M_PI / nbLonBands;
415             float sinPhi, cosPhi;
416
417             sincosf(phi, &sinPhi, &cosPhi);
418
419             float x = cosPhi * sinTheta;
420             float y = cosTheta;
421             float z = sinPhi * sinTheta;
422
423             unsigned off1 = lat * (nbLonBands + 1) + lon;
424             dst_data[off1].position.x = SPHERE_RADIUS * x;
425             dst_data[off1].position.y = SPHERE_RADIUS * y;
426             dst_data[off1].position.z = SPHERE_RADIUS * z;
427
428             dst_data[off1].texture.x = scaleX * lon / (float) nbLonBands; // 0(left) to 1(right)
429             dst_data[off1].texture.y = scaleY * lat / (float) nbLatBands; // 0(top) to 1 (bottom)
430         }
431     }
432
433     for (unsigned lat = 0; lat < nbLatBands; lat++) {
434         for (unsigned lon = 0; lon < nbLonBands; lon++) {
435             unsigned first = (lat * (nbLonBands + 1)) + lon;
436             unsigned second = first + nbLonBands + 1;
437
438             unsigned off = (lat * nbLatBands + lon) * 3 * 2;
439
440             triangle_pos[off] = first;
441             triangle_pos[off + 1] = first + 1;
442             triangle_pos[off + 2] = second;
443
444             triangle_pos[off + 3] = second;
445             triangle_pos[off + 4] = first + 1;
446             triangle_pos[off + 5] = second + 1;
447         }
448     }
449 }
450
451
452 static void SetupQuadCube(d3d_vertex_t *dst_data, const RECT *output,
453                           const d3d_quad_t *quad, WORD *triangle_pos)
454 {
455     static const float coord[] = {
456         -1.0,    1.0,    -1.0f, // front
457         -1.0,    -1.0,   -1.0f,
458         1.0,     1.0,    -1.0f,
459         1.0,     -1.0,   -1.0f,
460
461         -1.0,    1.0,    1.0f, // back
462         -1.0,    -1.0,   1.0f,
463         1.0,     1.0,    1.0f,
464         1.0,     -1.0,   1.0f,
465
466         -1.0,    1.0,    -1.0f, // left
467         -1.0,    -1.0,   -1.0f,
468         -1.0,     1.0,    1.0f,
469         -1.0,     -1.0,   1.0f,
470
471         1.0f,    1.0,    -1.0f, // right
472         1.0f,   -1.0,    -1.0f,
473         1.0f,   1.0,     1.0f,
474         1.0f,   -1.0,    1.0f,
475
476         -1.0,    -1.0,    1.0f, // bottom
477         -1.0,    -1.0,   -1.0f,
478         1.0,     -1.0,    1.0f,
479         1.0,     -1.0,   -1.0f,
480
481         -1.0,    1.0,    1.0f, // top
482         -1.0,    1.0,   -1.0f,
483         1.0,     1.0,    1.0f,
484         1.0,     1.0,   -1.0f,
485     };
486
487     const float scaleX = (float)(output->right - output->left) / quad->i_width;
488     const float scaleY = (float)(output->bottom - output->top) / quad->i_height;
489
490     const float col[] = {0.f, scaleX / 3, scaleX * 2 / 3, scaleX};
491     const float row[] = {0.f, scaleY / 2, scaleY};
492
493     const float tex[] = {
494         col[1], row[1], // front
495         col[1], row[2],
496         col[2], row[1],
497         col[2], row[2],
498
499         col[3], row[1], // back
500         col[3], row[2],
501         col[2], row[1],
502         col[2], row[2],
503
504         col[2], row[0], // left
505         col[2], row[1],
506         col[1], row[0],
507         col[1], row[1],
508
509         col[0], row[0], // right
510         col[0], row[1],
511         col[1], row[0],
512         col[1], row[1],
513
514         col[0], row[2], // bottom
515         col[0], row[1],
516         col[1], row[2],
517         col[1], row[1],
518
519         col[2], row[0], // top
520         col[2], row[1],
521         col[3], row[0],
522         col[3], row[1],
523     };
524
525     const unsigned i_nbVertices = ARRAY_SIZE(coord) / 3;
526
527     for (unsigned v = 0; v < i_nbVertices; ++v)
528     {
529         dst_data[v].position.x = coord[3 * v];
530         dst_data[v].position.y = coord[3 * v + 1];
531         dst_data[v].position.z = coord[3 * v + 2];
532
533         dst_data[v].texture.x = tex[2 * v];
534         dst_data[v].texture.y = tex[2 * v + 1];
535     }
536
537     const WORD ind[] = {
538         2, 1, 0,       3, 1, 2, // front
539         4, 7, 6,       5, 7, 4, // back
540         8, 11, 10,     9, 11, 8, // left
541         14, 13, 12,    15, 13, 14, // right
542         16, 19, 18,    17, 19, 16, // bottom
543         22, 21, 20,    23, 21, 22, // top
544     };
545
546     memcpy(triangle_pos, ind, sizeof(ind));
547 }
548
549 #undef D3D11_UpdateQuadPosition
550 bool D3D11_UpdateQuadPosition( vlc_object_t *o, d3d11_device_t *d3d_dev, d3d_quad_t *quad,
551                                 const RECT *output, video_orientation_t orientation )
552 {
553     bool result = true;
554     HRESULT hr;
555     D3D11_MAPPED_SUBRESOURCE mappedResource;
556
557     if (unlikely(quad->pVertexBuffer == NULL))
558         return false;
559
560     /* create the vertices */
561     hr = ID3D11DeviceContext_Map(d3d_dev->d3dcontext, (ID3D11Resource *)quad->pVertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
562     if (FAILED(hr)) {
563         msg_Err(o, "Failed to lock the vertex buffer (hr=0x%lX)", hr);
564         return false;
565     }
566     d3d_vertex_t *dst_data = mappedResource.pData;
567
568     /* create the vertex indices */
569     hr = ID3D11DeviceContext_Map(d3d_dev->d3dcontext, (ID3D11Resource *)quad->pIndexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
570     if (FAILED(hr)) {
571         msg_Err(o, "Failed to lock the index buffer (hr=0x%lX)", hr);
572         ID3D11DeviceContext_Unmap(d3d_dev->d3dcontext, (ID3D11Resource *)quad->pVertexBuffer, 0);
573         return false;
574     }
575     WORD *triangle_pos = mappedResource.pData;
576
577     switch (quad->projection)
578     {
579     case PROJECTION_MODE_RECTANGULAR:
580         SetupQuadFlat(dst_data, output, quad, triangle_pos, orientation);
581         break;
582     case PROJECTION_MODE_EQUIRECTANGULAR:
583         SetupQuadSphere(dst_data, output, quad, triangle_pos);
584         break;
585     case PROJECTION_MODE_CUBEMAP_LAYOUT_STANDARD:
586         SetupQuadCube(dst_data, output, quad, triangle_pos);
587         break;
588     default:
589         msg_Warn(o, "Projection mode %d not handled", quad->projection);
590         result = false;
591     }
592
593     ID3D11DeviceContext_Unmap(d3d_dev->d3dcontext, (ID3D11Resource *)quad->pIndexBuffer, 0);
594     ID3D11DeviceContext_Unmap(d3d_dev->d3dcontext, (ID3D11Resource *)quad->pVertexBuffer, 0);
595
596     return result;
597 }
598
599 static bool ShaderUpdateConstants(vlc_object_t *o, d3d11_device_t *d3d_dev, d3d_quad_t *quad, size_t index, void *new_buf)
600 {
601     D3D11_MAPPED_SUBRESOURCE mappedResource;
602     HRESULT hr = ID3D11DeviceContext_Map(d3d_dev->d3dcontext, (ID3D11Resource *)quad->pPixelShaderConstants[index], 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
603     if (FAILED(hr))
604     {
605         msg_Err(o, "Failed to lock the picture shader constants (hr=0x%lX)", hr);
606         return false;
607     }
608
609     if (index == PS_CONST_LUMI_BOUNDS)
610         memcpy(mappedResource.pData, new_buf,sizeof(PS_CONSTANT_BUFFER));
611     else
612         memcpy(mappedResource.pData, new_buf,sizeof(PS_COLOR_TRANSFORM));
613     ID3D11DeviceContext_Unmap(d3d_dev->d3dcontext, (ID3D11Resource *)quad->pPixelShaderConstants[index], 0);
614     return true;
615 }
616
617 #undef D3D11_UpdateQuadOpacity
618 void D3D11_UpdateQuadOpacity(vlc_object_t *o, d3d11_device_t *d3d_dev, d3d_quad_t *quad, float opacity)
619 {
620     if (quad->shaderConstants.Opacity == opacity)
621         return;
622
623     float old = quad->shaderConstants.Opacity;
624     quad->shaderConstants.Opacity = opacity;
625     if (!ShaderUpdateConstants(o, d3d_dev, quad, PS_CONST_LUMI_BOUNDS, &quad->shaderConstants))
626         quad->shaderConstants.Opacity = old;
627 }
628
629 #undef D3D11_UpdateQuadLuminanceScale
630 void D3D11_UpdateQuadLuminanceScale(vlc_object_t *o, d3d11_device_t *d3d_dev, d3d_quad_t *quad, float luminanceScale)
631 {
632     if (quad->shaderConstants.LuminanceScale == luminanceScale)
633         return;
634
635     float old = quad->shaderConstants.LuminanceScale;
636     quad->shaderConstants.LuminanceScale = luminanceScale;
637     if (!ShaderUpdateConstants(o, d3d_dev, quad, PS_CONST_LUMI_BOUNDS, &quad->shaderConstants))
638         quad->shaderConstants.LuminanceScale = old;
639 }
640
641 #undef D3D11_AllocateQuad
642 int D3D11_AllocateQuad(vlc_object_t *o, d3d11_device_t *d3d_dev,
643                        video_projection_mode_t projection, d3d_quad_t *quad)
644 {
645     HRESULT hr;
646     static_assert((sizeof(PS_CONSTANT_BUFFER)%16)==0,"Constant buffers require 16-byte alignment");
647     D3D11_BUFFER_DESC constantDesc = {
648         .Usage = D3D11_USAGE_DYNAMIC,
649         .ByteWidth = sizeof(PS_CONSTANT_BUFFER),
650         .BindFlags = D3D11_BIND_CONSTANT_BUFFER,
651         .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE,
652     };
653     hr = ID3D11Device_CreateBuffer(d3d_dev->d3ddevice, &constantDesc, NULL, &quad->pPixelShaderConstants[PS_CONST_LUMI_BOUNDS]);
654     if(FAILED(hr)) {
655         msg_Err(o, "Could not create the pixel shader constant buffer. (hr=0x%lX)", hr);
656         goto error;
657     }
658
659     static_assert((sizeof(PS_COLOR_TRANSFORM)%16)==0,"Constant buffers require 16-byte alignment");
660     constantDesc.ByteWidth = sizeof(PS_COLOR_TRANSFORM);
661     hr = ID3D11Device_CreateBuffer(d3d_dev->d3ddevice, &constantDesc, NULL, &quad->pPixelShaderConstants[PS_CONST_COLORSPACE]);
662     if(FAILED(hr)) {
663         msg_Err(o, "Could not create the pixel shader colorspace buffer. (hr=0x%lX)", hr);
664         goto error;
665     }
666
667     if (projection == PROJECTION_MODE_EQUIRECTANGULAR || projection == PROJECTION_MODE_CUBEMAP_LAYOUT_STANDARD)
668     {
669         static_assert((sizeof(VS_PROJECTION_CONST)%16)==0,"Constant buffers require 16-byte alignment");
670         constantDesc.ByteWidth = sizeof(VS_PROJECTION_CONST);
671         hr = ID3D11Device_CreateBuffer(d3d_dev->d3ddevice, &constantDesc, NULL, &quad->pVertexShaderConstants);
672         if(FAILED(hr)) {
673             msg_Err(o, "Could not create the vertex shader constant buffer. (hr=0x%lX)", hr);
674             goto error;
675         }
676     }
677
678     if (!AllocQuadVertices(o, d3d_dev, quad, projection))
679         goto error;
680     quad->projection = projection;
681
682     return VLC_SUCCESS;
683
684 error:
685     D3D11_ReleaseQuad(quad);
686     return VLC_EGENERIC;
687 }
688
689 #undef D3D11_SetupQuad
690 int D3D11_SetupQuad(vlc_object_t *o, d3d11_device_t *d3d_dev, const video_format_t *fmt, d3d_quad_t *quad,
691                     const display_info_t *displayFormat, const RECT *output,
692                     video_orientation_t orientation)
693 {
694     const bool RGB_shader = IsRGBShader(quad->textureFormat);
695
696     quad->shaderConstants.LuminanceScale = GetFormatLuminance(o, fmt) / (float)displayFormat->luminance_peak;
697
698     /* pixel shader constant buffer */
699     quad->shaderConstants.Opacity = 1.0;
700     if (fmt->i_visible_width == fmt->i_width)
701         quad->shaderConstants.BoundaryX = 1.0; /* let texture clamping happen */
702     else
703         quad->shaderConstants.BoundaryX = (FLOAT) (fmt->i_visible_width - 1) / fmt->i_width;
704     if (fmt->i_visible_height == fmt->i_height)
705         quad->shaderConstants.BoundaryY = 1.0; /* let texture clamping happen */
706     else
707         quad->shaderConstants.BoundaryY = (FLOAT) (fmt->i_visible_height - 1) / fmt->i_height;
708
709     ShaderUpdateConstants(o, d3d_dev, quad, PS_CONST_LUMI_BOUNDS, &quad->shaderConstants);
710
711     FLOAT itu_black_level = 0.f;
712     FLOAT itu_achromacy   = 0.f;
713     if (!RGB_shader)
714     {
715         switch (quad->textureFormat->bitsPerChannel)
716         {
717         case 8:
718             /* Rec. ITU-R BT.709-6 ¶4.6 */
719             itu_black_level  =              16.f / 255.f;
720             itu_achromacy    =             128.f / 255.f;
721             break;
722         case 10:
723             /* Rec. ITU-R BT.709-6 ¶4.6 */
724             itu_black_level  =              64.f / 1023.f;
725             itu_achromacy    =             512.f / 1023.f;
726             break;
727         case 12:
728             /* Rec. ITU-R BT.2020-2 Table 5 */
729             itu_black_level  =               256.f / 4095.f;
730             itu_achromacy    =              2048.f / 4095.f;
731             break;
732         default:
733             /* unknown bitdepth, use approximation for infinite bit depth */
734             itu_black_level  =              16.f / 256.f;
735             itu_achromacy    =             128.f / 256.f;
736             break;
737         }
738     }
739
740     static const FLOAT IDENTITY_4X4[4 * 4] = {
741         1.f, 0.f, 0.f, 0.f,
742         0.f, 1.f, 0.f, 0.f,
743         0.f, 0.f, 1.f, 0.f,
744         0.f, 0.f, 0.f, 1.f,
745     };
746
747     /* matrices for studio range */
748     /* see https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion, in studio range */
749     static const FLOAT COLORSPACE_BT601_YUV_TO_FULL_RGBA[4*4] = {
750         1.164383561643836f,                 0.f,  1.596026785714286f, 0.f,
751         1.164383561643836f, -0.391762290094914f, -0.812967647237771f, 0.f,
752         1.164383561643836f,  2.017232142857142f,                 0.f, 0.f,
753                        0.f,                 0.f,                 0.f, 1.f,
754     };
755
756     static const FLOAT COLORSPACE_FULL_RGBA_TO_BT601_YUV[4*4] = {
757         0.299000f,  0.587000f,  0.114000f, 0.f,
758        -0.168736f, -0.331264f,  0.500000f, 0.f,
759         0.500000f, -0.418688f, -0.081312f, 0.f,
760               0.f,        0.f,        0.f, 1.f,
761     };
762
763     /* see https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.709_conversion, in studio range */
764     static const FLOAT COLORSPACE_BT709_YUV_TO_FULL_RGBA[4*4] = {
765         1.164383561643836f,                 0.f,  1.792741071428571f, 0.f,
766         1.164383561643836f, -0.213248614273730f, -0.532909328559444f, 0.f,
767         1.164383561643836f,  2.112401785714286f,                 0.f, 0.f,
768                        0.f,                 0.f,                 0.f, 1.f,
769     };
770     /* see https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.2020_conversion, in studio range */
771     static const FLOAT COLORSPACE_BT2020_YUV_TO_FULL_RGBA[4*4] = {
772         1.164383561643836f,  0.000000000000f,  1.678674107143f, 0.f,
773         1.164383561643836f, -0.127007098661f, -0.440987687946f, 0.f,
774         1.164383561643836f,  2.141772321429f,  0.000000000000f, 0.f,
775                        0.f,              0.f,              0.f, 1.f,
776     };
777
778     PS_COLOR_TRANSFORM colorspace;
779
780     memcpy(colorspace.WhitePoint, IDENTITY_4X4, sizeof(colorspace.WhitePoint));
781
782     const FLOAT *ppColorspace;
783     if (!IsRGBShader(displayFormat->pixelFormat))
784     {
785         if (!RGB_shader)
786             ppColorspace = IDENTITY_4X4;
787         else
788         {
789             ppColorspace = COLORSPACE_FULL_RGBA_TO_BT601_YUV;
790             colorspace.WhitePoint[0*4 + 3] = -itu_black_level;
791             colorspace.WhitePoint[1*4 + 3] = itu_achromacy;
792             colorspace.WhitePoint[2*4 + 3] = itu_achromacy;
793         }
794     }
795     else
796     {
797         if (RGB_shader)
798             ppColorspace = IDENTITY_4X4;
799         else {
800             switch (fmt->space){
801                 case COLOR_SPACE_BT709:
802                     ppColorspace = COLORSPACE_BT709_YUV_TO_FULL_RGBA;
803                     break;
804                 case COLOR_SPACE_BT2020:
805                     ppColorspace = COLORSPACE_BT2020_YUV_TO_FULL_RGBA;
806                     break;
807                 case COLOR_SPACE_BT601:
808                     ppColorspace = COLORSPACE_BT601_YUV_TO_FULL_RGBA;
809                     break;
810                 default:
811                 case COLOR_SPACE_UNDEF:
812                     if( fmt->i_height > 576 )
813                         ppColorspace = COLORSPACE_BT709_YUV_TO_FULL_RGBA;
814                     else
815                         ppColorspace = COLORSPACE_BT601_YUV_TO_FULL_RGBA;
816                     break;
817             }
818             /* all matrices work in studio range and output in full range */
819             colorspace.WhitePoint[0*4 + 3] = -itu_black_level;
820             colorspace.WhitePoint[1*4 + 3] = -itu_achromacy;
821             colorspace.WhitePoint[2*4 + 3] = -itu_achromacy;
822         }
823     }
824
825     memcpy(colorspace.Colorspace, ppColorspace, sizeof(colorspace.Colorspace));
826
827     ShaderUpdateConstants(o, d3d_dev, quad, PS_CONST_COLORSPACE, &colorspace);
828
829
830     quad->picSys.formatTexture = quad->textureFormat->formatTexture;
831     quad->picSys.context = d3d_dev->d3dcontext;
832     ID3D11DeviceContext_AddRef(quad->picSys.context);
833
834     if (!D3D11_UpdateQuadPosition(o, d3d_dev, quad, output, orientation))
835         return VLC_EGENERIC;
836
837     for (size_t i=0; i<D3D11_MAX_SHADER_VIEW; i++)
838     {
839         quad->cropViewport[i].MinDepth = 0.0f;
840         quad->cropViewport[i].MaxDepth = 1.0f;
841     }
842     quad->resourceCount = DxgiResourceCount(quad->textureFormat);
843
844     return VLC_SUCCESS;
845 }
846
847 void D3D11_UpdateViewport(d3d_quad_t *quad, const RECT *rect, const d3d_format_t *display)
848 {
849 #define RECTWidth(r)   (LONG)((r)->right - (r)->left)
850 #define RECTHeight(r)  (LONG)((r)->bottom - (r)->top)
851     LONG srcAreaWidth, srcAreaHeight;
852
853     srcAreaWidth  = RECTWidth(rect);
854     srcAreaHeight = RECTHeight(rect);
855 #undef RECTWidth
856 #undef RECTHeight
857
858     quad->cropViewport[0].TopLeftX = rect->left;
859     quad->cropViewport[0].TopLeftY = rect->top;
860     quad->cropViewport[0].Width    = srcAreaWidth;
861     quad->cropViewport[0].Height   = srcAreaHeight;
862
863     switch ( quad->textureFormat->formatTexture )
864     {
865     case DXGI_FORMAT_NV12:
866     case DXGI_FORMAT_P010:
867         quad->cropViewport[1].TopLeftX = rect->left / 2;
868         quad->cropViewport[1].TopLeftY = rect->top / 2;
869         quad->cropViewport[1].Width    = srcAreaWidth / 2;
870         quad->cropViewport[1].Height   = srcAreaHeight / 2;
871         break;
872     case DXGI_FORMAT_R8G8B8A8_UNORM:
873     case DXGI_FORMAT_B8G8R8A8_UNORM:
874     case DXGI_FORMAT_B8G8R8X8_UNORM:
875     case DXGI_FORMAT_B5G6R5_UNORM:
876     case DXGI_FORMAT_R10G10B10A2_UNORM:
877     case DXGI_FORMAT_R16G16B16A16_UNORM:
878     case DXGI_FORMAT_YUY2:
879     case DXGI_FORMAT_AYUV:
880         if ( display->formatTexture == DXGI_FORMAT_NV12 ||
881              display->formatTexture == DXGI_FORMAT_P010 )
882         {
883             quad->cropViewport[1].TopLeftX = rect->left / 2;
884             quad->cropViewport[1].TopLeftY = rect->top / 2;
885             quad->cropViewport[1].Width    = srcAreaWidth / 2;
886             quad->cropViewport[1].Height   = srcAreaHeight / 2;
887         }
888         break;
889     case DXGI_FORMAT_UNKNOWN:
890         switch ( quad->textureFormat->fourcc )
891         {
892         case VLC_CODEC_YUVA:
893             if ( display->formatTexture != DXGI_FORMAT_NV12 &&
894                  display->formatTexture != DXGI_FORMAT_P010 )
895             {
896                 quad->cropViewport[1] = quad->cropViewport[2] =
897                 quad->cropViewport[3] = quad->cropViewport[0];
898                 break;
899             }
900             /* fallthrough */
901         case VLC_CODEC_I420:
902             quad->cropViewport[1].TopLeftX = quad->cropViewport[0].TopLeftX / 2;
903             quad->cropViewport[1].TopLeftY = quad->cropViewport[0].TopLeftY / 2;
904             quad->cropViewport[1].Width    = quad->cropViewport[0].Width / 2;
905             quad->cropViewport[1].Height   = quad->cropViewport[0].Height / 2;
906             quad->cropViewport[2] = quad->cropViewport[1];
907             break;
908         }
909         break;
910     default:
911         vlc_assert_unreachable();
912     }
913 }