| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the examples of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:BSD$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** BSD License Usage |
| ** Alternatively, you may use this file under the terms of the BSD license |
| ** as follows: |
| ** |
| ** "Redistribution and use in source and binary forms, with or without |
| ** modification, are permitted provided that the following conditions are |
| ** met: |
| ** * Redistributions of source code must retain the above copyright |
| ** notice, this list of conditions and the following disclaimer. |
| ** * Redistributions in binary form must reproduce the above copyright |
| ** notice, this list of conditions and the following disclaimer in |
| ** the documentation and/or other materials provided with the |
| ** distribution. |
| ** * Neither the name of The Qt Company Ltd nor the names of its |
| ** contributors may be used to endorse or promote products derived |
| ** from this software without specific prior written permission. |
| ** |
| ** |
| ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "d3d12renderer.h" |
| #include <QQuickItem> |
| #include <QQuickWindow> |
| #include <QSGRendererInterface> |
| #include <QFile> |
| |
| // ### Qt 6: remove |
| |
| #if QT_CONFIG(d3d12) |
| |
| D3D12RenderNode::~D3D12RenderNode() |
| { |
| releaseResources(); |
| } |
| |
| void D3D12RenderNode::releaseResources() |
| { |
| if (vbPtr) { |
| vertexBuffer->Unmap(0, nullptr); |
| vbPtr = nullptr; |
| } |
| if (cbPtr) { |
| constantBuffer->Unmap(0, nullptr); |
| cbPtr = nullptr; |
| } |
| constantBuffer = nullptr; |
| vertexBuffer = nullptr; |
| rootSignature = nullptr; |
| pipelineState = nullptr; |
| m_device = nullptr; |
| } |
| |
| void D3D12RenderNode::init() |
| { |
| QSGRendererInterface *rif = m_window->rendererInterface(); |
| m_device = static_cast<ID3D12Device *>(rif->getResource(m_window, QSGRendererInterface::DeviceResource)); |
| Q_ASSERT(m_device); |
| |
| D3D12_ROOT_PARAMETER rootParameter; |
| rootParameter.ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; |
| rootParameter.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; |
| rootParameter.Descriptor.ShaderRegister = 0; // b0 |
| rootParameter.Descriptor.RegisterSpace = 0; |
| |
| D3D12_ROOT_SIGNATURE_DESC desc; |
| desc.NumParameters = 1; |
| desc.pParameters = &rootParameter; |
| desc.NumStaticSamplers = 0; |
| desc.pStaticSamplers = nullptr; |
| desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; |
| |
| ComPtr<ID3DBlob> signature; |
| ComPtr<ID3DBlob> error; |
| if (FAILED(D3D12SerializeRootSignature(&desc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error))) { |
| qWarning("Failed to serialize root signature"); |
| return; |
| } |
| if (FAILED(m_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), |
| IID_PPV_ARGS(&rootSignature)))) { |
| qWarning("Failed to create root signature"); |
| return; |
| } |
| |
| D3D12_INPUT_ELEMENT_DESC inputElementDescs[] = { |
| { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, |
| { "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 8, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 } |
| }; |
| |
| QFile f(QStringLiteral(":/scenegraph/rendernode/shader_vert.cso")); |
| if (!f.open(QIODevice::ReadOnly)) { |
| qWarning("Failed to open file with vertex shader bytecode"); |
| return; |
| } |
| QByteArray vshader_cso = f.readAll(); |
| f.close(); |
| f.setFileName(QStringLiteral(":/scenegraph/rendernode/shader_frag.cso")); |
| if (!f.open(QIODevice::ReadOnly)) { |
| qWarning("Failed to open file with fragment shader bytecode"); |
| return; |
| } |
| QByteArray fshader_cso = f.readAll(); |
| D3D12_SHADER_BYTECODE vshader; |
| vshader.pShaderBytecode = vshader_cso.constData(); |
| vshader.BytecodeLength = vshader_cso.size(); |
| D3D12_SHADER_BYTECODE pshader; |
| pshader.pShaderBytecode = fshader_cso.constData(); |
| pshader.BytecodeLength = fshader_cso.size(); |
| |
| D3D12_RASTERIZER_DESC rastDesc = {}; |
| rastDesc.FillMode = D3D12_FILL_MODE_SOLID; |
| rastDesc.CullMode = D3D12_CULL_MODE_BACK; |
| rastDesc.FrontCounterClockwise = TRUE; // Vertices are given CCW |
| |
| // Enable color write and blending (premultiplied alpha). The latter is |
| // needed because the example changes the item's opacity and we pass |
| // inheritedOpacity() into the pixel shader. If that wasn't the case, |
| // blending could have stayed disabled. |
| const D3D12_RENDER_TARGET_BLEND_DESC premulBlendDesc = { |
| TRUE, FALSE, |
| D3D12_BLEND_ONE, D3D12_BLEND_INV_SRC_ALPHA, D3D12_BLEND_OP_ADD, |
| D3D12_BLEND_ONE, D3D12_BLEND_INV_SRC_ALPHA, D3D12_BLEND_OP_ADD, |
| D3D12_LOGIC_OP_NOOP, |
| D3D12_COLOR_WRITE_ENABLE_ALL |
| }; |
| D3D12_BLEND_DESC blendDesc = {}; |
| blendDesc.RenderTarget[0] = premulBlendDesc; |
| |
| D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {}; |
| psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) }; |
| psoDesc.pRootSignature = rootSignature.Get(); |
| psoDesc.VS = vshader; |
| psoDesc.PS = pshader; |
| psoDesc.RasterizerState = rastDesc; |
| psoDesc.BlendState = blendDesc; |
| // No depth. The correct stacking of the item is ensured by the projection matrix. |
| // Note that this does not support clipping. |
| // If clipping is desired, render() needs to set a different PSO |
| // with stencil enabled whenever the RenderState indicates so. |
| psoDesc.SampleMask = UINT_MAX; |
| psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; |
| psoDesc.NumRenderTargets = 1; |
| psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; |
| psoDesc.DSVFormat = DXGI_FORMAT_D24_UNORM_S8_UINT; // not in use due to !DepthEnable, but this would be the correct format otherwise |
| // We are rendering on the default render target so if the QuickWindow/View |
| // has requested samples > 0 then we have to follow suit. |
| const uint samples = qMax(1, m_window->format().samples()); |
| psoDesc.SampleDesc.Count = samples; |
| if (samples > 1) { |
| D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msaaInfo = {}; |
| msaaInfo.Format = psoDesc.RTVFormats[0]; |
| msaaInfo.SampleCount = samples; |
| if (SUCCEEDED(m_device->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &msaaInfo, sizeof(msaaInfo)))) { |
| if (msaaInfo.NumQualityLevels > 0) |
| psoDesc.SampleDesc.Quality = msaaInfo.NumQualityLevels - 1; |
| } |
| } |
| |
| if (FAILED(m_device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pipelineState)))) { |
| qWarning("Failed to create graphics pipeline state"); |
| return; |
| } |
| |
| const UINT vertexBufferSize = (2 + 3) * 3 * sizeof(float); |
| |
| D3D12_HEAP_PROPERTIES heapProp = {}; |
| heapProp.Type = D3D12_HEAP_TYPE_UPLOAD; |
| |
| D3D12_RESOURCE_DESC bufDesc; |
| bufDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; |
| bufDesc.Alignment = 0; |
| bufDesc.Width = vertexBufferSize; |
| bufDesc.Height = 1; |
| bufDesc.DepthOrArraySize = 1; |
| bufDesc.MipLevels = 1; |
| bufDesc.Format = DXGI_FORMAT_UNKNOWN; |
| bufDesc.SampleDesc.Count = 1; |
| bufDesc.SampleDesc.Quality = 0; |
| bufDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; |
| bufDesc.Flags = D3D12_RESOURCE_FLAG_NONE; |
| |
| if (FAILED(m_device->CreateCommittedResource(&heapProp, D3D12_HEAP_FLAG_NONE, &bufDesc, |
| D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, |
| IID_PPV_ARGS(&vertexBuffer)))) { |
| qWarning("Failed to create committed resource (vertex buffer)"); |
| return; |
| } |
| |
| vertexBufferView.BufferLocation = vertexBuffer->GetGPUVirtualAddress(); |
| vertexBufferView.StrideInBytes = vertexBufferSize / 3; |
| vertexBufferView.SizeInBytes = vertexBufferSize; |
| |
| bufDesc.Width = 256; |
| if (FAILED(m_device->CreateCommittedResource(&heapProp, D3D12_HEAP_FLAG_NONE, &bufDesc, |
| D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, |
| IID_PPV_ARGS(&constantBuffer)))) { |
| qWarning("Failed to create committed resource (constant buffer)"); |
| return; |
| } |
| |
| const D3D12_RANGE readRange = { 0, 0 }; |
| if (FAILED(vertexBuffer->Map(0, &readRange, reinterpret_cast<void **>(&vbPtr)))) { |
| qWarning("Map failed"); |
| return; |
| } |
| |
| if (FAILED(constantBuffer->Map(0, &readRange, reinterpret_cast<void **>(&cbPtr)))) { |
| qWarning("Map failed (constant buffer)"); |
| return; |
| } |
| |
| float *vp = reinterpret_cast<float *>(vbPtr); |
| vp += 2; |
| *vp++ = 1.0f; *vp++ = 0.0f; *vp++ = 0.0f; |
| vp += 2; |
| *vp++ = 0.0f; *vp++ = 1.0f; *vp++ = 0.0f; |
| vp += 2; |
| *vp++ = 0.0f; *vp++ = 0.0f; *vp++ = 1.0f; |
| } |
| |
| void D3D12RenderNode::render(const RenderState *state) |
| { |
| if (!m_device) |
| init(); |
| |
| QSGRendererInterface *rif = m_window->rendererInterface(); |
| ID3D12GraphicsCommandList *commandList = static_cast<ID3D12GraphicsCommandList *>( |
| rif->getResource(m_window, QSGRendererInterface::CommandListResource)); |
| Q_ASSERT(commandList); |
| |
| const int msize = 16 * sizeof(float); |
| memcpy(cbPtr, matrix()->constData(), msize); |
| memcpy(cbPtr + msize, state->projectionMatrix()->constData(), msize); |
| const float opacity = inheritedOpacity(); |
| memcpy(cbPtr + 2 * msize, &opacity, sizeof(float)); |
| |
| const QPointF p0(m_width - 1, m_height - 1); |
| const QPointF p1(0, 0); |
| const QPointF p2(0, m_height - 1); |
| |
| float *vp = reinterpret_cast<float *>(vbPtr); |
| *vp++ = p0.x(); |
| *vp++ = p0.y(); |
| vp += 3; |
| *vp++ = p1.x(); |
| *vp++ = p1.y(); |
| vp += 3; |
| *vp++ = p2.x(); |
| *vp++ = p2.y(); |
| |
| commandList->SetPipelineState(pipelineState.Get()); |
| commandList->SetGraphicsRootSignature(rootSignature.Get()); |
| commandList->SetGraphicsRootConstantBufferView(0, constantBuffer->GetGPUVirtualAddress()); |
| commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); |
| commandList->IASetVertexBuffers(0, 1, &vertexBufferView); |
| |
| commandList->DrawInstanced(3, 1, 0, 0); |
| } |
| |
| // No need to reimplement changedStates() because no relevant commands are |
| // added to the command list in render(). |
| |
| QSGRenderNode::RenderingFlags D3D12RenderNode::flags() const |
| { |
| return BoundedRectRendering | DepthAwareRendering; |
| } |
| |
| QRectF D3D12RenderNode::rect() const |
| { |
| return QRect(0, 0, m_width, m_height); |
| } |
| |
| void D3D12RenderNode::sync(QQuickItem *item) |
| { |
| m_window = item->window(); |
| m_width = item->width(); |
| m_height = item->height(); |
| } |
| |
| #endif // d3d12 |