File indexing completed on 2024-05-05 04:53:35

0001 /*
0002     SPDX-FileCopyrightText: 2023 Meltytech, LLC
0003     SPDX-License-Identifier: GPL-3.0-or-later
0004 */
0005 
0006 #include "d3dvideowidget.h"
0007 #include "core.h"
0008 #include "profiles/profilemodel.hpp"
0009 
0010 #include <d3dcompiler.h>
0011 
0012 D3DVideoWidget::D3DVideoWidget(int id, QObject *parent)
0013     : VideoWidget{id, parent}
0014 {
0015     m_maxTextureSize = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
0016     ::memset(&m_constants, 0, sizeof(m_constants));
0017 }
0018 
0019 D3DVideoWidget::~D3DVideoWidget()
0020 {
0021     for (int i = 0; i < 3; i++) {
0022         if (m_texture[i]) m_texture[i]->Release();
0023     }
0024     if (m_vs) m_vs->Release();
0025     if (m_ps) m_ps->Release();
0026     if (m_vbuf) m_vbuf->Release();
0027     if (m_cbuf) m_cbuf->Release();
0028     if (m_inputLayout) m_inputLayout->Release();
0029     if (m_rastState) m_rastState->Release();
0030     if (m_dsState) m_dsState->Release();
0031 }
0032 
0033 void D3DVideoWidget::initialize()
0034 {
0035     m_initialized = true;
0036     QSGRendererInterface *rif = quickWindow()->rendererInterface();
0037 
0038     // We are not prepared for anything other than running with the RHI and its D3D11 backend.
0039     Q_ASSERT(rif->graphicsApi() == QSGRendererInterface::Direct3D11);
0040 
0041     m_device = reinterpret_cast<ID3D11Device *>(rif->getResource(quickWindow(), QSGRendererInterface::DeviceResource));
0042     Q_ASSERT(m_device);
0043     m_context = reinterpret_cast<ID3D11DeviceContext *>(rif->getResource(quickWindow(), QSGRendererInterface::DeviceContextResource));
0044     Q_ASSERT(m_context);
0045 
0046     if (m_vert.isEmpty()) prepareShader(VertexStage);
0047     if (m_frag.isEmpty()) prepareShader(FragmentStage);
0048 
0049     const QByteArray vs = compileShader(VertexStage, m_vert, m_vertEntryPoint);
0050     const QByteArray fs = compileShader(FragmentStage, m_frag, m_fragEntryPoint);
0051 
0052     HRESULT hr = m_device->CreateVertexShader(vs.constData(), vs.size(), nullptr, &m_vs);
0053     if (FAILED(hr)) qFatal("Failed to create vertex shader: 0x%x", uint(hr));
0054 
0055     hr = m_device->CreatePixelShader(fs.constData(), fs.size(), nullptr, &m_ps);
0056     if (FAILED(hr)) qFatal("Failed to create pixel shader: 0x%x", uint(hr));
0057 
0058     D3D11_BUFFER_DESC bufDesc;
0059     memset(&bufDesc, 0, sizeof(bufDesc));
0060     bufDesc.ByteWidth = sizeof(float) * 16;
0061     bufDesc.Usage = D3D11_USAGE_DEFAULT;
0062     bufDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
0063     hr = m_device->CreateBuffer(&bufDesc, nullptr, &m_vbuf);
0064     if (FAILED(hr)) qFatal("Failed to create buffer: 0x%x", uint(hr));
0065 
0066     bufDesc.ByteWidth = sizeof(m_constants) + 0xf & 0xfffffff0; // must be a multiple of 16
0067     bufDesc.Usage = D3D11_USAGE_DYNAMIC;
0068     bufDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
0069     bufDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
0070     hr = m_device->CreateBuffer(&bufDesc, nullptr, &m_cbuf);
0071     if (FAILED(hr)) qFatal("Failed to create buffer: 0x%x", uint(hr));
0072 
0073     const D3D11_INPUT_ELEMENT_DESC inputDesc[] = {
0074         {"VERTEX", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
0075         {"TEXCOORD", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, sizeof(DirectX::XMFLOAT2), D3D11_INPUT_PER_VERTEX_DATA, 0},
0076     };
0077     hr = m_device->CreateInputLayout(inputDesc, ARRAYSIZE(inputDesc), vs.constData(), vs.size(), &m_inputLayout);
0078     if (FAILED(hr)) qFatal("Failed to create input layout: 0x%x", uint(hr));
0079 
0080     D3D11_RASTERIZER_DESC rastDesc;
0081     memset(&rastDesc, 0, sizeof(rastDesc));
0082     rastDesc.FillMode = D3D11_FILL_SOLID;
0083     rastDesc.CullMode = D3D11_CULL_NONE;
0084     hr = m_device->CreateRasterizerState(&rastDesc, &m_rastState);
0085     if (FAILED(hr)) qFatal("Failed to create rasterizer state: 0x%x", uint(hr));
0086 
0087     D3D11_DEPTH_STENCIL_DESC dsDesc;
0088     memset(&dsDesc, 0, sizeof(dsDesc));
0089     hr = m_device->CreateDepthStencilState(&dsDesc, &m_dsState);
0090     if (FAILED(hr)) qFatal("Failed to create depth/stencil state: 0x%x", uint(hr));
0091 
0092     VideoWidget::initialize();
0093 }
0094 
0095 void D3DVideoWidget::beforeRendering()
0096 {
0097     quickWindow()->beginExternalCommands();
0098     m_context->ClearState();
0099 
0100     // Provide vertices of triangle strip
0101     float width = rect().width() * devicePixelRatioF() / 2.0f;
0102     float height = rect().height() * devicePixelRatioF() / 2.0f;
0103     float vertexData[] = {
0104         // x,y plus u,v texture coordinates
0105         width,  -height, 1.f, 1.f, // bottom left
0106         -width, -height, 0.f, 1.f, // bottom right
0107         width,  height,  1.f, 0.f, // top left
0108         -width, height,  0.f, 0.f  // top right
0109     };
0110 
0111     // Setup an orthographic projection
0112     QMatrix4x4 modelView;
0113     width = this->width() * devicePixelRatioF();
0114     height = this->height() * devicePixelRatioF();
0115     modelView.scale(2.0f / width, 2.0f / height);
0116 
0117     // Set model-view
0118     if (rect().width() > 0.0 && zoom() > 0.0) {
0119         if (offset().x() || offset().y()) modelView.translate(-offset().x() * devicePixelRatioF(), offset().y() * devicePixelRatioF());
0120         modelView.scale(zoom(), zoom());
0121     }
0122     for (int i = 0; i < 4; i++) {
0123         vertexData[4 * i] *= modelView(0, 0);
0124         vertexData[4 * i] += modelView(0, 3);
0125         vertexData[4 * i + 1] *= modelView(1, 1);
0126         vertexData[4 * i + 1] += modelView(1, 3);
0127     }
0128     m_context->UpdateSubresource(m_vbuf, 0, nullptr, vertexData, 0, 0);
0129 
0130     // (Re)create the textures
0131     m_mutex.lock();
0132     if (!m_sharedFrame.is_valid()) {
0133         m_mutex.unlock();
0134         quickWindow()->endExternalCommands();
0135         VideoWidget::beforeRendering();
0136         return;
0137     }
0138     int iwidth = m_sharedFrame.get_image_width();
0139     int iheight = m_sharedFrame.get_image_height();
0140     const uint8_t *image = m_sharedFrame.get_image(mlt_image_yuv420p);
0141     for (int i = 0; i < 3; i++) {
0142         if (m_texture[i]) m_texture[i]->Release();
0143     }
0144     m_texture[0] = initTexture(image, iwidth, iheight);
0145     m_texture[1] = initTexture(image + iwidth * iheight, iwidth / 2, iheight / 2);
0146     m_texture[2] = initTexture(image + iwidth * iheight + iwidth / 2 * iheight / 2, iwidth / 2, iheight / 2);
0147     m_mutex.unlock();
0148 
0149     // Update the constants
0150     D3D11_MAPPED_SUBRESOURCE mp;
0151     // will copy the entire constant buffer every time -> pass WRITE_DISCARD -> prevent pipeline stalls
0152     HRESULT hr = m_context->Map(m_cbuf, 0, D3D11_MAP_WRITE_DISCARD, 0, &mp);
0153     if (SUCCEEDED(hr)) {
0154         m_constants.colorspace = pCore->getCurrentProfile()->colorspace();
0155         ::memcpy(mp.pData, &m_constants, sizeof(m_constants));
0156         m_context->Unmap(m_cbuf, 0);
0157     } else {
0158         quickWindow()->endExternalCommands();
0159         qFatal("Failed to map constant buffer: 0x%x", uint(hr));
0160         return;
0161     }
0162 
0163     quickWindow()->endExternalCommands();
0164     VideoWidget::beforeRendering();
0165 }
0166 
0167 void D3DVideoWidget::renderVideo()
0168 {
0169     if (!m_texture[0]) {
0170         VideoWidget::renderVideo();
0171         return;
0172     }
0173     quickWindow()->beginExternalCommands();
0174 
0175     D3D11_VIEWPORT v;
0176     v.TopLeftX = 0.f;
0177     v.TopLeftY = -qRound(m_displayRulerHeight * devicePixelRatioF() * 0.5);
0178     v.Width = this->width() * devicePixelRatioF();
0179     v.Height = this->height() * devicePixelRatioF();
0180     v.MinDepth = 0.f;
0181     v.MaxDepth = 1.f;
0182 
0183     m_context->RSSetViewports(1, &v);
0184     m_context->VSSetShader(m_vs, nullptr, 0);
0185     m_context->PSSetShader(m_ps, nullptr, 0);
0186     m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
0187     m_context->IASetInputLayout(m_inputLayout);
0188     m_context->OMSetDepthStencilState(m_dsState, 0);
0189     m_context->RSSetState(m_rastState);
0190     const UINT stride = sizeof(float) * 4;
0191     const UINT offset = 0;
0192     m_context->IASetVertexBuffers(0, 1, &m_vbuf, &stride, &offset);
0193     m_context->PSSetConstantBuffers(0, 1, &m_cbuf);
0194     m_context->PSSetShaderResources(0, 3, m_texture);
0195     m_context->Draw(4, 0);
0196 
0197     quickWindow()->endExternalCommands();
0198     VideoWidget::renderVideo();
0199 }
0200 
0201 void D3DVideoWidget::prepareShader(Stage stage)
0202 {
0203     if (stage == VertexStage) {
0204         m_vert = "struct VSInput {"
0205                  "  float2 vertex : VERTEX;"
0206                  "  float2 coords : TEXCOORD;"
0207                  "};"
0208                  "struct VSOutput {"
0209                  "  float2 coords : TEXCOORD0;"
0210                  "  float4 position : SV_Position;"
0211                  "};"
0212                  "VSOutput main(VSInput input) {"
0213                  "  VSOutput output;"
0214                  "  output.position = float4(input.vertex, 0.0f, 1.0f);"
0215                  "  output.coords = input.coords;"
0216                  "  return output;"
0217                  "}";
0218         Q_ASSERT(!m_vert.isEmpty());
0219         m_vertEntryPoint = QByteArrayLiteral("main");
0220     } else {
0221         m_frag = "Texture2D yTex, uTex, vTex;"
0222                  "SamplerState yuvSampler;"
0223                  "cbuffer buf {"
0224                  "    int colorspace;"
0225                  "};"
0226                  "struct PSInput {"
0227                  "  float2 coords : TEXCOORD0;"
0228                  "};"
0229                  "struct PSOutput {"
0230                  "  float4 color : SV_Target0;"
0231                  "};"
0232                  "PSOutput main(PSInput input) {"
0233                  "  float3 yuv;"
0234                  "  yuv.x = yTex.Sample(yuvSampler, input.coords).r -  16.0f/255.0f;"
0235                  "  yuv.y = uTex.Sample(yuvSampler, input.coords).r - 128.0f/255.0f;"
0236                  "  yuv.z = vTex.Sample(yuvSampler, input.coords).r - 128.0f/255.0f;"
0237                  "  float3x3 coefficients;"
0238                  "  if (colorspace == 601) {"
0239                  "    coefficients = float3x3("
0240                  "      1.1643f,  0.0f,      1.5958f,"
0241                  "      1.1643f, -0.39173f, -0.8129f,"
0242                  "      1.1643f,  2.017f,    0.0f);"
0243                  "  } else {" // ITU-R 709
0244                  "    coefficients = float3x3("
0245                  "      1.1643f,  0.0f,    1.793f,"
0246                  "      1.1643f, -0.213f, -0.533f,"
0247                  "      1.1643f,  2.112f,  0.0f);"
0248                  "  }"
0249                  "  PSOutput output;"
0250                  "  output.color = float4(mul(coefficients, yuv), 1.0f);"
0251                  "  return output;"
0252                  "}";
0253         m_fragEntryPoint = QByteArrayLiteral("main");
0254     }
0255 }
0256 
0257 QByteArray D3DVideoWidget::compileShader(Stage stage, const QByteArray &source, const QByteArray &entryPoint)
0258 {
0259     const char *target;
0260     switch (stage) {
0261     case VertexStage:
0262         target = "vs_5_0";
0263         break;
0264     case FragmentStage:
0265         target = "ps_5_0";
0266         break;
0267     default:
0268         qFatal("Unknown shader stage %d", stage);
0269         return QByteArray();
0270     }
0271 
0272     ID3DBlob *bytecode = nullptr;
0273     ID3DBlob *errors = nullptr;
0274     HRESULT hr = D3DCompile(source.constData(), source.size(), nullptr, nullptr, nullptr, entryPoint.constData(), target, 0, 0, &bytecode, &errors);
0275     if (FAILED(hr) || !bytecode) {
0276         qWarning("HLSL shader compilation failed: 0x%x", uint(hr));
0277         if (errors) {
0278             const QByteArray msg(static_cast<const char *>(errors->GetBufferPointer()), errors->GetBufferSize());
0279             errors->Release();
0280             qWarning("%s", msg.constData());
0281         }
0282         return QByteArray();
0283     }
0284 
0285     QByteArray result;
0286     result.resize(bytecode->GetBufferSize());
0287     memcpy(result.data(), bytecode->GetBufferPointer(), result.size());
0288     bytecode->Release();
0289 
0290     return result;
0291 }
0292 
0293 ID3D11ShaderResourceView *D3DVideoWidget::initTexture(const void *p, int width, int height)
0294 {
0295     ID3D11ShaderResourceView *result;
0296     D3D11_TEXTURE2D_DESC desc;
0297     desc.Width = width;
0298     desc.Height = height;
0299     desc.MipLevels = 1;
0300     desc.ArraySize = 1;
0301     desc.Format = DXGI_FORMAT_R8_UNORM;
0302     desc.SampleDesc.Count = 1;
0303     desc.SampleDesc.Quality = 0;
0304     desc.Usage = D3D11_USAGE_DEFAULT;
0305     desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
0306     desc.CPUAccessFlags = 0;
0307     desc.MiscFlags = 0;
0308 
0309     D3D11_SUBRESOURCE_DATA subresourceData;
0310     subresourceData.pSysMem = p;
0311     subresourceData.SysMemPitch = width;
0312     subresourceData.SysMemSlicePitch = 0;
0313 
0314     ID3D11Texture2D *texture;
0315     m_device->CreateTexture2D(&desc, &subresourceData, &texture);
0316 
0317     D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
0318     srvDesc.Format = desc.Format;
0319     srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
0320     srvDesc.Texture2D.MipLevels = 1;
0321     srvDesc.Texture2D.MostDetailedMip = 0;
0322 
0323     m_device->CreateShaderResourceView(texture, &srvDesc, &result);
0324     texture->Release();
0325 
0326     return result;
0327 }