summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authorStelios Tsampas2019-12-16 19:51:22 +0200
committerStelios Tsampas2019-12-16 19:51:22 +0200
commit6724cfc86102383af0a6b9b5c01951cb0ba06285 (patch)
treecfa981e2e4998564654b31d30f6131394954c7c1
parent18d91a7251336929cbd9816eaf4f6deb24975dca (diff)
downloadaur-6724cfc86102383af0a6b9b5c01951cb0ba06285.tar.gz
Version 1.5. Merge with d9vk
-rw-r--r--.SRCINFO35
-rw-r--r--.gitignore3
-rw-r--r--PKGBUILD108
-rw-r--r--dxvk-async.patch446
-rw-r--r--dxvk-mangohud.patch665
-rw-r--r--extraopts.patch70
-rw-r--r--setup_dxvk2
7 files changed, 1283 insertions, 46 deletions
diff --git a/.SRCINFO b/.SRCINFO
index 58869e3eef9a..1c24be532b9d 100644
--- a/.SRCINFO
+++ b/.SRCINFO
@@ -1,36 +1,33 @@
pkgbase = dxvk-winelib
- pkgdesc = A Vulkan-based compatibility layer for Direct3D 10/11 which allows running 3D applications on Linux using Wine. Winelib version.
- pkgver = 1.4.4
- pkgrel = 2
+ pkgdesc = Vulkan-based implementation of D3D9, D3D10 and D3D11 for Linux / Wine, Winelib version
+ pkgver = 1.5
+ pkgrel = 1
url = https://github.com/doitsujin/dxvk
arch = x86_64
license = zlib/libpng
makedepends = ninja
- makedepends = meson
+ makedepends = meson>=0.43
makedepends = glslang
makedepends = git
makedepends = wine
depends = vulkan-icd-loader
depends = wine>=4.0rc1
depends = lib32-vulkan-icd-loader
- provides = dxvk-bin
- provides = dxvk-git
- provides = dxvk-wine32-git
- provides = dxvk-wine64-git
- provides = dxvk-win32-git
- provides = dxvk-win64-git
- provides = dxvk-mingw-git
- provides = dxvk-winelib-git
+ depends = bash
provides = dxvk
- conflicts = dxvk-git
- conflicts = dxvk-wine32-git
- conflicts = dxvk-wine64-git
- conflicts = dxvk-win32-git
- conflicts = dxvk-win64-git
- conflicts = dxvk-winelib-git
+ provides = d9vk
conflicts = dxvk
- source = git+https://github.com/doitsujin/dxvk.git#tag=v1.4.4
+ conflicts = d9vk
+ source = git+https://github.com/doitsujin/dxvk.git#tag=v1.5
+ source = setup_dxvk
+ source = dxvk-async.patch
+ source = dxvk-mangohud.patch
+ source = extraopts.patch
sha256sums = SKIP
+ sha256sums = b2413cabd8cca56e2d308ef5513edf1c7f909036ed2ccfeae17536a0e864dc96
+ sha256sums = 6ff286091c20327e67252e1e6812830a42c990d1ee56541023eb217712209f3c
+ sha256sums = 2e335237623aaf006f8814fc9712f3a4be0d678cd0714879a3a4545f3bbf41ce
+ sha256sums = acde3a23166f79fa87ab090200be2aabaf16e5876ce19b8270ad1179bb0bc7a5
pkgname = dxvk-winelib
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 5ed2b9752c6b..000000000000
--- a/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-src
-pkg
-dxvk
diff --git a/PKGBUILD b/PKGBUILD
index b2463a698791..151d6b194544 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -1,49 +1,109 @@
-# Maintainer: nikos fytilis n - fit [at] live [dot] com
+# Maintainer: loathingkernel <loathingkernel @at gmail .dot com>
+# Contributor: nikos fytilis n - fit [at] live [dot] com
# Contributor: AdriƠ Cereto i MassaguƩ <ssorgatem at gmail.com>
-pkgbase=dxvk-winelib
pkgname=dxvk-winelib
-pkgver=1.4.4
-pkgrel=2
-pkgdesc="A Vulkan-based compatibility layer for Direct3D 10/11 which allows running 3D applications on Linux using Wine. Winelib version."
+pkgver=1.5
+pkgrel=1
+pkgdesc='Vulkan-based implementation of D3D9, D3D10 and D3D11 for Linux / Wine, Winelib version'
arch=('x86_64')
url="https://github.com/doitsujin/dxvk"
license=('zlib/libpng')
-depends=('vulkan-icd-loader' 'wine>=4.0rc1' 'lib32-vulkan-icd-loader')
-makedepends=('ninja' 'meson' 'glslang' 'git' 'wine')
-provides=("dxvk-bin" "dxvk-git" "dxvk-wine32-git" "dxvk-wine64-git" "dxvk-win32-git" "dxvk-win64-git" "dxvk-mingw-git" "dxvk-winelib-git" "dxvk")
-conflicts=("dxvk-git" "dxvk-wine32-git" "dxvk-wine64-git" "dxvk-win32-git" "dxvk-win64-git" "dxvk-winelib-git" "dxvk")
+depends=('vulkan-icd-loader' 'wine>=4.0rc1' 'lib32-vulkan-icd-loader' 'bash')
+makedepends=('ninja' 'meson>=0.43' 'glslang' 'git' 'wine')
+provides=('dxvk' 'd9vk')
+conflicts=('dxvk' 'd9vk')
+source=(
+ "git+https://github.com/doitsujin/dxvk.git#tag=v$pkgver"
+ "setup_dxvk"
+ "dxvk-async.patch"
+ "dxvk-mangohud.patch"
+ "extraopts.patch"
+)
+sha256sums=(
+ "SKIP"
+ "b2413cabd8cca56e2d308ef5513edf1c7f909036ed2ccfeae17536a0e864dc96"
+ "6ff286091c20327e67252e1e6812830a42c990d1ee56541023eb217712209f3c"
+ "2e335237623aaf006f8814fc9712f3a4be0d678cd0714879a3a4545f3bbf41ce"
+ "acde3a23166f79fa87ab090200be2aabaf16e5876ce19b8270ad1179bb0bc7a5"
+)
-source=("git+https://github.com/doitsujin/dxvk.git#tag=v$pkgver")
-sha256sums=("SKIP")
+prepare() {
+ cd dxvk
+ # Uncomment to enable extra optimizations
+ # Patch crossfiles with extra optimizations from makepkg.conf
+ #patch -p1 -i ../extraopts.patch
+ # Filter known bad flags before applying optimizations
+ # If using -march=native and the CPU supports AVX, launching a d3d9
+ # game can cause an Unhandled exception. The cause seems to be the
+ # combination of AVX instructions and tree vectorization (implied by O3),
+ # all tested archictures from sandybridge to haswell are affected.
+ # Disabling either AVX (and AVX2 as a side-effect) or tree
+ # vectorization fixes the issue. I am not sure which one is better
+ # to disable so below you can choose. Append either of these flags.
+ # Relevant Wine issues
+ # https://bugs.winehq.org/show_bug.cgi?id=45289
+ # https://bugs.winehq.org/show_bug.cgi?id=43516
+ CFLAGS+=" -mno-avx"
+ #CFLAGS+=" -fno-tree-vectorize"
+ # Filter fstack-protector flag for MingW.
+ # https://github.com/Joshua-Ashton/d9vk/issues/476
+ #CFLAGS+=" -fno-stack-protector"
+ #CFLAGS="${CFLAGS// -fstack-protector+(-all|-strong)/}"
+ #CFLAGS="${CFLAGS// -fstack-protector+(?=[ ])/}"
+ # Adjust optimization level in meson arguments. This is ignored
+ # anyway because meson sets its own optimization level.
+ CFLAGS="${CFLAGS// -O+([0-3s]|fast)/}"
+ # Doesn't compile with these flags in MingW so remove them.
+ # They are also filtered in Wine PKGBUILDs so remove them
+ # for winelib versions too.
+ CFLAGS="${CFLAGS/ -fno-plt/}"
+ LDFLAGS="${LDFLAGS/,-z,now/}"
+ sed -i build-wine64.txt \
+ -e "s|@CARGS@|\'${CFLAGS// /\',\'}\'|g" \
+ -e "s|@LDARGS@|\'${LDFLAGS// /\',\'}\'|g"
+ sed -i build-wine32.txt \
+ -e "s|@CARGS@|\'${CFLAGS// /\',\'}\'|g" \
+ -e "s|@LDARGS@|\'${LDFLAGS// /\',\'}\'|g"
-build() {
+ # Uncomment to enable dxvk async patch.
+ # Enable at your own risk. If you don't know what it is,
+ # and its implications, leave it as is. You have been warned.
+ # I am not liable if anything happens to you by using it.
+ # Patch enables async by default. YOU HAVE BEEN WARNED.
+ #patch -p1 -i ../dxvk-async.patch
+
+ # Uncomment to enable Mango HUD for dxvk
+ #patch -p1 -i ../dxvk-mangohud.patch
+}
+build() {
meson dxvk "build/x64" \
--cross-file dxvk/build-wine64.txt \
--prefix "/usr/share/dxvk/x64" \
--bindir "" --libdir "" \
--buildtype "release" \
+ --optimization=3 \
--strip \
- -D enable_tests=false
- ninja -C "build/x64"
+ -Denable_tests=false
+ ninja -C "build/x64" -v
meson dxvk "build/x32" \
--cross-file dxvk/build-wine32.txt \
--prefix "/usr/share/dxvk/x32" \
--bindir "" --libdir "" \
--buildtype "release" \
+ --optimization=3 \
--strip \
- -D enable_tests=false
- ninja -C "build/x32"
+ -Denable_tests=false
+ ninja -C "build/x32" -v
}
-package_dxvk-winelib() {
- DESTDIR="$pkgdir" ninja -C "build/x32" install
- DESTDIR="$pkgdir" ninja -C "build/x64" install
- install -Dm 644 dxvk/setup_dxvk.sh "$pkgdir/usr/share/dxvk/setup_dxvk.sh"
- install -Dm 644 dxvk/LICENSE "$pkgdir/share/licenses/$pkgname/LICENSE"
- mkdir -p "$pkgdir/usr/bin"
- ln -s /usr/share/dxvk/setup_dxvk.sh "$pkgdir/usr/bin/setup_dxvk"
- chmod +x "$pkgdir/usr/share/dxvk/setup_dxvk.sh"
+package() {
+ DESTDIR="$pkgdir" ninja -C "build/x32" install
+ DESTDIR="$pkgdir" ninja -C "build/x64" install
+ install -Dm 755 -t "$pkgdir/usr/share/dxvk" dxvk/setup_dxvk.sh
+ install -Dm 644 -t "$pkgdir/usr/share/doc/dxvk" dxvk/dxvk.conf
+ install -Dm 644 -t "$pkgdir/usr/share/$pkgname" dxvk/LICENSE
+ install -Dm 755 -t "$pkgdir/usr/bin" setup_dxvk
}
diff --git a/dxvk-async.patch b/dxvk-async.patch
new file mode 100644
index 000000000000..19c506c4964f
--- /dev/null
+++ b/dxvk-async.patch
@@ -0,0 +1,446 @@
+From dfac76f528d0fc88ecbfe27ccb87483840828b9b Mon Sep 17 00:00:00 2001
+From: Tk-Glitch <ti3nou@gmail.com>
+Date: Sat, 28 Sep 2019 18:59:04 +0200
+Subject: Async pipecompiler rebase against DXVK
+ d128d776ad906d6b8d3941eda7b7ee679346dbaf
+
+
+diff --git a/meson.build b/meson.build
+index 9a519e46..dea82ee1 100644
+--- a/meson.build
++++ b/meson.build
+@@ -101,7 +101,7 @@ else
+ endif
+
+ dxvk_version = vcs_tag(
+- command: ['git', 'describe', '--dirty=+'],
++ command: ['git', 'describe', '--dirty=-async'],
+ input: 'version.h.in',
+ output: 'version.h')
+
+diff --git a/src/dxvk/dxvk_context.cpp b/src/dxvk/dxvk_context.cpp
+index b4f679fa..13c86d8f 100644
+--- a/src/dxvk/dxvk_context.cpp
++++ b/src/dxvk/dxvk_context.cpp
+@@ -606,7 +606,7 @@ namespace dxvk {
+ const Rc<DxvkImageView>& imageView,
+ VkImageAspectFlags clearAspects,
+ VkClearValue clearValue) {
+- this->updateFramebuffer();
++ this->updateFramebuffer(false);
+
+ // Prepare attachment ops
+ DxvkColorAttachmentOps colorOp;
+@@ -2433,7 +2433,7 @@ namespace dxvk {
+ VkExtent3D extent,
+ VkImageAspectFlags aspect,
+ VkClearValue value) {
+- this->updateFramebuffer();
++ this->updateFramebuffer(false);
+
+ // Find out if the render target view is currently bound,
+ // so that we can avoid spilling the render pass if it is.
+@@ -3652,7 +3652,7 @@ namespace dxvk {
+ : DxvkContextFlag::GpDirtyStencilRef);
+
+ // Retrieve and bind actual Vulkan pipeline handle
+- m_gpActivePipeline = m_state.gp.pipeline->getPipelineHandle(m_state.gp.state, m_state.om.framebuffer->getRenderPass());
++ m_gpActivePipeline = m_state.gp.pipeline->getPipelineHandle(m_state.gp.state, m_state.om.framebuffer->getRenderPass(), this->checkAsyncCompilationCompat());
+
+ if (unlikely(!m_gpActivePipeline))
+ return false;
+@@ -3969,7 +3969,7 @@ namespace dxvk {
+ }
+
+
+- void DxvkContext::updateFramebuffer() {
++ void DxvkContext::updateFramebuffer(bool isDraw) {
+ if (m_flags.test(DxvkContextFlag::GpDirtyFramebuffer)) {
+ m_flags.clr(DxvkContextFlag::GpDirtyFramebuffer);
+
+@@ -3988,6 +3988,11 @@ namespace dxvk {
+ : VkComponentMapping();
+ }
+
++ if (isDraw) {
++ for (uint32_t i = 0; i < fb->numAttachments(); i++)
++ fb->getAttachment(i).view->setRtBindingFrameId(m_device->getCurrentFrameId());
++ }
++
+ m_flags.set(DxvkContextFlag::GpDirtyPipelineState);
+ }
+ }
+@@ -4205,7 +4210,7 @@ namespace dxvk {
+ template<bool Indexed>
+ void DxvkContext::commitGraphicsState() {
+ if (m_flags.test(DxvkContextFlag::GpDirtyFramebuffer))
+- this->updateFramebuffer();
++ this->updateFramebuffer(true);
+
+ if (!m_flags.test(DxvkContextFlag::GpRenderPassBound))
+ this->startRenderPass();
+@@ -4465,4 +4470,13 @@ namespace dxvk {
+ }
+ }
+
+-}
+\ No newline at end of file
++
++ bool DxvkContext::checkAsyncCompilationCompat() {
++ bool fbCompat = true;
++ for (uint32_t i = 0; fbCompat && i < m_state.om.framebuffer->numAttachments(); i++) {
++ const auto& attachment = m_state.om.framebuffer->getAttachment(i);
++ fbCompat &= attachment.view->getRtBindingAsyncCompilationCompat();
++ }
++ return fbCompat;
++ }
++}
+diff --git a/src/dxvk/dxvk_context.h b/src/dxvk/dxvk_context.h
+index f54f5321..0984d085 100644
+--- a/src/dxvk/dxvk_context.h
++++ b/src/dxvk/dxvk_context.h
+@@ -1141,7 +1141,7 @@ namespace dxvk {
+ VkDescriptorSet set,
+ const DxvkPipelineLayout* layout);
+
+- void updateFramebuffer();
++ void updateFramebuffer(bool isDraw);
+
+ void updateIndexBufferBinding();
+ void updateVertexBufferBindings();
+@@ -1180,6 +1180,7 @@ namespace dxvk {
+
+ void trackDrawBuffer();
+
++ bool checkAsyncCompilationCompat();
+ };
+
+-}
+\ No newline at end of file
++}
+diff --git a/src/dxvk/dxvk_graphics.cpp b/src/dxvk/dxvk_graphics.cpp
+index 5e05b425..8586c794 100644
+--- a/src/dxvk/dxvk_graphics.cpp
++++ b/src/dxvk/dxvk_graphics.cpp
+@@ -75,8 +75,6 @@ namespace dxvk {
+
+
+ DxvkGraphicsPipeline::~DxvkGraphicsPipeline() {
+- for (const auto& instance : m_pipelines)
+- this->destroyPipeline(instance.pipeline());
+ }
+
+
+@@ -96,7 +94,8 @@ namespace dxvk {
+
+ VkPipeline DxvkGraphicsPipeline::getPipelineHandle(
+ const DxvkGraphicsPipelineStateInfo& state,
+- const DxvkRenderPass* renderPass) {
++ const DxvkRenderPass* renderPass,
++ bool async) {
+ DxvkGraphicsPipelineInstance* instance = nullptr;
+
+ { std::lock_guard<sync::Spinlock> lock(m_mutex);
+@@ -105,10 +104,13 @@ namespace dxvk {
+
+ if (instance)
+ return instance->pipeline();
+-
+- instance = this->createInstance(state, renderPass);
++
++ if (async && m_pipeMgr->m_compiler != nullptr)
++ m_pipeMgr->m_compiler->queueCompilation(this, state, renderPass);
++ else
++ instance = this->createInstance(state, renderPass);
+ }
+-
++
+ if (!instance)
+ return VK_NULL_HANDLE;
+
+diff --git a/src/dxvk/dxvk_graphics.h b/src/dxvk/dxvk_graphics.h
+index 168e9714..07f0883d 100644
+--- a/src/dxvk/dxvk_graphics.h
++++ b/src/dxvk/dxvk_graphics.h
+@@ -247,11 +247,13 @@ namespace dxvk {
+ * state. If necessary, a new pipeline will be created.
+ * \param [in] state Pipeline state vector
+ * \param [in] renderPass The render pass
++ * \param [in] async Compile asynchronously
+ * \returns Pipeline handle
+ */
+ VkPipeline getPipelineHandle(
+ const DxvkGraphicsPipelineStateInfo& state,
+- const DxvkRenderPass* renderPass);
++ const DxvkRenderPass* renderPass,
++ bool async);
+
+ /**
+ * \brief Compiles a pipeline
+diff --git a/src/dxvk/dxvk_image.h b/src/dxvk/dxvk_image.h
+index 8f82d65a..156f6054 100644
+--- a/src/dxvk/dxvk_image.h
++++ b/src/dxvk/dxvk_image.h
+@@ -442,6 +442,37 @@ namespace dxvk {
+ return result;
+ }
+
++ /**
++ * \brief Sets render target usage frame number
++ *
++ * The image view will track internally when
++ * it was last used as a render target. This
++ * info is used for async shader compilation.
++ * \param [in] frameId Frame number
++ */
++ void setRtBindingFrameId(uint32_t frameId) {
++ if (frameId != m_rtBindingFrameId) {
++ if (frameId == m_rtBindingFrameId + 1)
++ m_rtBindingFrameCount += 1;
++ else
++ m_rtBindingFrameCount = 0;
++
++ m_rtBindingFrameId = frameId;
++ }
++ }
++
++ /**
++ * \brief Checks for async pipeline compatibility
++ *
++ * Asynchronous pipeline compilation may be enabled if the
++ * render target has been drawn to in the previous frames.
++ * \param [in] frameId Current frame ID
++ * \returns \c true if async compilation is supported
++ */
++ bool getRtBindingAsyncCompilationCompat() const {
++ return m_rtBindingFrameCount >= 5;
++ }
++
+ private:
+
+ Rc<vk::DeviceFn> m_vkd;
+@@ -450,6 +481,9 @@ namespace dxvk {
+ DxvkImageViewCreateInfo m_info;
+ VkImageView m_views[ViewCount];
+
++ uint32_t m_rtBindingFrameId = 0;
++ uint32_t m_rtBindingFrameCount = 0;
++
+ void createView(VkImageViewType type, uint32_t numLayers);
+
+ };
+diff --git a/src/dxvk/dxvk_options.cpp b/src/dxvk/dxvk_options.cpp
+index 394feabb..df05ecd8 100644
+--- a/src/dxvk/dxvk_options.cpp
++++ b/src/dxvk/dxvk_options.cpp
+@@ -5,6 +5,7 @@ namespace dxvk {
+ DxvkOptions::DxvkOptions(const Config& config) {
+ enableStateCache = config.getOption<bool> ("dxvk.enableStateCache", true);
+ enableOpenVR = config.getOption<bool> ("dxvk.enableOpenVR", true);
++ useAsync = config.getOption<bool> ("dxvk.useAsync", true);
+ numCompilerThreads = config.getOption<int32_t> ("dxvk.numCompilerThreads", 0);
+ asyncPresent = config.getOption<Tristate>("dxvk.asyncPresent", Tristate::Auto);
+ useRawSsbo = config.getOption<Tristate>("dxvk.useRawSsbo", Tristate::Auto);
+diff --git a/src/dxvk/dxvk_options.h b/src/dxvk/dxvk_options.h
+index 447294b5..571d6c15 100644
+--- a/src/dxvk/dxvk_options.h
++++ b/src/dxvk/dxvk_options.h
+@@ -10,6 +10,7 @@ namespace dxvk {
+
+ /// Enable state cache
+ bool enableStateCache;
++ bool useAsync;
+
+ /// Use transfer queue if available
+ bool enableTransferQueue;
+diff --git a/src/dxvk/dxvk_pipecompiler.cpp b/src/dxvk/dxvk_pipecompiler.cpp
+new file mode 100644
+index 00000000..cfef228a
+--- /dev/null
++++ b/src/dxvk/dxvk_pipecompiler.cpp
+@@ -0,0 +1,69 @@
++#include "dxvk_graphics.h"
++#include "dxvk_pipecompiler.h"
++
++namespace dxvk {
++
++ DxvkPipelineCompiler::DxvkPipelineCompiler() {
++ uint32_t sysCpuCount = dxvk::thread::hardware_concurrency();
++ uint32_t threadCount = sysCpuCount > 2 ? sysCpuCount - 2 : 1;
++
++ Logger::info(str::format(
++ "DxvkPipelineCompiler: Using ",
++ threadCount, " workers"));
++
++ // Start the compiler threads
++ m_compilerThreads.resize(threadCount);
++
++ for (uint32_t i = 0; i < threadCount; i++) {
++ m_compilerThreads.at(i) = dxvk::thread(
++ [this] { this->runCompilerThread(); });
++ }
++ }
++
++
++ DxvkPipelineCompiler::~DxvkPipelineCompiler() {
++ { std::unique_lock<std::mutex> lock(m_compilerLock);
++ m_compilerStop.store(true);
++ }
++
++ m_compilerCond.notify_all();
++ for (auto& thread : m_compilerThreads)
++ thread.join();
++ }
++
++
++ void DxvkPipelineCompiler::queueCompilation(
++ DxvkGraphicsPipeline* pipeline,
++ const DxvkGraphicsPipelineStateInfo& state,
++ const DxvkRenderPass* renderPass) {
++ std::unique_lock<std::mutex> lock(m_compilerLock);
++ m_compilerQueue.push({ pipeline, state, renderPass });
++ m_compilerCond.notify_one();
++ }
++
++
++ void DxvkPipelineCompiler::runCompilerThread() {
++ env::setThreadName("dxvk-pcompiler");
++
++ while (!m_compilerStop.load()) {
++ PipelineEntry entry;
++
++ { std::unique_lock<std::mutex> lock(m_compilerLock);
++
++ m_compilerCond.wait(lock, [this] {
++ return m_compilerStop.load()
++ || m_compilerQueue.size() != 0;
++ });
++
++ if (m_compilerQueue.size() != 0) {
++ entry = std::move(m_compilerQueue.front());
++ m_compilerQueue.pop();
++ }
++ }
++
++ if (entry.pipeline != nullptr && entry.renderPass != nullptr)
++ entry.pipeline->compilePipeline(entry.state, entry.renderPass);
++ }
++ }
++
++}
+diff --git a/src/dxvk/dxvk_pipecompiler.h b/src/dxvk/dxvk_pipecompiler.h
+new file mode 100644
+index 00000000..a82fff60
+--- /dev/null
++++ b/src/dxvk/dxvk_pipecompiler.h
+@@ -0,0 +1,60 @@
++#pragma once
++
++#include <atomic>
++#include <condition_variable>
++#include <mutex>
++#include <queue>
++
++#include "../util/thread.h"
++#include "dxvk_include.h"
++
++namespace dxvk {
++
++ class DxvkGraphicsPipeline;
++ class DxvkGraphicsPipelineStateInfo;
++
++ /**
++ * \brief Pipeline compiler
++ *
++ * Asynchronous pipeline compiler
++ */
++ class DxvkPipelineCompiler : public RcObject {
++
++ public:
++
++ DxvkPipelineCompiler();
++ ~DxvkPipelineCompiler();
++
++ /**
++ * \brief Compiles a pipeline asynchronously
++ *
++ * This should be used to compile graphics
++ * pipeline instances asynchronously.
++ * \param [in] pipeline The pipeline object
++ * \param [in] state The pipeline state info object
++ * \param [in] renderPass
++ */
++ void queueCompilation(
++ DxvkGraphicsPipeline* pipeline,
++ const DxvkGraphicsPipelineStateInfo& state,
++ const DxvkRenderPass* renderPass);
++
++ private:
++
++ struct PipelineEntry {
++ DxvkGraphicsPipeline* pipeline = nullptr;
++ DxvkGraphicsPipelineStateInfo state;
++ const DxvkRenderPass* renderPass = nullptr;
++ };
++
++ std::atomic<bool> m_compilerStop = { false };
++ std::mutex m_compilerLock;
++ std::condition_variable m_compilerCond;
++ std::queue<PipelineEntry> m_compilerQueue;
++ std::vector<dxvk::thread> m_compilerThreads;
++
++ void runCompilerThread();
++
++ };
++
++}
+diff --git a/src/dxvk/dxvk_pipemanager.cpp b/src/dxvk/dxvk_pipemanager.cpp
+index 378bb3d1..3323f926 100644
+--- a/src/dxvk/dxvk_pipemanager.cpp
++++ b/src/dxvk/dxvk_pipemanager.cpp
+@@ -45,9 +45,13 @@ namespace dxvk {
+ : m_device (device),
+ m_cache (new DxvkPipelineCache(device->vkd())) {
+ std::string useStateCache = env::getEnvVar("DXVK_STATE_CACHE");
++ std::string useAsync = env::getEnvVar("DXVK_ASYNC");
+
+ if (useStateCache != "0" && device->config().enableStateCache)
+ m_stateCache = new DxvkStateCache(device, this, passManager);
++
++ if (useAsync != "0" || device->config().useAsync)
++ m_compiler = new DxvkPipelineCompiler();
+ }
+
+
+diff --git a/src/dxvk/dxvk_pipemanager.h b/src/dxvk/dxvk_pipemanager.h
+index f0087d15..29e758c7 100644
+--- a/src/dxvk/dxvk_pipemanager.h
++++ b/src/dxvk/dxvk_pipemanager.h
+@@ -6,6 +6,7 @@
+
+ #include "dxvk_compute.h"
+ #include "dxvk_graphics.h"
++#include "dxvk_pipecompiler.h"
+
+ namespace dxvk {
+
+@@ -107,6 +108,7 @@ namespace dxvk {
+ const DxvkDevice* m_device;
+ Rc<DxvkPipelineCache> m_cache;
+ Rc<DxvkStateCache> m_stateCache;
++ Rc<DxvkPipelineCompiler> m_compiler;
+
+ std::atomic<uint32_t> m_numComputePipelines = { 0 };
+ std::atomic<uint32_t> m_numGraphicsPipelines = { 0 };
+diff --git a/src/dxvk/meson.build b/src/dxvk/meson.build
+index 867f3002..f4c4336c 100644
+--- a/src/dxvk/meson.build
++++ b/src/dxvk/meson.build
+@@ -82,6 +82,7 @@ dxvk_src = files([
+ 'dxvk_openvr.cpp',
+ 'dxvk_options.cpp',
+ 'dxvk_pipecache.cpp',
++ 'dxvk_pipecompiler.cpp',
+ 'dxvk_pipelayout.cpp',
+ 'dxvk_pipemanager.cpp',
+ 'dxvk_queue.cpp',
diff --git a/dxvk-mangohud.patch b/dxvk-mangohud.patch
new file mode 100644
index 000000000000..13fbdee8db9a
--- /dev/null
+++ b/dxvk-mangohud.patch
@@ -0,0 +1,665 @@
+diff --git a/README.md b/README.md
+index 25d03230..e8a53432 100644
+--- a/README.md
++++ b/README.md
+@@ -1,3 +1,29 @@
++# MangoHud Specific
++
++- `DXVK_HUD_OFFSET_X` Set X offset of the DVXK Hud.
++- `DXVK_HUD_OFFSET_Y` Set Y offset of the DVXK Hud.
++- `DXVK_LOG_TO_FILE` Turn on logging and select path/filename (Fps,Cpu load,Gpu load)
++- Logging Gpu load requires either mangogpuload or gpuload hud options
++
++# Hud options
++- `mangogpuload` : Shows current gpu load.
++- `mangocpuload` : Shows current cpu load.
++
++# Keybinds
++- `F2` : Toggle Logging on/off
++- `F12` : Toggle Hud on/off
++
++# MangoLog file
++
++ When you press F2, a file is created with your chosen name + date/time stamp.
++ this file can be uploaded to https://flightlessmango.com/logs/new to create graphs automatically.
++ you can share the created page with others, just link it.
++
++ #### Multiple log files
++
++ It's possible to upload multiple files, you can rename them to your preferred names and upload them in a batch.
++ The graphs will then use those names in the data.
++
+ # DXVK
+
+ A Vulkan-based translation layer for Direct3D 10/11 which allows running 3D applications on Linux using Wine.
+diff --git a/src/dxvk/dxvk_cpu.h b/src/dxvk/dxvk_cpu.h
+new file mode 100644
+index 00000000..b2c8736a
+--- /dev/null
++++ b/src/dxvk/dxvk_cpu.h
+@@ -0,0 +1,172 @@
++#include <cmath>
++#include <iomanip>
++#include <array>
++#include <vector>
++#include <algorithm>
++#include <iterator>
++#include <thread>
++#include <sstream>
++#include <fstream>
++using namespace std;
++
++const int NUM_CPU_STATES = 10;
++
++struct Cpus{
++ size_t num;
++ string name;
++ float value;
++ string output;
++};
++
++size_t numCpuCores = dxvk::thread::hardware_concurrency();
++size_t arraySize = numCpuCores + 1;
++std::vector<Cpus> cpuArray;
++
++void coreCounting(){
++ cpuArray.push_back({0, "CPU:"});
++ for (size_t i = 0; i < arraySize; i++) {
++ size_t offset = i;
++ stringstream ss;
++ ss << "CPU" << offset << ":";
++ string cpuNameString = ss.str();
++ cpuArray.push_back({i+1 , cpuNameString});
++ }
++}
++
++std::string m_cpuUtilizationString;
++
++enum CPUStates
++{
++ S_USER = 0,
++ S_NICE,
++ S_SYSTEM,
++ S_IDLE,
++ S_IOWAIT,
++ S_IRQ,
++ S_SOFTIRQ,
++ S_STEAL,
++ S_GUEST,
++ S_GUEST_NICE
++};
++
++typedef struct CPUData
++{
++ std::string cpu;
++ size_t times[NUM_CPU_STATES];
++} CPUData;
++
++void ReadStatsCPU(std::vector<CPUData> & entries)
++{
++ std::ifstream fileStat("/proc/stat");
++
++ std::string line;
++
++ const std::string STR_CPU("cpu");
++ const std::size_t LEN_STR_CPU = STR_CPU.size();
++ const std::string STR_TOT("tot");
++
++ while(std::getline(fileStat, line))
++ {
++ // cpu stats line found
++ if(!line.compare(0, LEN_STR_CPU, STR_CPU))
++ {
++ std::istringstream ss(line);
++
++ // store entry
++ entries.emplace_back(CPUData());
++ CPUData & entry = entries.back();
++
++ // read cpu label
++ ss >> entry.cpu;
++
++ if(entry.cpu.size() > LEN_STR_CPU)
++ entry.cpu.erase(0, LEN_STR_CPU);
++ else
++ entry.cpu = STR_TOT;
++
++ // read times
++ for(int i = 0; i < NUM_CPU_STATES; ++i)
++ ss >> entry.times[i];
++ }
++ }
++}
++
++size_t GetIdleTime(const CPUData & e)
++{
++ return e.times[S_IDLE] +
++ e.times[S_IOWAIT];
++}
++
++size_t GetActiveTime(const CPUData & e)
++{
++ return e.times[S_USER] +
++ e.times[S_NICE] +
++ e.times[S_SYSTEM] +
++ e.times[S_IRQ] +
++ e.times[S_SOFTIRQ] +
++ e.times[S_STEAL] +
++ e.times[S_GUEST] +
++ e.times[S_GUEST_NICE];
++}
++
++void PrintStats(const std::vector<CPUData> & entries1, const std::vector<CPUData> & entries2)
++{
++ const size_t NUM_ENTRIES = entries1.size();
++
++ for(size_t i = 0; i < NUM_ENTRIES; ++i)
++ {
++ const CPUData & e1 = entries1[i];
++ const CPUData & e2 = entries2[i];
++
++ const float ACTIVE_TIME = static_cast<float>(GetActiveTime(e2) - GetActiveTime(e1));
++ const float IDLE_TIME = static_cast<float>(GetIdleTime(e2) - GetIdleTime(e1));
++ const float TOTAL_TIME = ACTIVE_TIME + IDLE_TIME;
++
++ cpuArray[i].value = (truncf(100.f * ACTIVE_TIME / TOTAL_TIME) * 10 / 10);
++ }
++}
++
++int getCpuUsage()
++{
++ std::vector<CPUData> entries1;
++ std::vector<CPUData> entries2;
++
++ // snapshot 1
++ ReadStatsCPU(entries1);
++
++ // 100ms pause
++ std::this_thread::sleep_for(std::chrono::milliseconds(100));
++
++ // snapshot 2
++ ReadStatsCPU(entries2);
++
++ // print output
++ PrintStats(entries1, entries2);
++
++ return 0;
++}
++
++
++void updateCpuStrings(){
++ for (size_t i = 0; i < arraySize; i++) {
++ size_t spacing = 10;
++ string value = to_string(cpuArray[i].value);
++ value.erase( value.find_last_not_of('0') + 1, std::string::npos );
++ size_t correctionValue = (spacing - cpuArray[i].name.length()) - value.length();
++ string correction = "";
++ for (size_t i = 0; i < correctionValue; i++) {
++ correction.append(" ");
++ }
++ stringstream ss;
++ if (i < 11) {
++ if (i == 0) {
++ ss << cpuArray[i].name << correction << cpuArray[i].value << "%";
++ } else {
++ ss << cpuArray[i].name << correction << cpuArray[i].value << "%";
++ }
++ } else {
++ ss << cpuArray[i].name << correction << cpuArray[i].value << "%";
++ }
++ cpuArray[i].output = ss.str();
++ }
++ }
+\ No newline at end of file
+diff --git a/src/dxvk/hud/dxvk_hud.cpp b/src/dxvk/hud/dxvk_hud.cpp
+index 4fcd3bd2..b6e23c4c 100644
+--- a/src/dxvk/hud/dxvk_hud.cpp
++++ b/src/dxvk/hud/dxvk_hud.cpp
+@@ -1,9 +1,13 @@
+-#include <cstring>
+ #include <version.h>
+-
+ #include "dxvk_hud.h"
++#include <windows.h>
++#include <time.h>
+
+ namespace dxvk::hud {
++ float Hud::offset_x_float = 0;
++ float Hud::offset_y_float = 0;
++ bool show_hud = true;
++ int lastPress;
+
+ Hud::Hud(
+ const Rc<DxvkDevice>& device,
+@@ -60,6 +64,14 @@ namespace dxvk::hud {
+
+ Rc<Hud> Hud::createHud(const Rc<DxvkDevice>& device) {
+ std::string hudElements = env::getEnvVar("DXVK_HUD");
++ std::string offset_x = env::getEnvVar("DXVK_HUD_OFFSET_X");
++ std::string offset_y = env::getEnvVar("DXVK_HUD_OFFSET_Y");
++
++ if (!offset_x.empty())
++ offset_x_float = stof(offset_x);
++
++ if (!offset_y.empty())
++ offset_y_float = stof(offset_y);
+
+ if (hudElements.empty())
+ hudElements = device->config().hud;
+@@ -82,33 +94,45 @@ namespace dxvk::hud {
+ m_renderer.beginFrame(ctx, m_uniformData.surfaceSize);
+ }
+
+-
+ void Hud::renderHudElements(const Rc<DxvkContext>& ctx) {
+- HudPos position = { 8.0f, 24.0f };
++ if(GetAsyncKeyState(VK_F12) & 0x8000)
++ {
++ if (GetTickCount() - lastPress > 500){
++ lastPress = GetTickCount();
++ if (show_hud){
++ show_hud = false;
++ } else {
++ show_hud = true;
++ }
++ }
++ }
+
+- if (m_config.elements.test(HudElement::DxvkVersion)) {
+- m_renderer.drawText(ctx, 16.0f,
+- { position.x, position.y },
+- { 1.0f, 1.0f, 1.0f, 1.0f },
+- "DXVK " DXVK_VERSION);
+- position.y += 24.0f;
+- }
+-
+- if (m_config.elements.test(HudElement::DxvkClientApi)) {
+- m_renderer.drawText(ctx, 16.0f,
+- { position.x, position.y },
+- { 1.0f, 1.0f, 1.0f, 1.0f },
+- m_device->clientApi());
+- position.y += 24.0f;
+- }
+-
+- if (m_config.elements.test(HudElement::DeviceInfo)) {
+- position = m_hudDeviceInfo.render(
+- ctx, m_renderer, position);
+- }
++ HudPos position = { offset_x_float + 8.0f, offset_y_float + 24.0f };
+
+- position = m_hudFramerate.render(ctx, m_renderer, position);
+- position = m_hudStats .render(ctx, m_renderer, position);
++ if (show_hud){
++ if (m_config.elements.test(HudElement::DxvkVersion)) {
++ m_renderer.drawText(ctx, 16.0f,
++ { position.x, position.y },
++ { 1.0f, 1.0f, 1.0f, 1.0f },
++ "DXVK " DXVK_VERSION);
++ position.y += 24.0f;
++ }
++
++ if (m_config.elements.test(HudElement::DxvkClientApi)) {
++ m_renderer.drawText(ctx, 16.0f,
++ { position.x, position.y },
++ { 1.0f, 1.0f, 1.0f, 1.0f },
++ m_device->clientApi());
++ position.y += 24.0f;
++ }
++
++ if (m_config.elements.test(HudElement::DeviceInfo)) {
++ position = m_hudDeviceInfo.render(
++ ctx, m_renderer, position);
++ }
++ position = m_hudFramerate.render(ctx, m_renderer, position);
++ position = m_hudStats .render(ctx, m_renderer, position);
++ }
+ }
+
+
+diff --git a/src/dxvk/hud/dxvk_hud.h b/src/dxvk/hud/dxvk_hud.h
+index 3b702c3b..71779977 100644
+--- a/src/dxvk/hud/dxvk_hud.h
++++ b/src/dxvk/hud/dxvk_hud.h
+@@ -29,6 +29,8 @@ namespace dxvk::hud {
+ class Hud : public RcObject {
+
+ public:
++ static float offset_x_float;
++ static float offset_y_float;
+
+ Hud(
+ const Rc<DxvkDevice>& device,
+diff --git a/src/dxvk/hud/dxvk_hud_config.cpp b/src/dxvk/hud/dxvk_hud_config.cpp
+index fe1745bd..020a394b 100644
+--- a/src/dxvk/hud/dxvk_hud_config.cpp
++++ b/src/dxvk/hud/dxvk_hud_config.cpp
+@@ -16,6 +16,9 @@ namespace dxvk::hud {
+ { "version", HudElement::DxvkVersion },
+ { "api", HudElement::DxvkClientApi },
+ { "compiler", HudElement::CompilerActivity },
++ { "mangogpuload", HudElement::GpuLoad },
++ { "mangocpuload", HudElement::CpuLoad },
++ { "mangocpuload", HudElement::Logging },
+ }};
+
+
+diff --git a/src/dxvk/hud/dxvk_hud_config.h b/src/dxvk/hud/dxvk_hud_config.h
+index 05a1e4b6..5d571663 100644
+--- a/src/dxvk/hud/dxvk_hud_config.h
++++ b/src/dxvk/hud/dxvk_hud_config.h
+@@ -22,6 +22,9 @@ namespace dxvk::hud {
+ DxvkVersion = 8,
+ DxvkClientApi = 9,
+ CompilerActivity = 10,
++ GpuLoad = 11,
++ CpuLoad = 12,
++ Logging = 13,
+ };
+
+ using HudElements = Flags<HudElement>;
+diff --git a/src/dxvk/hud/dxvk_hud_fps.cpp b/src/dxvk/hud/dxvk_hud_fps.cpp
+index f8cb6e7d..84eb2db6 100644
+--- a/src/dxvk/hud/dxvk_hud_fps.cpp
++++ b/src/dxvk/hud/dxvk_hud_fps.cpp
+@@ -1,7 +1,22 @@
+ #include "dxvk_hud_fps.h"
++#include "dxvk_hud_stats.h"
++#include "../dxvk_cpu.h"
++#include <time.h>
+
+ #include <cmath>
+ #include <iomanip>
++using namespace std;
++time_t now_log = time(0);
++tm *log_time = localtime(&now_log);
++fstream f;
++
++struct logData{
++ float fps;
++ float cpu;
++ uint64_t gpu;
++};
++
++std::vector<logData> logArray;
+
+ namespace dxvk::hud {
+
+@@ -9,8 +24,8 @@ namespace dxvk::hud {
+ : m_elements (elements),
+ m_fpsString ("FPS: "),
+ m_prevFpsUpdate(Clock::now()),
+- m_prevFtgUpdate(Clock::now()) {
+-
++ m_prevFtgUpdate(Clock::now()),
++ m_prevLogUpdate(Clock::now()) {
+ }
+
+
+@@ -25,11 +40,48 @@ namespace dxvk::hud {
+ TimePoint now = Clock::now();
+ TimeDiff elapsedFps = std::chrono::duration_cast<TimeDiff>(now - m_prevFpsUpdate);
+ TimeDiff elapsedFtg = std::chrono::duration_cast<TimeDiff>(now - m_prevFtgUpdate);
++ TimeDiff elapsedLog = std::chrono::duration_cast<TimeDiff>(now - m_prevLogUpdate);
+ m_prevFtgUpdate = now;
++
++ if(GetAsyncKeyState(VK_F2) & 0x8000)
++ {
++ elapsedF2 = std::chrono::duration_cast<TimeDiff>(now - m_prevF2Press);
++ if (elapsedF2.count() > UpdateInterval || elapsedF2.count() == 0) {
++ if (mango_logging){
++ m_prevF2Press = now;
++ mango_logging = false;
++ for (size_t i = 0; i < logArray.size(); i++) {
++ f << logArray[i].fps << "," << logArray[i].cpu << "," << logArray[i].gpu << endl;
++ }
++ f.close();
++ logArray.clear();
++ } else {
++ m_prevF2Press = now;
++ now_log = time(0);
++ log_time = localtime(&now_log);
++ mango_logging = true;
++ string date = to_string(log_time->tm_year + 1900) + "-" + to_string(1 + log_time->tm_mon) + "-" + to_string(log_time->tm_mday) + "_" + to_string(1 + log_time->tm_hour) + "-" + to_string(1 + log_time->tm_min) + "-" + to_string(1 + log_time->tm_sec);
++ f.open(logging + "_" + date, f.out | f.app);
++ }
++ }
++ }
++
++ if (elapsedLog.count() >= LogUpdateInterval) {
++ fps = (10'000'000ll * m_frameCount) / elapsedFps.count();
++ if (!logging.empty()){
++ if (mango_logging){
++ logArray.push_back({float(fps / 10 + (float(fps % 10) / 10)), cpuArray[0].value, gpuLoad});
++ }
++ }
++ m_prevLogUpdate = now;
++ }
+
+- // Update FPS string
+ if (elapsedFps.count() >= UpdateInterval) {
+- const int64_t fps = (10'000'000ll * m_frameCount) / elapsedFps.count();
++ // Update FPS string
++ coreCounting();
++ dxvk::thread([this] () { getCpuUsage();});
++ updateCpuStrings();
++ m_cpuUtilizationString = str::format(cpuArray[0].output);
+ m_fpsString = str::format("FPS: ", fps / 10, ".", fps % 10);
+
+ m_prevFpsUpdate = now;
+@@ -46,32 +98,81 @@ namespace dxvk::hud {
+ const Rc<DxvkContext>& context,
+ HudRenderer& renderer,
+ HudPos position) {
++ if (m_elements.test(HudElement::GpuLoad)) {
++ position = this->renderGpuText(
++ context, renderer, position);
++ }
++ if (m_elements.test(HudElement::CpuLoad)) {
++ position = this->renderCpuText(
++ context, renderer, position);
++ }
+ if (m_elements.test(HudElement::Framerate)) {
+ position = this->renderFpsText(
+ context, renderer, position);
+- }
++ }
+
+ if (m_elements.test(HudElement::Frametimes)) {
+ position = this->renderFrametimeGraph(
+ context, renderer, position);
+ }
+
++ if (mango_logging && !logging.empty()) {
++ this->renderLogging(context, renderer,
++ { float(renderer.surfaceSize().width) - 250.0f, float(renderer.surfaceSize().height) - 20.0f });
++ }
++
+ return position;
+ }
+
+
+- HudPos HudFps::renderFpsText(
+- const Rc<DxvkContext>& context,
+- HudRenderer& renderer,
+- HudPos position) {
++
++ HudPos HudFps::renderGpuText(
++ const Rc<DxvkContext>& context,
++ HudRenderer& renderer,
++ HudPos position) {
++ renderer.drawText(context, 16.0f,
++ { position.x, position.y },
++ { 1.0f, 1.0f, 1.0f, 1.0f },
++ m_gpuLoadString);
++
++ return HudPos { position.x, position.y + 24 };
++}
++
++HudPos HudFps::renderCpuText(
++const Rc<DxvkContext>& context,
++ HudRenderer& renderer,
++ HudPos position) {
++renderer.drawText(context, 16.0f,
++ { position.x, position.y },
++ { 1.0f, 1.0f, 1.0f, 1.0f },
++ m_cpuUtilizationString);
++
++return HudPos { position.x, position.y + 24 };
++}
++
++HudPos HudFps::renderFpsText(
++ const Rc<DxvkContext>& context,
++ HudRenderer& renderer,
++ HudPos position) {
+ renderer.drawText(context, 16.0f,
+ { position.x, position.y },
+ { 1.0f, 1.0f, 1.0f, 1.0f },
+ m_fpsString);
++
++ return HudPos { position.x, position.y + 24 };
++ }
+
+- return HudPos { position.x, position.y + 24 };
+- }
+-
++ HudPos HudFps::renderLogging(
++ const Rc<DxvkContext>& context,
++ HudRenderer& renderer,
++ HudPos position) {
++ renderer.drawText(context, 16.0f,
++ { position.x, position.y },
++ { 1.0f, 1.0f, 1.0f, 1.0f },
++ "Currently Logging...");
++
++ return HudPos { position.x, position.y};
++ }
+
+ HudPos HudFps::renderFrametimeGraph(
+ const Rc<DxvkContext>& context,
+diff --git a/src/dxvk/hud/dxvk_hud_fps.h b/src/dxvk/hud/dxvk_hud_fps.h
+index c8c4b984..b1476889 100644
+--- a/src/dxvk/hud/dxvk_hud_fps.h
++++ b/src/dxvk/hud/dxvk_hud_fps.h
+@@ -17,8 +17,9 @@ namespace dxvk::hud {
+ using TimeDiff = std::chrono::microseconds;
+ using TimePoint = typename Clock::time_point;
+
+- constexpr static uint32_t NumDataPoints = 300;
+- constexpr static int64_t UpdateInterval = 500'000;
++ constexpr static uint32_t NumDataPoints = 300;
++ constexpr static int64_t UpdateInterval = 500'000;
++ constexpr static int64_t LogUpdateInterval = 100'000;
+ public:
+
+ HudFps(HudElements elements);
+@@ -36,23 +37,46 @@ namespace dxvk::hud {
+ const HudElements m_elements;
+
+ std::string m_fpsString;
++ bool mango_logging = false;
++ time_t lastPress = time(0);
++ std::string logging = env::getEnvVar("DXVK_LOG_TO_FILE");
++ int64_t fps;
+
+ TimePoint m_prevFpsUpdate;
+ TimePoint m_prevFtgUpdate;
++ TimePoint m_prevLogUpdate;
++ TimePoint m_prevF2Press;
++ TimeDiff elapsedF2;
+ int64_t m_frameCount = 0;
+
+ std::array<float, NumDataPoints> m_dataPoints = {};
+ uint32_t m_dataPointId = 0;
++
++ HudPos renderGpuText(
++ const Rc<DxvkContext>& context,
++ HudRenderer& renderer,
++ HudPos position);
++
++ HudPos renderCpuText(
++ const Rc<DxvkContext>& context,
++ HudRenderer& renderer,
++ HudPos position);
+
+ HudPos renderFpsText(
+ const Rc<DxvkContext>& context,
+ HudRenderer& renderer,
+ HudPos position);
+
++
+ HudPos renderFrametimeGraph(
+ const Rc<DxvkContext>& context,
+ HudRenderer& renderer,
+ HudPos position);
++
++ HudPos renderLogging(
++ const Rc<DxvkContext>& context,
++ HudRenderer& renderer,
++ HudPos position);
+
+ };
+
+diff --git a/src/dxvk/hud/dxvk_hud_stats.cpp b/src/dxvk/hud/dxvk_hud_stats.cpp
+index 995f186b..be8a5eb7 100644
+--- a/src/dxvk/hud/dxvk_hud_stats.cpp
++++ b/src/dxvk/hud/dxvk_hud_stats.cpp
+@@ -1,5 +1,7 @@
+ #include "dxvk_hud_stats.h"
+
++std::string m_gpuLoadString = "GPU: ";
++uint64_t gpuLoad;
+ namespace dxvk::hud {
+
+ HudStats::HudStats(HudElements elements)
+@@ -24,7 +26,7 @@ namespace dxvk::hud {
+
+ // GPU load is a bit more complex than that since
+ // we don't want to update this every frame
+- if (m_elements.test(HudElement::StatGpuLoad))
++ if (m_elements.test(HudElement::GpuLoad) || m_elements.test(HudElement::StatGpuLoad))
+ this->updateGpuLoad();
+ }
+
+@@ -70,8 +72,8 @@ namespace dxvk::hud {
+ uint64_t busyTicks = ticks > m_diffGpuIdleTicks
+ ? uint64_t(ticks - m_diffGpuIdleTicks)
+ : uint64_t(0);
+-
+- m_gpuLoadString = str::format("GPU: ", (100 * busyTicks) / ticks, "%");
++ gpuLoad = 100 * busyTicks / ticks;
++ m_gpuLoadString = str::format("GPU: ", (100 * busyTicks) / ticks, "%");
+ }
+ }
+
+@@ -224,7 +226,8 @@ namespace dxvk::hud {
+ HudElement::StatPipelines,
+ HudElement::StatMemory,
+ HudElement::StatGpuLoad,
+- HudElement::CompilerActivity);
++ HudElement::CompilerActivity,
++ HudElement::GpuLoad);
+ }
+
+ }
+diff --git a/src/dxvk/hud/dxvk_hud_stats.h b/src/dxvk/hud/dxvk_hud_stats.h
+index 227f600c..b7d740f1 100644
+--- a/src/dxvk/hud/dxvk_hud_stats.h
++++ b/src/dxvk/hud/dxvk_hud_stats.h
+@@ -7,6 +7,8 @@
+ #include "dxvk_hud_config.h"
+ #include "dxvk_hud_renderer.h"
+
++extern std::string m_gpuLoadString;
++extern uint64_t gpuLoad;
+ namespace dxvk::hud {
+
+ /**
+@@ -44,8 +46,6 @@ namespace dxvk::hud {
+ uint64_t m_prevGpuIdleTicks = 0;
+ uint64_t m_diffGpuIdleTicks = 0;
+
+- std::string m_gpuLoadString = "GPU: ";
+-
+ void updateGpuLoad();
+
+ HudPos printDrawCallStats(
diff --git a/extraopts.patch b/extraopts.patch
new file mode 100644
index 000000000000..43a8fd6d10ec
--- /dev/null
+++ b/extraopts.patch
@@ -0,0 +1,70 @@
+diff --git a/build-win32.txt b/build-win32.txt
+index 0865fc5e..b93a280f 100644
+--- a/build-win32.txt
++++ b/build-win32.txt
+@@ -5,10 +5,10 @@ ar = 'i686-w64-mingw32-ar'
+ strip = 'i686-w64-mingw32-strip'
+
+ [properties]
+-c_args=['-msse', '-msse2']
+-cpp_args=['-msse', '-msse2']
+-c_link_args = ['-static', '-static-libgcc']
+-cpp_link_args = ['-static', '-static-libgcc', '-static-libstdc++']
++c_args=['-msse', '-msse2', @CARGS@]
++cpp_args=['-msse', '-msse2', @CARGS@]
++c_link_args = ['-static', '-static-libgcc', @LDARGS@]
++cpp_link_args = ['-static', '-static-libgcc', '-static-libstdc++', @LDARGS@]
+ needs_exe_wrapper = true
+
+ [host_machine]
+diff --git a/build-win64.txt b/build-win64.txt
+index 2a7fbee3..9e3bfc2e 100644
+--- a/build-win64.txt
++++ b/build-win64.txt
+@@ -5,8 +5,10 @@ ar = 'x86_64-w64-mingw32-ar'
+ strip = 'x86_64-w64-mingw32-strip'
+
+ [properties]
+-c_link_args = ['-static', '-static-libgcc']
+-cpp_link_args = ['-static', '-static-libgcc', '-static-libstdc++']
++c_args=[@CARGS@]
++cpp_args=[@CARGS@]
++c_link_args = ['-static', '-static-libgcc', @LDARGS@]
++cpp_link_args = ['-static', '-static-libgcc', '-static-libstdc++', @LDARGS@]
+ needs_exe_wrapper = true
+
+ [host_machine]
+diff --git a/build-wine32.txt b/build-wine32.txt
+index ba8d34a8..ecec2eb4 100644
+--- a/build-wine32.txt
++++ b/build-wine32.txt
+@@ -7,9 +7,9 @@ strip = 'strip'
+ [properties]
+ needs_exe_wrapper = true
+
+-c_args=['-m32', '-msse', '-msse2', '-fvisibility=hidden']
+-cpp_args=['-m32', '-msse', '-msse2', '-fvisibility=hidden', '-fvisibility-inlines-hidden', '-D__WIDL_objidl_generated_name_0000000C=']
+-cpp_link_args=['-m32', '-mwindows']
++c_args=['-m32', '-msse', '-msse2', '-fvisibility=hidden', @CARGS@]
++cpp_args=['-m32', '-msse', '-msse2', '-fvisibility=hidden', '-fvisibility-inlines-hidden', '-D__WIDL_objidl_generated_name_0000000C=', @CARGS@]
++cpp_link_args=['-m32', '-mwindows', @LDARGS@]
+
+ [host_machine]
+ system = 'linux'
+diff --git a/build-wine64.txt b/build-wine64.txt
+index b3e028bb..fb9d98fd 100644
+--- a/build-wine64.txt
++++ b/build-wine64.txt
+@@ -7,9 +7,9 @@ strip = 'strip'
+ [properties]
+ needs_exe_wrapper = true
+
+-c_args=['-m64', '-fvisibility=hidden']
+-cpp_args=['-m64', '-fvisibility=hidden', '-fvisibility-inlines-hidden', '-D__WIDL_objidl_generated_name_0000000C=']
+-cpp_link_args=['-m64', '-mwindows']
++c_args=['-m64', '-fvisibility=hidden', @CARGS@]
++cpp_args=['-m64', '-fvisibility=hidden', '-fvisibility-inlines-hidden', '-D__WIDL_objidl_generated_name_0000000C=', @CARGS@]
++cpp_link_args=['-m64', '-mwindows', @LDARGS@]
+
+ [host_machine]
+ system = 'linux'
diff --git a/setup_dxvk b/setup_dxvk
new file mode 100644
index 000000000000..01a7b908b232
--- /dev/null
+++ b/setup_dxvk
@@ -0,0 +1,2 @@
+#!/bin/sh
+/usr/share/dxvk/setup_dxvk.sh $1 --symlink