diff options
author | Andrew Knight <andrew.knight@digia.com> | 2013-12-08 22:33:43 +0200 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2013-12-09 10:27:28 +0100 |
commit | be2efef7a41871299cfc4b31fcffc39f96a196b6 (patch) | |
tree | 3b4ee1fad820f3adb919fc66e0262141bcddc916 /src/angle | |
parent | 25b390256bf2a699231a165e49f4100262ed29ac (diff) |
Introducing d3dcompiler_qt
d3dcompiler_qt is a DLL for use with ANGLE which replaces d3dcompiler_XX
at runtime to proxy shader compilation calls. This is useful for:
- Loading the newest D3D compiler DLL found, instead of loading the
version specified when Qt was compiled
- Reporting better debug information when the compiler cannot be loaded
- Caching shader blobs for later use
- Returning cached shader blobs
- Deferring compilation to another mechanism, such as a D3D compilation
service running on a host debugging machine *
The above use cases are especially important for Windows Store apps, as
they are not allowed to ship the d3dcompiler. On Windows Phone, where
there is no runtime compiler, this is essential for handling QtQuick apps
which require runtime shader compilation.
* This requires a separate service which monitors a directory for shader
source files, compiles these files into D3D bytecode, and places
the bytecode in the qtd3dcompiler cache directory. This directory is
monitored by qtd3dcompiler, which is then able to then load the blob.
Change-Id: I9889c8d66d2ddbe5a7a1dc44bfe5d8ad229b0e43
Reviewed-by: Oliver Wolff <oliver.wolff@digia.com>
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@digia.com>
Diffstat (limited to 'src/angle')
-rw-r--r-- | src/angle/angle.pro | 1 | ||||
-rw-r--r-- | src/angle/d3dcompiler/d3dcompiler.pro | 11 | ||||
-rw-r--r-- | src/angle/d3dcompiler/d3dcompiler_qt.def | 3 | ||||
-rw-r--r-- | src/angle/d3dcompiler/d3dcompiler_qtd.def | 3 | ||||
-rw-r--r-- | src/angle/d3dcompiler/main.cpp | 295 |
5 files changed, 313 insertions, 0 deletions
diff --git a/src/angle/angle.pro b/src/angle/angle.pro index 83510698d3..a8040428d1 100644 --- a/src/angle/angle.pro +++ b/src/angle/angle.pro @@ -1,5 +1,6 @@ TEMPLATE = subdirs SUBDIRS += src +angle_d3d11: SUBDIRS += d3dcompiler # We do it this way instead of letting load(qt_module) handle it for two reasons: # 1) qt_module_headers assumes the TARGET is the same as the include directory (eg: libGLESv2 != GLES2) diff --git a/src/angle/d3dcompiler/d3dcompiler.pro b/src/angle/d3dcompiler/d3dcompiler.pro new file mode 100644 index 0000000000..35b9be2025 --- /dev/null +++ b/src/angle/d3dcompiler/d3dcompiler.pro @@ -0,0 +1,11 @@ +TEMPLATE = lib +TARGET = $$qtLibraryTarget(d3dcompiler_qt) +DLLDESTDIR = $$QT_BUILD_TREE/bin + +QT = core +DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII +SOURCES += main.cpp +win32:!winrt: LIBS += -lole32 + +# __stdcall exports get mangled, so use a def file +DEF_FILE += $${TARGET}.def diff --git a/src/angle/d3dcompiler/d3dcompiler_qt.def b/src/angle/d3dcompiler/d3dcompiler_qt.def new file mode 100644 index 0000000000..0b8679c8e0 --- /dev/null +++ b/src/angle/d3dcompiler/d3dcompiler_qt.def @@ -0,0 +1,3 @@ +LIBRARY d3dcompiler_qt +EXPORTS + D3DCompile @1 diff --git a/src/angle/d3dcompiler/d3dcompiler_qtd.def b/src/angle/d3dcompiler/d3dcompiler_qtd.def new file mode 100644 index 0000000000..0bdd0a1ffd --- /dev/null +++ b/src/angle/d3dcompiler/d3dcompiler_qtd.def @@ -0,0 +1,3 @@ +LIBRARY d3dcompiler_qtd +EXPORTS + D3DCompile @1 diff --git a/src/angle/d3dcompiler/main.cpp b/src/angle/d3dcompiler/main.cpp new file mode 100644 index 0000000000..96d71d5e0d --- /dev/null +++ b/src/angle/d3dcompiler/main.cpp @@ -0,0 +1,295 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QByteArray> +#include <QCryptographicHash> +#include <QDateTime> +#include <QDir> +#include <QElapsedTimer> +#include <QFile> +#include <QLoggingCategory> +#include <QStandardPaths> +#include <QThread> + +#include <qt_windows.h> +#include <d3dcommon.h> + +Q_LOGGING_CATEGORY(QT_D3DCOMPILER, "qt.angle.d3dcompiler") + +namespace D3DCompiler { + +typedef HRESULT (WINAPI *D3DCompileFunc)(const void *data, SIZE_T data_size, const char *filename, + const D3D_SHADER_MACRO *defines, ID3DInclude *include, const char *entrypoint, + const char *target, UINT sflags, UINT eflags, ID3DBlob **shader, ID3DBlob **error_messages); +static D3DCompileFunc compile; + +class Blob : public ID3DBlob +{ +public: + Blob(const QByteArray &data) : m_data(data) + { + IIDFromString(L"00000000-0000-0000-C000-000000000046", &IID_IUnknown); + IIDFromString(L"8BA5FB08-5195-40e2-AC58-0D989C3A0102", &IID_ID3DBlob); + } + + virtual ~Blob() + { + } + + // ID3DBlob + LPVOID __stdcall GetBufferPointer() + { + return m_data.data(); + } + + SIZE_T __stdcall GetBufferSize() + { + return m_data.size(); + } + + // IUnknown + HRESULT __stdcall QueryInterface(REFIID riid, void **ppv) + { + IUnknown *out = 0; + if (riid == IID_IUnknown) + out = static_cast<IUnknown*>(this); + else if (riid == IID_ID3DBlob) + out = this; + + *ppv = out; + if (!out) + return E_NOINTERFACE; + + out->AddRef(); + return S_OK; + } + + ULONG __stdcall AddRef() + { + return ++m_ref; + } + + ULONG __stdcall Release() + { + ULONG ref = --m_ref; + if (!m_ref) + delete this; + + return ref; + } + +private: + QByteArray m_data; + ULONG m_ref; + + // These symbols might be missing, so define them here + IID IID_IUnknown; + IID IID_ID3DBlob; +}; + +static bool loadCompiler() +{ + static HMODULE d3dcompiler = 0; + if (!d3dcompiler) { + const wchar_t *dllNames[] = { + L"d3dcompiler_47.dll", + L"d3dcompiler_46.dll", + L"d3dcompiler_45.dll", + L"d3dcompiler_44.dll", + L"d3dcompiler_43.dll", + 0 + }; + for (int i = 0; const wchar_t *name = dllNames[i]; ++i) { +#ifndef Q_OS_WINRT + d3dcompiler = LoadLibrary(name); +#else + d3dcompiler = LoadPackagedLibrary(name, NULL); +#endif + if (d3dcompiler) { + qCDebug(QT_D3DCOMPILER) << "Found" << QString::fromWCharArray(name); + D3DCompiler::compile = reinterpret_cast<D3DCompiler::D3DCompileFunc>(GetProcAddress(d3dcompiler, "D3DCompile")); + if (D3DCompiler::compile) { + qCDebug(QT_D3DCOMPILER) << "Loaded" << QString::fromWCharArray(name); + break; + } + qCDebug(QT_D3DCOMPILER) << "Failed to load" << QString::fromWCharArray(name); + } + } + + if (!d3dcompiler) + qCDebug(QT_D3DCOMPILER) << "Unable to load D3D shader compiler."; + } + + return bool(compile); +} + +static bool serviceAvailable(const QString &path) +{ + if (path.isEmpty()) + return false; + + // Look for a file, "control", and check if it has been touched in the last 60 seconds + QFileInfo control(path + QStringLiteral("control")); + return control.exists() && control.lastModified().secsTo(QDateTime::currentDateTime()) < 60; +} + +static QString cacheKeyFor(const void *data) +{ + return QString::fromUtf8(QCryptographicHash::hash(reinterpret_cast<const char *>(data), QCryptographicHash::Sha1).toHex()); +} + +static QString makePath(const QDir &parent, const QString &child) +{ + const QString path = parent.absoluteFilePath(child); + if (!parent.mkpath(child)) { + qCWarning(QT_D3DCOMPILER) << "Path is inaccessible: " << path; + return QString(); + } + return path; +} + +} // namespace D3DCompiler + +extern "C" __declspec(dllexport) HRESULT WINAPI D3DCompile( + const void *, SIZE_T, const char *, const D3D_SHADER_MACRO *, ID3DInclude *, + const char *t, const char *, UINT, UINT, ID3DBlob **, ID3DBlob **); + +HRESULT WINAPI D3DCompile( + const void *data, SIZE_T data_size, const char *filename, + const D3D_SHADER_MACRO *defines, ID3DInclude *include, const char *entrypoint, + const char *target, UINT sflags, UINT eflags, ID3DBlob **shader, ID3DBlob **errorMsgs) +{ + static QString basePath; + static QString binaryPath; + static QString sourcePath; + if (basePath.isEmpty()) { + QDir base; + if (qEnvironmentVariableIsSet("QT_D3DCOMPILER_DIR")) + base.setPath(QString::fromUtf8(qgetenv("QT_D3DCOMPILER_DIR"))); + else + base.setPath(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); + + if (!base.exists() && !base.mkdir(QStringLiteral("."))) { + qCWarning(QT_D3DCOMPILER) << "D3D compiler base directory does not exist: " << QDir::toNativeSeparators(base.path()); + } else { + const QString path = base.absoluteFilePath(QStringLiteral("d3dcompiler/")); + if (!QFile::exists(path) && !base.mkdir(QStringLiteral("d3dcompiler"))) + qCWarning(QT_D3DCOMPILER) << "D3D compiler path could not be created: " << QDir::toNativeSeparators(path); + else + basePath = path; + } + } + + if (!basePath.isEmpty()) { + binaryPath = D3DCompiler::makePath(basePath, QStringLiteral("binary/")); + sourcePath = D3DCompiler::makePath(basePath, QStringLiteral("source/")); + } + + // Check if pre-compiled shader blob is available + const QByteArray sourceData = QByteArray::fromRawData(reinterpret_cast<const char *>(data), data_size); + const QString cacheKey = D3DCompiler::cacheKeyFor(sourceData); + QFile blob(binaryPath + cacheKey); + if (!binaryPath.isEmpty() && blob.exists()) { + if (blob.open(QFile::ReadOnly)) { + qCDebug(QT_D3DCOMPILER) << "Opening precompiled shader blob at" << blob.fileName(); + *shader = new D3DCompiler::Blob(blob.readAll()); + return S_FALSE; + } + qCDebug(QT_D3DCOMPILER) << "Found, but unable to open, precompiled shader blob at" << blob.fileName(); + } + + // Shader blob is not available, compile with compilation service if possible + if (D3DCompiler::serviceAvailable(basePath)) { + + if (sourcePath.isEmpty()) { + qCWarning(QT_D3DCOMPILER) << "Compiler service is available, but source directory is not writable."; + return E_ACCESSDENIED; + } + + // Dump source to source path; wait for blob to appear + QFile source(sourcePath + cacheKey); + if (!source.open(QFile::WriteOnly)) { + qCDebug(QT_D3DCOMPILER) << "Unable to write shader source to file:" << source.fileName() << source.errorString(); + return E_ACCESSDENIED; + } + + source.write(sourceData); + qCDebug(QT_D3DCOMPILER) << "Wrote shader source, waiting for blob:" << source.fileName(); + source.close(); + + qint64 timeout = qgetenv("QT_D3DCOMPILER_TIMEOUT").toLong(); + if (!timeout) + timeout = 3000; + + QElapsedTimer timer; + timer.start(); + while (!(blob.exists() && blob.open(QFile::ReadOnly)) && timer.elapsed() < timeout) + QThread::msleep(100); + + if (blob.isOpen()) { + *shader = new D3DCompiler::Blob(blob.readAll()); + return S_FALSE; + } + + qCDebug(QT_D3DCOMPILER) << "Shader blob failed to materialize after" << timeout << "ms."; + *errorMsgs = new D3DCompiler::Blob("Shader compilation timeout."); + return E_ABORT; + } + + // Fall back to compiler DLL + if (D3DCompiler::loadCompiler()) { + HRESULT hr = D3DCompiler::compile(data, data_size, filename, defines, include, entrypoint, + target, sflags, eflags, shader, errorMsgs); + // Cache shader + if (SUCCEEDED(hr) && !binaryPath.isEmpty()) { + const QByteArray blobContents = QByteArray::fromRawData( + reinterpret_cast<const char *>((*shader)->GetBufferPointer()), (*shader)->GetBufferSize()); + if (blob.open(QFile::WriteOnly) && blob.write(blobContents)) + qCDebug(QT_D3DCOMPILER) << "Cached shader blob at" << blob.fileName(); + else + qCDebug(QT_D3DCOMPILER) << "Unable to write shader blob at" << blob.fileName(); + } + return hr; + } + + *errorMsgs = new D3DCompiler::Blob("Unable to load D3D compiler DLL."); + return E_FAIL; +} |