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 }