summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthony <Helios747@users.noreply.github.com>2017-07-30 00:42:36 -0700
committerGitHub <noreply@github.com>2017-07-30 00:42:36 -0700
commit5bad2ee4a4129e734ff4f4d861cedc4cd573df55 (patch)
treecd3c3ccc6e35417858499449d774670c3983cb13
parent5e6d5493165d1214f513b91eaeb4e162d21fdd3e (diff)
parent357c55442f3dbdf6f23d16af5362d1a615cb84c1 (diff)
Merge pull request #5832 from stenzek/ubershader-prereq
Ubershader prerequisites
-rw-r--r--Source/Android/jni/MainAndroid.cpp4
-rw-r--r--Source/Core/Common/BitField.h2
-rw-r--r--Source/Core/Common/GL/GLInterface/AGL.h8
-rw-r--r--Source/Core/Common/GL/GLInterface/AGL.mm33
-rw-r--r--Source/Core/Common/GL/GLInterface/EGL.cpp26
-rw-r--r--Source/Core/Common/GL/GLInterface/EGL.h2
-rw-r--r--Source/Core/Common/GL/GLInterface/GLX.cpp174
-rw-r--r--Source/Core/Common/GL/GLInterface/GLX.h13
-rw-r--r--Source/Core/Core/Host.h1
-rw-r--r--Source/Core/DolphinNoGUI/MainNoGUI.cpp4
-rw-r--r--Source/Core/DolphinQt2/Host.cpp3
-rw-r--r--Source/Core/DolphinWX/Frame.cpp32
-rw-r--r--Source/Core/DolphinWX/Frame.h2
-rw-r--r--Source/Core/DolphinWX/Globals.h1
-rw-r--r--Source/Core/DolphinWX/Main.cpp9
-rw-r--r--Source/Core/VideoBackends/Vulkan/CMakeLists.txt1
-rw-r--r--Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp26
-rw-r--r--Source/Core/VideoBackends/Vulkan/ObjectCache.cpp987
-rw-r--r--Source/Core/VideoBackends/Vulkan/ObjectCache.h123
-rw-r--r--Source/Core/VideoBackends/Vulkan/PostProcessing.cpp7
-rw-r--r--Source/Core/VideoBackends/Vulkan/Renderer.cpp10
-rw-r--r--Source/Core/VideoBackends/Vulkan/ShaderCache.cpp1029
-rw-r--r--Source/Core/VideoBackends/Vulkan/ShaderCache.h172
-rw-r--r--Source/Core/VideoBackends/Vulkan/ShaderCompiler.cpp78
-rw-r--r--Source/Core/VideoBackends/Vulkan/ShaderCompiler.h8
-rw-r--r--Source/Core/VideoBackends/Vulkan/StateTracker.cpp17
-rw-r--r--Source/Core/VideoBackends/Vulkan/StateTracker.h2
-rw-r--r--Source/Core/VideoBackends/Vulkan/TextureCache.cpp6
-rw-r--r--Source/Core/VideoBackends/Vulkan/TextureConverter.cpp27
-rw-r--r--Source/Core/VideoBackends/Vulkan/Util.cpp33
-rw-r--r--Source/Core/VideoBackends/Vulkan/Util.h14
-rw-r--r--Source/Core/VideoBackends/Vulkan/VKTexture.cpp4
-rw-r--r--Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj2
-rw-r--r--Source/Core/VideoBackends/Vulkan/VulkanContext.cpp9
-rw-r--r--Source/Core/VideoBackends/Vulkan/VulkanContext.h3
-rw-r--r--Source/Core/VideoBackends/Vulkan/main.cpp11
-rw-r--r--Source/Core/VideoCommon/FPSCounter.cpp32
-rw-r--r--Source/Core/VideoCommon/FPSCounter.h14
-rw-r--r--Source/Core/VideoCommon/RenderBase.cpp2
-rw-r--r--Source/UnitTests/StubHost.cpp3
40 files changed, 1646 insertions, 1288 deletions
diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp
index cda73a9fe5..fa92372f6a 100644
--- a/Source/Android/jni/MainAndroid.cpp
+++ b/Source/Android/jni/MainAndroid.cpp
@@ -148,6 +148,10 @@ void Host_YieldToUI()
{
}
+void Host_UpdateProgressDialog(const char* caption, int position, int total)
+{
+}
+
static bool MsgAlert(const char* caption, const char* text, bool yes_no, int /*Style*/)
{
__android_log_print(ANDROID_LOG_ERROR, DOLPHIN_TAG, "%s:%s", caption, text);
diff --git a/Source/Core/Common/BitField.h b/Source/Core/Common/BitField.h
index 85a595239c..59bee7b4b5 100644
--- a/Source/Core/Common/BitField.h
+++ b/Source/Core/Common/BitField.h
@@ -141,6 +141,8 @@ public:
constexpr T Value() const { return Value(std::is_signed<T>()); }
constexpr operator T() const { return Value(); }
+ constexpr std::size_t StartBit() const { return position; }
+ constexpr std::size_t NumBits() const { return bits; }
private:
// StorageType is T for non-enum types and the underlying type of T if
// T is an enumeration. Note that T is wrapped within an enable_if in the
diff --git a/Source/Core/Common/GL/GLInterface/AGL.h b/Source/Core/Common/GL/GLInterface/AGL.h
index 5f9a138282..8d62086954 100644
--- a/Source/Core/Common/GL/GLInterface/AGL.h
+++ b/Source/Core/Common/GL/GLInterface/AGL.h
@@ -8,6 +8,7 @@
#import <AppKit/AppKit.h>
#else
struct NSOpenGLContext;
+struct NSOpenGLPixelFormat;
struct NSView;
#endif
@@ -18,13 +19,16 @@ class cInterfaceAGL : public cInterfaceBase
public:
void Swap() override;
bool Create(void* window_handle, bool stereo, bool core) override;
+ bool Create(cInterfaceBase* main_context) override;
bool MakeCurrent() override;
bool ClearCurrent() override;
void Shutdown() override;
void Update() override;
void SwapInterval(int interval) override;
+ std::unique_ptr<cInterfaceBase> CreateSharedContext() override;
private:
- NSView* m_view;
- NSOpenGLContext* m_context;
+ NSView* m_view = nullptr;
+ NSOpenGLContext* m_context = nullptr;
+ NSOpenGLPixelFormat* m_pixel_format = nullptr;
};
diff --git a/Source/Core/Common/GL/GLInterface/AGL.mm b/Source/Core/Common/GL/GLInterface/AGL.mm
index bdae829cbc..07502475f7 100644
--- a/Source/Core/Common/GL/GLInterface/AGL.mm
+++ b/Source/Core/Common/GL/GLInterface/AGL.mm
@@ -60,15 +60,14 @@ bool cInterfaceAGL::Create(void* window_handle, bool stereo, bool core)
NSOpenGLPFAAccelerated,
stereo ? NSOpenGLPFAStereo : static_cast<NSOpenGLPixelFormatAttribute>(0),
0};
- NSOpenGLPixelFormat* fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
- if (fmt == nil)
+ m_pixel_format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
+ if (m_pixel_format == nil)
{
ERROR_LOG(VIDEO, "failed to create pixel format");
return false;
}
- m_context = [[NSOpenGLContext alloc] initWithFormat:fmt shareContext:nil];
- [fmt release];
+ m_context = [[NSOpenGLContext alloc] initWithFormat:m_pixel_format shareContext:nil];
if (m_context == nil)
{
ERROR_LOG(VIDEO, "failed to create context");
@@ -82,6 +81,30 @@ bool cInterfaceAGL::Create(void* window_handle, bool stereo, bool core)
return AttachContextToView(m_context, m_view, &s_backbuffer_width, &s_backbuffer_height);
}
+bool cInterfaceAGL::Create(cInterfaceBase* main_context)
+{
+ cInterfaceAGL* agl_context = static_cast<cInterfaceAGL*>(main_context);
+ NSOpenGLPixelFormat* pixel_format = agl_context->m_pixel_format;
+ NSOpenGLContext* share_context = agl_context->m_context;
+
+ m_context = [[NSOpenGLContext alloc] initWithFormat:pixel_format shareContext:share_context];
+ if (m_context == nil)
+ {
+ ERROR_LOG(VIDEO, "failed to create shared context");
+ return false;
+ }
+
+ return true;
+}
+
+std::unique_ptr<cInterfaceBase> cInterfaceAGL::CreateSharedContext()
+{
+ std::unique_ptr<cInterfaceBase> context = std::make_unique<cInterfaceAGL>();
+ if (!context->Create(this))
+ return nullptr;
+ return context;
+}
+
bool cInterfaceAGL::MakeCurrent()
{
[m_context makeCurrentContext];
@@ -100,6 +123,8 @@ void cInterfaceAGL::Shutdown()
[m_context clearDrawable];
[m_context release];
m_context = nil;
+ [m_pixel_format release];
+ m_pixel_format = nil;
}
void cInterfaceAGL::Update()
diff --git a/Source/Core/Common/GL/GLInterface/EGL.cpp b/Source/Core/Common/GL/GLInterface/EGL.cpp
index 32cd705d73..8e85511fc7 100644
--- a/Source/Core/Common/GL/GLInterface/EGL.cpp
+++ b/Source/Core/Common/GL/GLInterface/EGL.cpp
@@ -212,7 +212,10 @@ bool cInterfaceEGL::Create(void* window_handle, bool stereo, bool core)
egl_ctx = eglCreateContext(egl_dpy, m_config, EGL_NO_CONTEXT, &core_attribs[0]);
if (egl_ctx)
+ {
+ m_attribs = std::move(core_attribs);
break;
+ }
}
}
@@ -220,6 +223,7 @@ bool cInterfaceEGL::Create(void* window_handle, bool stereo, bool core)
{
m_core = false;
egl_ctx = eglCreateContext(egl_dpy, m_config, EGL_NO_CONTEXT, &ctx_attribs[0]);
+ m_attribs = std::move(ctx_attribs);
}
if (!egl_ctx)
@@ -255,31 +259,13 @@ bool cInterfaceEGL::Create(cInterfaceBase* main_context)
m_is_shared = true;
m_has_handle = false;
- EGLint ctx_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
-
- switch (egl_context->GetMode())
- {
- case GLInterfaceMode::MODE_OPENGL:
- ctx_attribs[0] = EGL_NONE;
- break;
- case GLInterfaceMode::MODE_OPENGLES2:
- ctx_attribs[1] = 2;
- break;
- case GLInterfaceMode::MODE_OPENGLES3:
- ctx_attribs[1] = 3;
- break;
- default:
- INFO_LOG(VIDEO, "Unknown opengl mode set");
- return false;
- break;
- }
-
if (egl_context->GetMode() == GLInterfaceMode::MODE_OPENGL)
eglBindAPI(EGL_OPENGL_API);
else
eglBindAPI(EGL_OPENGL_ES_API);
- egl_ctx = eglCreateContext(egl_dpy, m_config, egl_context->egl_ctx, ctx_attribs);
+ egl_ctx =
+ eglCreateContext(egl_dpy, m_config, egl_context->egl_ctx, egl_context->m_attribs.data());
if (!egl_ctx)
{
INFO_LOG(VIDEO, "Error: eglCreateContext failed 0x%04x", eglGetError());
diff --git a/Source/Core/Common/GL/GLInterface/EGL.h b/Source/Core/Common/GL/GLInterface/EGL.h
index 69a351851f..16a716e3c2 100644
--- a/Source/Core/Common/GL/GLInterface/EGL.h
+++ b/Source/Core/Common/GL/GLInterface/EGL.h
@@ -7,6 +7,7 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <string>
+#include <vector>
#include "Common/GL/GLInterfaceBase.h"
@@ -17,6 +18,7 @@ private:
bool m_has_handle;
EGLNativeWindowType m_host_window;
bool m_supports_surfaceless = false;
+ std::vector<int> m_attribs;
bool CreateWindowSurface();
void DestroyWindowSurface();
diff --git a/Source/Core/Common/GL/GLInterface/GLX.cpp b/Source/Core/Common/GL/GLInterface/GLX.cpp
index f6fea067ef..e6a018e1d3 100644
--- a/Source/Core/Common/GL/GLInterface/GLX.cpp
+++ b/Source/Core/Common/GL/GLInterface/GLX.cpp
@@ -2,7 +2,8 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.
-#include <string>
+#include <array>
+#include <sstream>
#include "Common/GL/GLInterface/GLX.h"
#include "Common/Logging/Log.h"
@@ -17,6 +18,9 @@ typedef int (*PFNGLXSWAPINTERVALSGIPROC)(int interval);
static PFNGLXCREATECONTEXTATTRIBSPROC glXCreateContextAttribs = nullptr;
static PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI = nullptr;
+static PFNGLXCREATEGLXPBUFFERSGIXPROC glXCreateGLXPbufferSGIX = nullptr;
+static PFNGLXDESTROYGLXPBUFFERSGIXPROC glXDestroyGLXPbufferSGIX = nullptr;
+
static bool s_glxError;
static int ctxErrorHandler(Display* dpy, XErrorEvent* ev)
{
@@ -26,7 +30,7 @@ static int ctxErrorHandler(Display* dpy, XErrorEvent* ev)
void cInterfaceGLX::SwapInterval(int Interval)
{
- if (glXSwapIntervalSGI)
+ if (glXSwapIntervalSGI && m_has_handle)
glXSwapIntervalSGI(Interval);
else
ERROR_LOG(VIDEO, "No support for SwapInterval (framerate clamped to monitor refresh rate).");
@@ -45,6 +49,9 @@ void cInterfaceGLX::Swap()
// Call browser: Core.cpp:EmuThread() > main.cpp:Video_Initialize()
bool cInterfaceGLX::Create(void* window_handle, bool stereo, bool core)
{
+ m_has_handle = !!window_handle;
+ m_host_window = (Window)window_handle;
+
dpy = XOpenDisplay(nullptr);
int screen = DefaultScreen(dpy);
@@ -100,51 +107,43 @@ bool cInterfaceGLX::Create(void* window_handle, bool stereo, bool core)
fbconfig = *fbc;
XFree(fbc);
- // Get an appropriate visual
- XVisualInfo* vi = glXGetVisualFromFBConfig(dpy, fbconfig);
-
s_glxError = false;
XErrorHandler oldHandler = XSetErrorHandler(&ctxErrorHandler);
// Create a GLX context.
// We try to get a 4.0 core profile, else we try 3.3, else try it with anything we get.
- int context_attribs[] = {GLX_CONTEXT_MAJOR_VERSION_ARB,
- 4,
- GLX_CONTEXT_MINOR_VERSION_ARB,
- 0,
- GLX_CONTEXT_PROFILE_MASK_ARB,
- GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
- GLX_CONTEXT_FLAGS_ARB,
- GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
- None};
+ std::array<int, 9> context_attribs = {
+ {GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 0,
+ GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, GLX_CONTEXT_FLAGS_ARB,
+ GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, None}};
ctx = nullptr;
if (core)
{
- ctx = glXCreateContextAttribs(dpy, fbconfig, 0, True, context_attribs);
+ ctx = glXCreateContextAttribs(dpy, fbconfig, 0, True, &context_attribs[0]);
XSync(dpy, False);
+ m_attribs.insert(m_attribs.end(), context_attribs.begin(), context_attribs.end());
}
if (core && (!ctx || s_glxError))
{
- int context_attribs_33[] = {GLX_CONTEXT_MAJOR_VERSION_ARB,
- 3,
- GLX_CONTEXT_MINOR_VERSION_ARB,
- 3,
- GLX_CONTEXT_PROFILE_MASK_ARB,
- GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
- GLX_CONTEXT_FLAGS_ARB,
- GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
- None};
+ std::array<int, 9> context_attribs_33 = {
+ {GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 3,
+ GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, GLX_CONTEXT_FLAGS_ARB,
+ GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, None}};
s_glxError = false;
- ctx = glXCreateContextAttribs(dpy, fbconfig, 0, True, context_attribs_33);
+ ctx = glXCreateContextAttribs(dpy, fbconfig, 0, True, &context_attribs_33[0]);
XSync(dpy, False);
+ m_attribs.clear();
+ m_attribs.insert(m_attribs.end(), context_attribs_33.begin(), context_attribs_33.end());
}
if (!ctx || s_glxError)
{
- int context_attribs_legacy[] = {GLX_CONTEXT_MAJOR_VERSION_ARB, 1, GLX_CONTEXT_MINOR_VERSION_ARB,
- 0, None};
+ std::array<int, 5> context_attribs_legacy = {
+ {GLX_CONTEXT_MAJOR_VERSION_ARB, 1, GLX_CONTEXT_MINOR_VERSION_ARB, 0, None}};
s_glxError = false;
- ctx = glXCreateContextAttribs(dpy, fbconfig, 0, True, context_attribs_legacy);
+ ctx = glXCreateContextAttribs(dpy, fbconfig, 0, True, &context_attribs_legacy[0]);
XSync(dpy, False);
+ m_attribs.clear();
+ m_attribs.insert(m_attribs.end(), context_attribs_legacy.begin(), context_attribs_legacy.end());
}
if (!ctx || s_glxError)
{
@@ -153,30 +152,116 @@ bool cInterfaceGLX::Create(void* window_handle, bool stereo, bool core)
}
XSetErrorHandler(oldHandler);
- XWindow.Initialize(dpy);
+ std::string tmp;
+ std::istringstream buffer(glXQueryExtensionsString(dpy, screen));
+ while (buffer >> tmp)
+ {
+ if (tmp == "GLX_SGIX_pbuffer")
+ m_supports_pbuffer = true;
+ }
- Window parent = (Window)window_handle;
+ if (m_supports_pbuffer)
+ {
+ // Get the function pointers we require
+ glXCreateGLXPbufferSGIX =
+ (PFNGLXCREATEGLXPBUFFERSGIXPROC)GetFuncAddress("glXCreateGLXPbufferSGIX");
+ glXDestroyGLXPbufferSGIX =
+ (PFNGLXDESTROYGLXPBUFFERSGIXPROC)GetFuncAddress("glXDestroyGLXPbufferSGIX");
+ }
- XWindowAttributes attribs;
- if (!XGetWindowAttributes(dpy, parent, &attribs))
+ if (!CreateWindowSurface())
{
- ERROR_LOG(VIDEO, "Window attribute retrieval failed");
+ ERROR_LOG(VIDEO, "Error: CreateWindowSurface failed\n");
return false;
}
+ return true;
+}
+
+bool cInterfaceGLX::Create(cInterfaceBase* main_context)
+{
+ cInterfaceGLX* glx_context = static_cast<cInterfaceGLX*>(main_context);
- s_backbuffer_width = attribs.width;
- s_backbuffer_height = attribs.height;
+ m_has_handle = false;
+ dpy = glx_context->dpy;
+ fbconfig = glx_context->fbconfig;
+ s_glxError = false;
+ XErrorHandler oldHandler = XSetErrorHandler(&ctxErrorHandler);
- win = XWindow.CreateXWindow(parent, vi);
- XFree(vi);
+ ctx = glXCreateContextAttribs(dpy, fbconfig, glx_context->ctx, True, &glx_context->m_attribs[0]);
+ XSync(dpy, False);
+ if (!ctx || s_glxError)
+ {
+ ERROR_LOG(VIDEO, "Unable to create GL context.");
+ return false;
+ }
+ XSetErrorHandler(oldHandler);
+
+ if (!CreateWindowSurface())
+ {
+ ERROR_LOG(VIDEO, "Error: CreateWindowSurface failed\n");
+ return false;
+ }
return true;
}
+std::unique_ptr<cInterfaceBase> cInterfaceGLX::CreateSharedContext()
+{
+ std::unique_ptr<cInterfaceBase> context = std::make_unique<cInterfaceGLX>();
+ if (!context->Create(this))
+ return nullptr;
+ return context;
+}
+
+bool cInterfaceGLX::CreateWindowSurface()
+{
+ if (m_has_handle)
+ {
+ // Get an appropriate visual
+ XVisualInfo* vi = glXGetVisualFromFBConfig(dpy, fbconfig);
+
+ XWindow.Initialize(dpy);
+
+ XWindowAttributes attribs;
+ if (!XGetWindowAttributes(dpy, m_host_window, &attribs))
+ {
+ ERROR_LOG(VIDEO, "Window attribute retrieval failed");
+ return false;
+ }
+
+ s_backbuffer_width = attribs.width;
+ s_backbuffer_height = attribs.height;
+
+ win = XWindow.CreateXWindow(m_host_window, vi);
+ XFree(vi);
+ }
+ else
+ {
+ win = m_pbuffer = glXCreateGLXPbufferSGIX(dpy, fbconfig, 1, 1, nullptr);
+ if (!m_pbuffer)
+ return false;
+ }
+
+ return true;
+}
+
+void cInterfaceGLX::DestroyWindowSurface()
+{
+ if (!m_pbuffer)
+ {
+ XWindow.DestroyXWindow();
+ }
+ else
+ {
+ glXDestroyGLXPbufferSGIX(dpy, m_pbuffer);
+ m_pbuffer = 0;
+ }
+}
+
bool cInterfaceGLX::MakeCurrent()
{
bool success = glXMakeCurrent(dpy, win, ctx);
- if (success)
+ if (success && !glXSwapIntervalSGI)
{
// load this function based on the current bound context
glXSwapIntervalSGI =
@@ -193,11 +278,18 @@ bool cInterfaceGLX::ClearCurrent()
// Close backend
void cInterfaceGLX::Shutdown()
{
- XWindow.DestroyXWindow();
+ DestroyWindowSurface();
if (ctx)
{
glXDestroyContext(dpy, ctx);
- XCloseDisplay(dpy);
- ctx = nullptr;
+
+ // Don't close the display connection if we are a shared context.
+ // Saves doing reference counting on this object, and the main context will always
+ // be shut down last anyway.
+ if (m_has_handle)
+ {
+ XCloseDisplay(dpy);
+ ctx = nullptr;
+ }
}
}
diff --git a/Source/Core/Common/GL/GLInterface/GLX.h b/Source/Core/Common/GL/GLInterface/GLX.h
index ae019ccbf9..724dca5141 100644
--- a/Source/Core/Common/GL/GLInterface/GLX.h
+++ b/Source/Core/Common/GL/GLInterface/GLX.h
@@ -5,7 +5,10 @@
#pragma once
#include <GL/glx.h>
+#include <GL/glxext.h>
+#include <memory>
#include <string>
+#include <vector>
#include "Common/GL/GLInterface/X11_Util.h"
#include "Common/GL/GLInterfaceBase.h"
@@ -13,11 +16,19 @@
class cInterfaceGLX : public cInterfaceBase
{
private:
+ Window m_host_window;
cX11Window XWindow;
Display* dpy;
Window win;
GLXContext ctx;
GLXFBConfig fbconfig;
+ bool m_has_handle;
+ bool m_supports_pbuffer = false;
+ GLXPbufferSGIX m_pbuffer = 0;
+ std::vector<int> m_attribs;
+
+ bool CreateWindowSurface();
+ void DestroyWindowSurface();
public:
friend class cX11Window;
@@ -25,7 +36,9 @@ public:
void Swap() override;
void* GetFuncAddress(const std::string& name) override;
bool Create(void* window_handle, bool stereo, bool core) override;
+ bool Create(cInterfaceBase* main_context) override;
bool MakeCurrent() override;
bool ClearCurrent() override;
void Shutdown() override;
+ std::unique_ptr<cInterfaceBase> CreateSharedContext() override;
};
diff --git a/Source/Core/Core/Host.h b/Source/Core/Core/Host.h
index 0a8a8231ae..acbea49ef7 100644
--- a/Source/Core/Core/Host.h
+++ b/Source/Core/Core/Host.h
@@ -35,6 +35,7 @@ void Host_UpdateMainFrame();
void Host_UpdateTitle(const std::string& title);
void Host_ShowVideoConfig(void* parent, const std::string& backend_name);
void Host_YieldToUI();
+void Host_UpdateProgressDialog(const char* caption, int position, int total);
// TODO (neobrain): Remove this from host!
void* Host_GetRenderHandle();
diff --git a/Source/Core/DolphinNoGUI/MainNoGUI.cpp b/Source/Core/DolphinNoGUI/MainNoGUI.cpp
index fc91ccac04..37860057aa 100644
--- a/Source/Core/DolphinNoGUI/MainNoGUI.cpp
+++ b/Source/Core/DolphinNoGUI/MainNoGUI.cpp
@@ -136,6 +136,10 @@ void Host_YieldToUI()
{
}
+void Host_UpdateProgressDialog(const char* caption, int position, int total)
+{
+}
+
#if HAVE_X11
#include <X11/Xlib.h>
#include <X11/keysym.h>
diff --git a/Source/Core/DolphinQt2/Host.cpp b/Source/Core/DolphinQt2/Host.cpp
index 2509d8c9b9..c39207b252 100644
--- a/Source/Core/DolphinQt2/Host.cpp
+++ b/Source/Core/DolphinQt2/Host.cpp
@@ -84,6 +84,9 @@ void Host_YieldToUI()
{
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
}
+void Host_UpdateProgressDialog(const char* caption, int position, int total)
+{
+}
// We ignore these, and their purpose should be questioned individually.
// In particular, RequestRenderWindowSize, RequestFullscreen, and
diff --git a/Source/Core/DolphinWX/Frame.cpp b/Source/Core/DolphinWX/Frame.cpp
index 28e25b4806..08bbcc4a52 100644
--- a/Source/Core/DolphinWX/Frame.cpp
+++ b/Source/Core/DolphinWX/Frame.cpp
@@ -20,6 +20,7 @@
#include <wx/menu.h>
#include <wx/msgdlg.h>
#include <wx/panel.h>
+#include <wx/progdlg.h>
#include <wx/sizer.h>
#include <wx/statusbr.h>
#include <wx/textctrl.h>
@@ -817,6 +818,37 @@ void CFrame::OnHostMessage(wxCommandEvent& event)
case IDM_STOPPED:
OnStopped();
break;
+
+ case IDM_UPDATE_PROGRESS_DIALOG:
+ {
+ int current = event.GetInt();
+ int total = static_cast<int>(event.GetExtraLong());
+ if (total < 0 || current >= total)
+ {
+ if (m_progress_dialog)
+ {
+ delete m_progress_dialog;
+ m_progress_dialog = nullptr;
+ }
+ }
+ else if (total > 0 && current < total)
+ {
+ if (!m_progress_dialog)
+ {
+ m_progress_dialog = new wxProgressDialog(
+ _("Operation in progress..."), event.GetString(), total, m_render_frame,
+ wxPD_APP_MODAL | wxPD_ELAPSED_TIME | wxPD_SMOOTH | wxPD_REMAINING_TIME);
+ m_progress_dialog->Show();
+ }
+ else
+ {
+ if (m_progress_dialog->GetRange() != total)
+ m_progress_dialog->SetRange(total);
+ m_progress_dialog->Update(current, event.GetString());
+ }
+ }
+ }
+ break;
}
}
diff --git a/Source/Core/DolphinWX/Frame.h b/Source/Core/DolphinWX/Frame.h
index afc555af7a..918447e513 100644
--- a/Source/Core/DolphinWX/Frame.h
+++ b/Source/Core/DolphinWX/Frame.h
@@ -49,6 +49,7 @@ class wxAuiNotebook;
class wxAuiNotebookEvent;
class wxListEvent;
class wxMenuItem;
+class wxProgressDialog;
class CRenderFrame : public wxFrame
{
@@ -154,6 +155,7 @@ private:
FifoPlayerDlg* m_fifo_player_dialog = nullptr;
std::array<TASInputDlg*, 8> m_tas_input_dialogs{};
wxCheatsWindow* m_cheats_window = nullptr;
+ wxProgressDialog* m_progress_dialog = nullptr;
bool m_use_debugger = false;
bool m_batch_mode = false;
bool m_editing_perspectives = false;
diff --git a/Source/Core/DolphinWX/Globals.h b/Source/Core/DolphinWX/Globals.h
index d11f42efae..0ade18f159 100644
--- a/Source/Core/DolphinWX/Globals.h
+++ b/Source/Core/DolphinWX/Globals.h
@@ -307,6 +307,7 @@ enum
IDM_WINDOW_SIZE_REQUEST,
IDM_STOPPED,
IDM_HOST_MESSAGE,
+ IDM_UPDATE_PROGRESS_DIALOG,
IDM_MPANEL,
ID_STATUSBAR,
diff --git a/Source/Core/DolphinWX/Main.cpp b/Source/Core/DolphinWX/Main.cpp
index 2ef465531a..1a5fbad188 100644
--- a/Source/Core/DolphinWX/Main.cpp
+++ b/Source/Core/DolphinWX/Main.cpp
@@ -488,3 +488,12 @@ void Host_YieldToUI()
{
wxGetApp().GetMainLoop()->YieldFor(wxEVT_CATEGORY_UI);
}
+
+void Host_UpdateProgressDialog(const char* caption, int position, int total)
+{
+ wxCommandEvent event(wxEVT_HOST_COMMAND, IDM_UPDATE_PROGRESS_DIALOG);
+ event.SetString(caption);
+ event.SetInt(position);
+ event.SetExtraLong(total);
+ main_frame->GetEventHandler()->AddPendingEvent(event);
+}
diff --git a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt
index 744b471d73..f3d602afb6 100644
--- a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt
+++ b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt
@@ -7,6 +7,7 @@ set(SRCS
PostProcessing.cpp
RasterFont.cpp
Renderer.cpp
+ ShaderCache.cpp
ShaderCompiler.cpp
StateTracker.cpp
StagingBuffer.cpp
diff --git a/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp b/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp
index be751f636e..dec6d9e952 100644
--- a/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp
+++ b/Source/Core/VideoBackends/Vulkan/FramebufferManager.cpp
@@ -435,8 +435,8 @@ void FramebufferManager::ReinterpretPixelData(int convtype)
UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(),
g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD),
- m_efb_load_render_pass, g_object_cache->GetScreenQuadVertexShader(),
- g_object_cache->GetScreenQuadGeometryShader(), pixel_shader);
+ m_efb_load_render_pass, g_shader_cache->GetScreenQuadVertexShader(),
+ g_shader_cache->GetScreenQuadGeometryShader(), pixel_shader);
RasterizationState rs_state = Util::GetNoCullRasterizationState();
rs_state.samples = m_efb_samples;
@@ -510,8 +510,8 @@ Texture2D* FramebufferManager::ResolveEFBDepthTexture(const VkRect2D& region)
// Draw using resolve shader to write the minimum depth of all samples to the resolve texture.
UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(),
g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD),
- m_depth_resolve_render_pass, g_object_cache->GetScreenQuadVertexShader(),
- g_object_cache->GetScreenQuadGeometryShader(), m_ps_depth_resolve);
+ m_depth_resolve_render_pass, g_shader_cache->GetScreenQuadVertexShader(),
+ g_shader_cache->GetScreenQuadGeometryShader(), m_ps_depth_resolve);
draw.BeginRenderPass(m_depth_resolve_framebuffer, region);
draw.SetPSSampler(0, m_efb_depth_texture->GetView(), g_object_cache->GetPointSampler());
draw.SetViewportAndScissor(region.offset.x, region.offset.y, region.extent.width,
@@ -641,7 +641,7 @@ bool FramebufferManager::CompileConversionShaders()
}
)";
- std::string header = g_object_cache->GetUtilityShaderHeader();
+ std::string header = g_shader_cache->GetUtilityShaderHeader();
DestroyConversionShaders();
m_ps_rgb8_to_rgba6 = Util::CompileAndCreateFragmentShader(header + RGB8_TO_RGBA6_SHADER_SOURCE);
@@ -698,7 +698,7 @@ bool FramebufferManager::PopulateColorReadbackTexture()
UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(),
g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD),
- m_copy_color_render_pass, g_object_cache->GetScreenQuadVertexShader(),
+ m_copy_color_render_pass, g_shader_cache->GetScreenQuadVertexShader(),
VK_NULL_HANDLE, m_copy_color_shader);
VkRect2D rect = {{0, 0}, {EFB_WIDTH, EFB_HEIGHT}};
@@ -781,7 +781,7 @@ bool FramebufferManager::PopulateDepthReadbackTexture()
UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(),
g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD),
- m_copy_depth_render_pass, g_object_cache->GetScreenQuadVertexShader(),
+ m_copy_depth_render_pass, g_shader_cache->GetScreenQuadVertexShader(),
VK_NULL_HANDLE, m_copy_depth_shader);
VkRect2D rect = {{0, 0}, {EFB_WIDTH, EFB_HEIGHT}};
@@ -956,10 +956,10 @@ bool FramebufferManager::CompileReadbackShaders()
}
)";
- source = g_object_cache->GetUtilityShaderHeader() + COPY_COLOR_SHADER_SOURCE;
+ source = g_shader_cache->GetUtilityShaderHeader() + COPY_COLOR_SHADER_SOURCE;
m_copy_color_shader = Util::CompileAndCreateFragmentShader(source);
- source = g_object_cache->GetUtilityShaderHeader() + COPY_DEPTH_SHADER_SOURCE;
+ source = g_shader_cache->GetUtilityShaderHeader() + COPY_DEPTH_SHADER_SOURCE;
m_copy_depth_shader = Util::CompileAndCreateFragmentShader(source);
return m_copy_color_shader != VK_NULL_HANDLE && m_copy_depth_shader != VK_NULL_HANDLE;
@@ -1176,7 +1176,7 @@ void FramebufferManager::DrawPokeVertices(const EFBPokeVertex* vertices, size_t
pipeline_info.depth_stencil_state.compare_op = VK_COMPARE_OP_ALWAYS;
}
- VkPipeline pipeline = g_object_cache->GetPipeline(pipeline_info);
+ VkPipeline pipeline = g_shader_cache->GetPipeline(pipeline_info);
if (pipeline == VK_NULL_HANDLE)
{
PanicAlert("Failed to get pipeline for EFB poke draw");
@@ -1309,7 +1309,7 @@ bool FramebufferManager::CompilePokeShaders()
}
)";
- std::string source = g_object_cache->GetUtilityShaderHeader();
+ std::string source = g_shader_cache->GetUtilityShaderHeader();
if (m_poke_primitive_topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
source += "#define USE_POINT_SIZE 1\n";
source += POKE_VERTEX_SHADER_SOURCE;
@@ -1319,13 +1319,13 @@ bool FramebufferManager::CompilePokeShaders()
if (g_vulkan_context->SupportsGeometryShaders())
{
- source = g_object_cache->GetUtilityShaderHeader() + POKE_GEOMETRY_SHADER_SOURCE;
+ source = g_shader_cache->GetUtilityShaderHeader() + POKE_GEOMETRY_SHADER_SOURCE;
m_poke_geometry_shader = Util::CompileAndCreateGeometryShader(source);
if (m_poke_geometry_shader == VK_NULL_HANDLE)
return false;
}
- source = g_object_cache->GetUtilityShaderHeader() + POKE_PIXEL_SHADER_SOURCE;
+ source = g_shader_cache->GetUtilityShaderHeader() + POKE_PIXEL_SHADER_SOURCE;
m_poke_fragment_shader = Util::CompileAndCreateFragmentShader(source);
if (m_poke_fragment_shader == VK_NULL_HANDLE)
return false;
diff --git a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp
index 7f7dc93ccf..4e856cb0d5 100644
--- a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp
+++ b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp
@@ -33,9 +33,6 @@ ObjectCache::ObjectCache()
ObjectCache::~ObjectCache()
{
- DestroyPipelineCache();
- DestroyShaderCaches();
- DestroySharedShaders();
DestroySamplers();
DestroyPipelineLayouts();
DestroyDescriptorSetLayouts();
@@ -49,27 +46,12 @@ bool ObjectCache::Initialize()
if (!CreatePipelineLayouts())
return false;
- if (g_ActiveConfig.bShaderCache)
- {
- LoadShaderCaches();
- if (!LoadPipelineCache())
- return false;
- }
- else
- {
- if (!CreatePipelineCache())
- return false;
- }
-
if (!CreateUtilityShaderVertexFormat())
return false;
if (!CreateStaticSamplers())
return false;
- if (!CompileSharedShaders())
- return false;
-
m_utility_shader_vertex_buffer =
StreamBuffer::Create(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 1024 * 1024, 4 * 1024 * 1024);
m_utility_shader_uniform_buffer =
@@ -80,721 +62,6 @@ bool ObjectCache::Initialize()
return true;
}
-static bool IsStripPrimitiveTopology(VkPrimitiveTopology topology)
-{
- return topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP ||
- topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP ||
- topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY ||
- topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY;
-}
-
-static VkPipelineRasterizationStateCreateInfo
-GetVulkanRasterizationState(const RasterizationState& state)
-{
- return {
- VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, // VkStructureType sType
- nullptr, // const void* pNext
- 0, // VkPipelineRasterizationStateCreateFlags flags
- state.depth_clamp, // VkBool32 depthClampEnable
- VK_FALSE, // VkBool32 rasterizerDiscardEnable
- VK_POLYGON_MODE_FILL, // VkPolygonMode polygonMode
- state.cull_mode, // VkCullModeFlags cullMode
- VK_FRONT_FACE_CLOCKWISE, // VkFrontFace frontFace
- VK_FALSE, // VkBool32 depthBiasEnable
- 0.0f, // float depthBiasConstantFactor
- 0.0f, // float depthBiasClamp
- 0.0f, // float depthBiasSlopeFactor
- 1.0f // float lineWidth
- };
-}
-
-static VkPipelineMultisampleStateCreateInfo
-GetVulkanMultisampleState(const RasterizationState& rs_state)
-{
- return {
- VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, // VkStructureType sType
- nullptr, // const void* pNext
- 0, // VkPipelineMultisampleStateCreateFlags flags
- rs_state.samples, // VkSampleCountFlagBits rasterizationSamples
- rs_state.per_sample_shading, // VkBool32 sampleShadingEnable
- 1.0f, // float minSampleShading
- nullptr, // const VkSampleMask* pSampleMask;
- VK_FALSE, // VkBool32 alphaToCoverageEnable
- VK_FALSE // VkBool32 alphaToOneEnable
- };
-}
-
-static VkPipelineDepthStencilStateCreateInfo
-GetVulkanDepthStencilState(const DepthStencilState& state)
-{
- return {
- VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, // VkStructureType sType
- nullptr, // const void* pNext
- 0, // VkPipelineDepthStencilStateCreateFlags flags
- state.test_enable, // VkBool32 depthTestEnable
- state.write_enable, // VkBool32 depthWriteEnable
- state.compare_op, // VkCompareOp depthCompareOp
- VK_FALSE, // VkBool32 depthBoundsTestEnable
- VK_FALSE, // VkBool32 stencilTestEnable
- {}, // VkStencilOpState front
- {}, // VkStencilOpState back
- 0.0f, // float minDepthBounds
- 1.0f // float maxDepthBounds
- };
-}
-
-static VkPipelineColorBlendAttachmentState GetVulkanAttachmentBlendState(const BlendingState& state)
-{
- VkPipelineColorBlendAttachmentState vk_state = {};
- vk_state.blendEnable = static_cast<VkBool32>(state.blendenable);
- vk_state.colorBlendOp = state.subtract ? VK_BLEND_OP_REVERSE_SUBTRACT : VK_BLEND_OP_ADD;
- vk_state.alphaBlendOp = state.subtractAlpha ? VK_BLEND_OP_REVERSE_SUBTRACT : VK_BLEND_OP_ADD;
-
- if (state.usedualsrc && g_vulkan_context->SupportsDualSourceBlend())
- {
- static constexpr std::array<VkBlendFactor, 8> src_factors = {
- {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_DST_COLOR,
- VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, VK_BLEND_FACTOR_SRC1_ALPHA,
- VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA, VK_BLEND_FACTOR_DST_ALPHA,
- VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}};
- static constexpr std::array<VkBlendFactor, 8> dst_factors = {
- {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_SRC_COLOR,
- VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, VK_BLEND_FACTOR_SRC1_ALPHA,
- VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA, VK_BLEND_FACTOR_DST_ALPHA,
- VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}};
-
- vk_state.srcColorBlendFactor = src_factors[state.srcfactor];
- vk_state.srcAlphaBlendFactor = src_factors[state.srcfactoralpha];
- vk_state.dstColorBlendFactor = dst_factors[state.dstfactor];
- vk_state.dstAlphaBlendFactor = dst_factors[state.dstfactoralpha];
- }
- else
- {
- static constexpr std::array<VkBlendFactor, 8> src_factors = {
- {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_DST_COLOR,
- VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, VK_BLEND_FACTOR_SRC_ALPHA,
- VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_FACTOR_DST_ALPHA,
- VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}};
-
- static constexpr std::array<VkBlendFactor, 8> dst_factors = {
- {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_SRC_COLOR,
- VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, VK_BLEND_FACTOR_SRC_ALPHA,
- VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_FACTOR_DST_ALPHA,
- VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}};
-
- vk_state.srcColorBlendFactor = src_factors[state.srcfactor];
- vk_state.srcAlphaBlendFactor = src_factors[state.srcfactoralpha];
- vk_state.dstColorBlendFactor = dst_factors[state.dstfactor];
- vk_state.dstAlphaBlendFactor = dst_factors[state.dstfactoralpha];
- }
-
- if (state.colorupdate)
- {
- vk_state.colorWriteMask =
- VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT;
- }
- else
- {
- vk_state.colorWriteMask = 0;
- }
-
- if (state.alphaupdate)
- vk_state.colorWriteMask |= VK_COLOR_COMPONENT_A_BIT;
-
- return vk_state;
-}
-
-static VkPipelineColorBlendStateCreateInfo
-GetVulkanColorBlendState(const BlendingState& state,
- const VkPipelineColorBlendAttachmentState* attachments,
- uint32_t num_attachments)
-{
- static constexpr std::array<VkLogicOp, 16> vk_logic_ops = {
- {VK_LOGIC_OP_CLEAR, VK_LOGIC_OP_AND, VK_LOGIC_OP_AND_REVERSE, VK_LOGIC_OP_COPY,
- VK_LOGIC_OP_AND_INVERTED, VK_LOGIC_OP_NO_OP, VK_LOGIC_OP_XOR, VK_LOGIC_OP_OR,
- VK_LOGIC_OP_NOR, VK_LOGIC_OP_EQUIVALENT, VK_LOGIC_OP_INVERT, VK_LOGIC_OP_OR_REVERSE,
- VK_LOGIC_OP_COPY_INVERTED, VK_LOGIC_OP_OR_INVERTED, VK_LOGIC_OP_NAND, VK_LOGIC_OP_SET}};
-
- VkBool32 vk_logic_op_enable = static_cast<VkBool32>(state.logicopenable);
- if (vk_logic_op_enable && !g_vulkan_context->SupportsLogicOps())
- {
- // At the time of writing, Adreno and Mali drivers didn't support logic ops.
- // The "emulation" through blending path has been removed, so just disable it completely.
- // These drivers don't support dual-source blend either, so issues are to be expected.
- vk_logic_op_enable = VK_FALSE;
- }
-
- VkLogicOp vk_logic_op = vk_logic_op_enable ? vk_logic_ops[state.logicmode] : VK_LOGIC_OP_CLEAR;
-
- VkPipelineColorBlendStateCreateInfo vk_state = {
- VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, // VkStructureType sType
- nullptr, // const void* pNext
- 0, // VkPipelineColorBlendStateCreateFlags flags
- vk_logic_op_enable, // VkBool32 logicOpEnable
- vk_logic_op, // VkLogicOp logicOp
- num_attachments, // uint32_t attachmentCount
- attachments, // const VkPipelineColorBlendAttachmentState* pAttachments
- {1.0f, 1.0f, 1.0f, 1.0f} // float blendConstants[4]
- };
-
- return vk_state;
-}
-
-VkPipeline ObjectCache::CreatePipeline(const PipelineInfo& info)
-{
- // Declare descriptors for empty vertex buffers/attributes
- static const VkPipelineVertexInputStateCreateInfo empty_vertex_input_state = {
- VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, // VkStructureType sType
- nullptr, // const void* pNext
- 0, // VkPipelineVertexInputStateCreateFlags flags
- 0, // uint32_t vertexBindingDescriptionCount
- nullptr, // const VkVertexInputBindingDescription* pVertexBindingDescriptions
- 0, // uint32_t vertexAttributeDescriptionCount
- nullptr // const VkVertexInputAttributeDescription* pVertexAttributeDescriptions
- };
-
- // Vertex inputs
- const VkPipelineVertexInputStateCreateInfo& vertex_input_state =
- info.vertex_format ? info.vertex_format->GetVertexInputStateInfo() : empty_vertex_input_state;
-
- // Input assembly
- VkPipelineInputAssemblyStateCreateInfo input_assembly_state = {
- VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, // VkStructureType sType
- nullptr, // const void* pNext
- 0, // VkPipelineInputAssemblyStateCreateFlags flags
- info.primitive_topology, // VkPrimitiveTopology topology
- VK_FALSE // VkBool32 primitiveRestartEnable
- };
-
- // See Vulkan spec, section 19:
- // If topology is VK_PRIMITIVE_TOPOLOGY_POINT_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST,
- // VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY,
- // VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY or VK_PRIMITIVE_TOPOLOGY_PATCH_LIST,
- // primitiveRestartEnable must be VK_FALSE
- if (g_ActiveConfig.backend_info.bSupportsPrimitiveRestart &&
- IsStripPrimitiveTopology(info.primitive_topology))
- {
- input_assembly_state.primitiveRestartEnable = VK_TRUE;
- }
-
- // Shaders to stages
- VkPipelineShaderStageCreateInfo shader_stages[3];
- uint32_t num_shader_stages = 0;
- if (info.vs != VK_NULL_HANDLE)
- {
- shader_stages[num_shader_stages++] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
- nullptr,
- 0,
- VK_SHADER_STAGE_VERTEX_BIT,
- info.vs,
- "main"};
- }
- if (info.gs != VK_NULL_HANDLE)
- {
- shader_stages[num_shader_stages++] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
- nullptr,
- 0,
- VK_SHADER_STAGE_GEOMETRY_BIT,
- info.gs,
- "main"};
- }
- if (info.ps != VK_NULL_HANDLE)
- {
- shader_stages[num_shader_stages++] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
- nullptr,
- 0,
- VK_SHADER_STAGE_FRAGMENT_BIT,
- info.ps,
- "main"};
- }
-
- // Fill in Vulkan descriptor structs from our state structures.
- VkPipelineRasterizationStateCreateInfo rasterization_state =
- GetVulkanRasterizationState(info.rasterization_state);
- VkPipelineMultisampleStateCreateInfo multisample_state =
- GetVulkanMultisampleState(info.rasterization_state);
- VkPipelineDepthStencilStateCreateInfo depth_stencil_state =
- GetVulkanDepthStencilState(info.depth_stencil_state);
- VkPipelineColorBlendAttachmentState blend_attachment_state =
- GetVulkanAttachmentBlendState(info.blend_state);
- VkPipelineColorBlendStateCreateInfo blend_state =
- GetVulkanColorBlendState(info.blend_state, &blend_attachment_state, 1);
-
- // This viewport isn't used, but needs to be specified anyway.
- static const VkViewport viewport = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
- static const VkRect2D scissor = {{0, 0}, {1, 1}};
- static const VkPipelineViewportStateCreateInfo viewport_state = {
- VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
- nullptr,
- 0, // VkPipelineViewportStateCreateFlags flags;
- 1, // uint32_t viewportCount
- &viewport, // const VkViewport* pViewports
- 1, // uint32_t scissorCount
- &scissor // const VkRect2D* pScissors
- };
-
- // Set viewport and scissor dynamic state so we can change it elsewhere.
- static const VkDynamicState dynamic_states[] = {VK_DYNAMIC_STATE_VIEWPORT,
- VK_DYNAMIC_STATE_SCISSOR};
- static const VkPipelineDynamicStateCreateInfo dynamic_state = {
- VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, nullptr,
- 0, // VkPipelineDynamicStateCreateFlags flags
- static_cast<u32>(ArraySize(dynamic_states)), // uint32_t dynamicStateCount
- dynamic_states // const VkDynamicState* pDynamicStates
- };
-
- // Combine to full pipeline info structure.
- VkGraphicsPipelineCreateInfo pipeline_info = {
- VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
- nullptr, // VkStructureType sType
- 0, // VkPipelineCreateFlags flags
- num_shader_stages, // uint32_t stageCount
- shader_stages, // const VkPipelineShaderStageCreateInfo* pStages
- &vertex_input_state, // const VkPipelineVertexInputStateCreateInfo* pVertexInputState
- &input_assembly_state, // const VkPipelineInputAssemblyStateCreateInfo* pInputAssemblyState
- nullptr, // const VkPipelineTessellationStateCreateInfo* pTessellationState
- &viewport_state, // const VkPipelineViewportStateCreateInfo* pViewportState
- &rasterization_state, // const VkPipelineRasterizationStateCreateInfo* pRasterizationState
- &multisample_state, // const VkPipelineMultisampleStateCreateInfo* pMultisampleState
- &depth_stencil_state, // const VkPipelineDepthStencilStateCreateInfo* pDepthStencilState
- &blend_state, // const VkPipelineColorBlendStateCreateInfo* pColorBlendState
- &dynamic_state, // const VkPipelineDynamicStateCreateInfo* pDynamicState
- info.pipeline_layout, // VkPipelineLayout layout
- info.render_pass, // VkRenderPass renderPass
- 0, // uint32_t subpass
- VK_NULL_HANDLE, // VkPipeline basePipelineHandle
- -1 // int32_t basePipelineIndex
- };
-
- VkPipeline pipeline;
- VkResult res = vkCreateGraphicsPipelines(g_vulkan_context->GetDevice(), m_pipeline_cache, 1,
- &pipeline_info, nullptr, &pipeline);
- if (res != VK_SUCCESS)
- {
- LOG_VULKAN_ERROR(res, "vkCreateGraphicsPipelines failed: ");
- return VK_NULL_HANDLE;
- }
-
- return pipeline;
-}
-
-VkPipeline ObjectCache::GetPipeline(const PipelineInfo& info)
-{
- return GetPipelineWithCacheResult(info).first;
-}
-
-std::pair<VkPipeline, bool> ObjectCache::GetPipelineWithCacheResult(const PipelineInfo& info)
-{
- auto iter = m_pipeline_objects.find(info);
- if (iter != m_pipeline_objects.end())
- return {iter->second, true};
-
- VkPipeline pipeline = CreatePipeline(info);
- m_pipeline_objects.emplace(info, pipeline);
- return {pipeline, false};
-}
-
-VkPipeline ObjectCache::CreateComputePipeline(const ComputePipelineInfo& info)
-{
- VkComputePipelineCreateInfo pipeline_info = {VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
- nullptr,
- 0,
- {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
- nullptr, 0, VK_SHADER_STAGE_COMPUTE_BIT, info.cs,
- "main", nullptr},
- info.pipeline_layout,
- VK_NULL_HANDLE,
- -1};
-
- VkPipeline pipeline;
- VkResult res = vkCreateComputePipelines(g_vulkan_context->GetDevice(), VK_NULL_HANDLE, 1,
- &pipeline_info, nullptr, &pipeline);
- if (res != VK_SUCCESS)
- {
- LOG_VULKAN_ERROR(res, "vkCreateComputePipelines failed: ");
- return VK_NULL_HANDLE;
- }
-
- return pipeline;
-}
-
-VkPipeline ObjectCache::GetComputePipeline(const ComputePipelineInfo& info)
-{
- auto iter = m_compute_pipeline_objects.find(info);
- if (iter != m_compute_pipeline_objects.end())
- return iter->second;
-
- VkPipeline pipeline = CreateComputePipeline(info);
- m_compute_pipeline_objects.emplace(info, pipeline);
- return pipeline;
-}
-
-void ObjectCache::ClearPipelineCache()
-{
- for (const auto& it : m_pipeline_objects)
- {
- if (it.second != VK_NULL_HANDLE)
- vkDestroyPipeline(g_vulkan_context->GetDevice(), it.second, nullptr);
- }
- m_pipeline_objects.clear();
-
- for (const auto& it : m_compute_pipeline_objects)
- {
- if (it.second != VK_NULL_HANDLE)
- vkDestroyPipeline(g_vulkan_context->GetDevice(), it.second, nullptr);
- }
- m_compute_pipeline_objects.clear();
-}
-
-class PipelineCacheReadCallback : public LinearDiskCacheReader<u32, u8>
-{
-public:
- PipelineCacheReadCallback(std::vector<u8>* data) : m_data(data) {}
- void Read(const u32& key, const u8* value, u32 value_size) override
- {
- m_data->resize(value_size);
- if (value_size > 0)
- memcpy(m_data->data(), value, value_size);
- }
-
-private:
- std::vector<u8>* m_data;
-};
-
-class PipelineCacheReadIgnoreCallback : public LinearDiskCacheReader<u32, u8>
-{
-public:
- void Read(const u32& key, const u8* value, u32 value_size) override {}
-};
-
-bool ObjectCache::CreatePipelineCache()
-{
- // Vulkan pipeline caches can be shared between games for shader compile time reduction.
- // This assumes that drivers don't create all pipelines in the cache on load time, only
- // when a lookup occurs that matches a pipeline (or pipeline data) in the cache.
- m_pipeline_cache_filename = GetDiskShaderCacheFileName(APIType::Vulkan, "Pipeline", false, true);
-
- VkPipelineCacheCreateInfo info = {
- VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, // VkStructureType sType
- nullptr, // const void* pNext
- 0, // VkPipelineCacheCreateFlags flags
- 0, // size_t initialDataSize
- nullptr // const void* pInitialData
- };
-
- VkResult res =
- vkCreatePipelineCache(g_vulkan_context->GetDevice(), &info, nullptr, &m_pipeline_cache);
- if (res == VK_SUCCESS)
- return true;
-
- LOG_VULKAN_ERROR(res, "vkCreatePipelineCache failed: ");
- return false;
-}
-
-bool ObjectCache::LoadPipelineCache()
-{
- // We have to keep the pipeline cache file name around since when we save it
- // we delete the old one, by which time the game's unique ID is already cleared.
- m_pipeline_cache_filename = GetDiskShaderCacheFileName(APIType::Vulkan, "Pipeline", false, true);
-
- std::vector<u8> disk_data;
- LinearDiskCache<u32, u8> disk_cache;
- PipelineCacheReadCallback read_callback(&disk_data);
- if (disk_cache.OpenAndRead(m_pipeline_cache_filename, read_callback) != 1)
- disk_data.clear();
-
- if (!disk_data.empty() && !ValidatePipelineCache(disk_data.data(), disk_data.size()))
- {
- // Don't use this data. In fact, we should delete it to prevent it from being used next time.
- File::Delete(m_pipeline_cache_filename);
- return CreatePipelineCache();
- }
-
- VkPipelineCacheCreateInfo info = {
- VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, // VkStructureType sType
- nullptr, // const void* pNext
- 0, // VkPipelineCacheCreateFlags flags
- disk_data.size(), // size_t initialDataSize
- disk_data.data() // const void* pInitialData
- };
-
- VkResult res =
- vkCreatePipelineCache(g_vulkan_context->GetDevice(), &info, nullptr, &m_pipeline_cache);
- if (res == VK_SUCCESS)
- return true;
-
- // Failed to create pipeline cache, try with it empty.
- LOG_VULKAN_ERROR(res, "vkCreatePipelineCache failed, trying empty cache: ");
- return CreatePipelineCache();
-}
-
-// Based on Vulkan 1.0 specification,
-// Table 9.1. Layout for pipeline cache header version VK_PIPELINE_CACHE_HEADER_VERSION_ONE
-// NOTE: This data is assumed to be in little-endian format.
-#pragma pack(push, 4)
-struct VK_PIPELINE_CACHE_HEADER
-{
- u32 header_length;
- u32 header_version;
- u32 vendor_id;
- u32 device_id;
- u8 uuid[VK_UUID_SIZE];
-};
-#pragma pack(pop)
-// TODO: Remove the #if here when GCC 5 is a minimum build requirement.
-#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5
-static_assert(std::has_trivial_copy_constructor<VK_PIPELINE_CACHE_HEADER>::value,
- "VK_PIPELINE_CACHE_HEADER must be trivially copyable");
-#else
-static_assert(std::is_trivially_copyable<VK_PIPELINE_CACHE_HEADER>::value,
- "VK_PIPELINE_CACHE_HEADER must be trivially copyable");
-#endif
-
-bool ObjectCache::ValidatePipelineCache(const u8* data, size_t data_length)
-{
- if (data_length < sizeof(VK_PIPELINE_CACHE_HEADER))
- {
- ERROR_LOG(VIDEO, "Pipeline cache failed validation: Invalid header");
- return false;
- }
-
- VK_PIPELINE_CACHE_HEADER header;
- std::memcpy(&header, data, sizeof(header));
- if (header.header_length < sizeof(VK_PIPELINE_CACHE_HEADER))
- {
- ERROR_LOG(VIDEO, "Pipeline cache failed validation: Invalid header length");
- return false;
- }
-
- if (header.header_version != VK_PIPELINE_CACHE_HEADER_VERSION_ONE)
- {
- ERROR_LOG(VIDEO, "Pipeline cache failed validation: Invalid header version");
- return false;
- }
-
- if (header.vendor_id != g_vulkan_context->GetDeviceProperties().vendorID)
- {
- ERROR_LOG(VIDEO,
- "Pipeline cache failed validation: Incorrect vendor ID (file: 0x%X, device: 0x%X)",
- header.vendor_id, g_vulkan_context->GetDeviceProperties().vendorID);
- return false;
- }
-
- if (header.device_id != g_vulkan_context->GetDeviceProperties().deviceID)
- {
- ERROR_LOG(VIDEO,
- "Pipeline cache failed validation: Incorrect device ID (file: 0x%X, device: 0x%X)",
- header.device_id, g_vulkan_context->GetDeviceProperties().deviceID);
- return false;
- }
-
- if (std::memcmp(header.uuid, g_vulkan_context->GetDeviceProperties().pipelineCacheUUID,
- VK_UUID_SIZE) != 0)
- {
- ERROR_LOG(VIDEO, "Pipeline cache failed validation: Incorrect UUID");
- return false;
- }
-
- return true;
-}
-
-void ObjectCache::DestroyPipelineCache()
-{
- ClearPipelineCache();
- vkDestroyPipelineCache(g_vulkan_context->GetDevice(), m_pipeline_cache, nullptr);
- m_pipeline_cache = VK_NULL_HANDLE;
-}
-
-void ObjectCache::SavePipelineCache()
-{
- size_t data_size;
- VkResult res =
- vkGetPipelineCacheData(g_vulkan_context->GetDevice(), m_pipeline_cache, &data_size, nullptr);
- if (res != VK_SUCCESS)
- {
- LOG_VULKAN_ERROR(res, "vkGetPipelineCacheData failed: ");
- return;
- }
-
- std::vector<u8> data(data_size);
- res = vkGetPipelineCacheData(g_vulkan_context->GetDevice(), m_pipeline_cache, &data_size,
- data.data());
- if (res != VK_SUCCESS)
- {
- LOG_VULKAN_ERROR(res, "vkGetPipelineCacheData failed: ");
- return;
- }
-
- // Delete the old cache and re-create.
- File::Delete(m_pipeline_cache_filename);
-
- // We write a single key of 1, with the entire pipeline cache data.
- // Not ideal, but our disk cache class does not support just writing a single blob
- // of data without specifying a key.
- LinearDiskCache<u32, u8> disk_cache;
- PipelineCacheReadIgnoreCallback callback;
- disk_cache.OpenAndRead(m_pipeline_cache_filename, callback);
- disk_cache.Append(1, data.data(), static_cast<u32>(data.size()));
- disk_cache.Close();
-}
-
-// Cache inserter that is called back when reading from the file
-template <typename Uid>
-struct ShaderCacheReader : public LinearDiskCacheReader<Uid, u32>
-{
- ShaderCacheReader(std::map<Uid, VkShaderModule>& shader_map) : m_shader_map(shader_map) {}
- void Read(const Uid& key, const u32* value, u32 value_size) override
- {
- // We don't insert null modules into the shader map since creation could succeed later on.
- // e.g. we're generating bad code, but fix this in a later version, and for some reason
- // the cache is not invalidated.
- VkShaderModule module = Util::CreateShaderModule(value, value_size);
- if (module == VK_NULL_HANDLE)
- return;
-
- m_shader_map.emplace(key, module);
- }
-
- std::map<Uid, VkShaderModule>& m_shader_map;
-};
-
-void ObjectCache::LoadShaderCaches()
-{
- ShaderCacheReader<VertexShaderUid> vs_reader(m_vs_cache.shader_map);
- m_vs_cache.disk_cache.OpenAndRead(GetDiskShaderCacheFileName(APIType::Vulkan, "VS", true, true),
- vs_reader);
-
- ShaderCacheReader<PixelShaderUid> ps_reader(m_ps_cache.shader_map);
- m_ps_cache.disk_cache.OpenAndRead(GetDiskShaderCacheFileName(APIType::Vulkan, "PS", true, true),
- ps_reader);
-
- if (g_vulkan_context->SupportsGeometryShaders())
- {
- ShaderCacheReader<GeometryShaderUid> gs_reader(m_gs_cache.shader_map);
- m_gs_cache.disk_cache.OpenAndRead(GetDiskShaderCacheFileName(APIType::Vulkan, "GS", true, true),
- gs_reader);
- }
-
- SETSTAT(stats.numPixelShadersCreated, static_cast<int>(m_ps_cache.shader_map.size()));
- SETSTAT(stats.numPixelShadersAlive, static_cast<int>(m_ps_cache.shader_map.size()));
- SETSTAT(stats.numVertexShadersCreated, static_cast<int>(m_vs_cache.shader_map.size()));
- SETSTAT(stats.numVertexShadersAlive, static_cast<int>(m_vs_cache.shader_map.size()));
-}
-
-template <typename T>
-static void DestroyShaderCache(T& cache)
-{
- cache.disk_cache.Sync();
- cache.disk_cache.Close();
- for (const auto& it : cache.shader_map)
- {
- if (it.second != VK_NULL_HANDLE)
- vkDestroyShaderModule(g_vulkan_context->GetDevice(), it.second, nullptr);
- }
- cache.shader_map.clear();
-}
-
-void ObjectCache::DestroyShaderCaches()
-{
- DestroyShaderCache(m_vs_cache);
- DestroyShaderCache(m_ps_cache);
-
- if (g_vulkan_context->SupportsGeometryShaders())
- DestroyShaderCache(m_gs_cache);
-
- SETSTAT(stats.numPixelShadersCreated, 0);
- SETSTAT(stats.numPixelShadersAlive, 0);
- SETSTAT(stats.numVertexShadersCreated, 0);
- SETSTAT(stats.numVertexShadersAlive, 0);
-}
-
-VkShaderModule ObjectCache::GetVertexShaderForUid(const VertexShaderUid& uid)
-{
- auto it = m_vs_cache.shader_map.find(uid);
- if (it != m_vs_cache.shader_map.end())
- return it->second;
-
- // Not in the cache, so compile the shader.
- ShaderCompiler::SPIRVCodeVector spv;
- VkShaderModule module = VK_NULL_HANDLE;
- ShaderCode source_code =
- GenerateVertexShaderCode(APIType::Vulkan, ShaderHostConfig::GetCurrent(), uid.GetUidData());
- if (ShaderCompiler::CompileVertexShader(&spv, source_code.GetBuffer().c_str(),
- source_code.GetBuffer().length()))
- {
- module = Util::CreateShaderModule(spv.data(), spv.size());
-
- // Append to shader cache if it created successfully.
- if (module != VK_NULL_HANDLE)
- {
- m_vs_cache.disk_cache.Append(uid, spv.data(), static_cast<u32>(spv.size()));
- INCSTAT(stats.numVertexShadersCreated);
- INCSTAT(stats.numVertexShadersAlive);
- }
- }
-
- // We still insert null entries to prevent further compilation attempts.
- m_vs_cache.shader_map.emplace(uid, module);
- return module;
-}
-
-VkShaderModule ObjectCache::GetGeometryShaderForUid(const GeometryShaderUid& uid)
-{
- _assert_(g_vulkan_context->SupportsGeometryShaders());
- auto it = m_gs_cache.shader_map.find(uid);
- if (it != m_gs_cache.shader_map.end())
- return it->second;
-
- // Not in the cache, so compile the shader.
- ShaderCompiler::SPIRVCodeVector spv;
- VkShaderModule module = VK_NULL_HANDLE;
- ShaderCode source_code =
- GenerateGeometryShaderCode(APIType::Vulkan, ShaderHostConfig::GetCurrent(), uid.GetUidData());
- if (ShaderCompiler::CompileGeometryShader(&spv, source_code.GetBuffer().c_str(),
- source_code.GetBuffer().length()))
- {
- module = Util::CreateShaderModule(spv.data(), spv.size());
-
- // Append to shader cache if it created successfully.
- if (module != VK_NULL_HANDLE)
- m_gs_cache.disk_cache.Append(uid, spv.data(), static_cast<u32>(spv.size()));
- }
-
- // We still insert null entries to prevent further compilation attempts.
- m_gs_cache.shader_map.emplace(uid, module);
- return module;
-}
-
-VkShaderModule ObjectCache::GetPixelShaderForUid(const PixelShaderUid& uid)
-{
- auto it = m_ps_cache.shader_map.find(uid);
- if (it != m_ps_cache.shader_map.end())
- return it->second;
-
- // Not in the cache, so compile the shader.
- ShaderCompiler::SPIRVCodeVector spv;
- VkShaderModule module = VK_NULL_HANDLE;
- ShaderCode source_code =
- GeneratePixelShaderCode(APIType::Vulkan, ShaderHostConfig::GetCurrent(), uid.GetUidData());
- if (ShaderCompiler::CompileFragmentShader(&spv, source_code.GetBuffer().c_str(),
- source_code.GetBuffer().length()))
- {
- module = Util::CreateShaderModule(spv.data(), spv.size());
-
- // Append to shader cache if it created successfully.
- if (module != VK_NULL_HANDLE)
- {
- m_ps_cache.disk_cache.Append(uid, spv.data(), static_cast<u32>(spv.size()));
- INCSTAT(stats.numPixelShadersCreated);
- INCSTAT(stats.numPixelShadersAlive);
- }
- }
-
- // We still insert null entries to prevent further compilation attempts.
- m_ps_cache.shader_map.emplace(uid, module);
- return module;
-}
-
void ObjectCache::ClearSamplerCache()
{
for (const auto& it : m_sampler_cache)
@@ -822,30 +89,6 @@ void ObjectCache::DestroySamplers()
}
}
-void ObjectCache::RecompileSharedShaders()
-{
- DestroySharedShaders();
- if (!CompileSharedShaders())
- PanicAlert("Failed to recompile shared shaders.");
-}
-
-void ObjectCache::ReloadShaderAndPipelineCaches()
-{
- SavePipelineCache();
- DestroyShaderCaches();
- DestroyPipelineCache();
-
- if (g_ActiveConfig.bShaderCache)
- {
- LoadShaderCaches();
- LoadPipelineCache();
- }
- else
- {
- CreatePipelineCache();
- }
-}
-
bool ObjectCache::CreateDescriptorSetLayouts()
{
static const VkDescriptorSetLayoutBinding ubo_set_bindings[] = {
@@ -1102,234 +345,4 @@ VkSampler ObjectCache::GetSampler(const SamplerState& info)
m_sampler_cache.emplace(info, sampler);
return sampler;
}
-
-std::string ObjectCache::GetUtilityShaderHeader() const
-{
- std::stringstream ss;
- if (g_ActiveConfig.iMultisamples > 1)
- {
- ss << "#define MSAA_ENABLED 1" << std::endl;
- ss << "#define MSAA_SAMPLES " << g_ActiveConfig.iMultisamples << std::endl;
- if (g_ActiveConfig.bSSAA)
- ss << "#define SSAA_ENABLED 1" << std::endl;
- }
-
- u32 efb_layers = (g_ActiveConfig.iStereoMode != STEREO_OFF) ? 2 : 1;
- ss << "#define EFB_LAYERS " << efb_layers << std::endl;
-
- return ss.str();
-}
-
-// Comparison operators for PipelineInfos
-// Since these all boil down to POD types, we can just memcmp the entire thing for speed
-// The is_trivially_copyable check fails on MSVC due to BitField.
-// TODO: Can we work around this any way?
-#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5 && !defined(_MSC_VER)
-static_assert(std::has_trivial_copy_constructor<PipelineInfo>::value,
- "PipelineInfo is trivially copyable");
-#elif !defined(_MSC_VER)
-static_assert(std::is_trivially_copyable<PipelineInfo>::value,
- "PipelineInfo is trivially copyable");
-#endif
-
-std::size_t PipelineInfoHash::operator()(const PipelineInfo& key) const
-{
- return static_cast<std::size_t>(XXH64(&key, sizeof(key), 0));
-}
-
-bool operator==(const PipelineInfo& lhs, const PipelineInfo& rhs)
-{
- return std::memcmp(&lhs, &rhs, sizeof(lhs)) == 0;
-}
-
-bool operator!=(const PipelineInfo& lhs, const PipelineInfo& rhs)
-{
- return !operator==(lhs, rhs);
-}
-
-bool operator<(const PipelineInfo& lhs, const PipelineInfo& rhs)
-{
- return std::memcmp(&lhs, &rhs, sizeof(lhs)) < 0;
-}
-
-bool operator>(const PipelineInfo& lhs, const PipelineInfo& rhs)
-{
- return std::memcmp(&lhs, &rhs, sizeof(lhs)) > 0;
-}
-
-bool operator==(const SamplerState& lhs, const SamplerState& rhs)
-{
- return lhs.bits == rhs.bits;
-}
-
-bool operator!=(const SamplerState& lhs, const SamplerState& rhs)
-{
- return !operator==(lhs, rhs);
-}
-
-bool operator>(const SamplerState& lhs, const SamplerState& rhs)
-{
- return lhs.bits > rhs.bits;
-}
-
-bool operator<(const SamplerState& lhs, const SamplerState& rhs)
-{
- return lhs.bits < rhs.bits;
-}
-
-std::size_t ComputePipelineInfoHash::operator()(const ComputePipelineInfo& key) const
-{
- return static_cast<std::size_t>(XXH64(&key, sizeof(key), 0));
-}
-
-bool operator==(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs)
-{
- return std::memcmp(&lhs, &rhs, sizeof(lhs)) == 0;
-}
-
-bool operator!=(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs)
-{
- return !operator==(lhs, rhs);
-}
-
-bool operator<(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs)
-{
- return std::memcmp(&lhs, &rhs, sizeof(lhs)) < 0;
-}
-
-bool operator>(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs)
-{
- return std::memcmp(&lhs, &rhs, sizeof(lhs)) > 0;
-}
-
-bool ObjectCache::CompileSharedShaders()
-{
- static const char PASSTHROUGH_VERTEX_SHADER_SOURCE[] = R"(
- layout(location = 0) in vec4 ipos;
- layout(location = 5) in vec4 icol0;
- layout(location = 8) in vec3 itex0;
-
- layout(location = 0) out vec3 uv0;
- layout(location = 1) out vec4 col0;
-
- void main()
- {
- gl_Position = ipos;
- uv0 = itex0;
- col0 = icol0;
- }
- )";
-
- static const char PASSTHROUGH_GEOMETRY_SHADER_SOURCE[] = R"(
- layout(triangles) in;
- layout(triangle_strip, max_vertices = EFB_LAYERS * 3) out;
-
- layout(location = 0) in vec3 in_uv0[];
- layout(location = 1) in vec4 in_col0[];
-
- layout(location = 0) out vec3 out_uv0;
- layout(location = 1) out vec4 out_col0;
-
- void main()
- {
- for (int j = 0; j < EFB_LAYERS; j++)
- {
- for (int i = 0; i < 3; i++)
- {
- gl_Layer = j;
- gl_Position = gl_in[i].gl_Position;
- out_uv0 = vec3(in_uv0[i].xy, float(j));
- out_col0 = in_col0[i];
- EmitVertex();
- }
- EndPrimitive();
- }
- }
- )";
-
- static const char SCREEN_QUAD_VERTEX_SHADER_SOURCE[] = R"(
- layout(location = 0) out vec3 uv0;
-
- void main()
- {
- /*
- * id &1 &2 clamp(*2-1)
- * 0 0,0 0,0 -1,-1 TL
- * 1 1,0 1,0 1,-1 TR
- * 2 0,2 0,1 -1,1 BL
- * 3 1,2 1,1 1,1 BR
- */
- vec2 rawpos = vec2(float(gl_VertexID & 1), clamp(float(gl_VertexID & 2), 0.0f, 1.0f));
- gl_Position = vec4(rawpos * 2.0f - 1.0f, 0.0f, 1.0f);
- uv0 = vec3(rawpos, 0.0f);
- }
- )";
-
- static const char SCREEN_QUAD_GEOMETRY_SHADER_SOURCE[] = R"(
- layout(triangles) in;
- layout(triangle_strip, max_vertices = EFB_LAYERS * 3) out;
-
- layout(location = 0) in vec3 in_uv0[];
-
- layout(location = 0) out vec3 out_uv0;
-
- void main()
- {
- for (int j = 0; j < EFB_LAYERS; j++)
- {
- for (int i = 0; i < 3; i++)
- {
- gl_Layer = j;
- gl_Position = gl_in[i].gl_Position;
- out_uv0 = vec3(in_uv0[i].xy, float(j));
- EmitVertex();
- }
- EndPrimitive();
- }
- }
- )";
-
- std::string header = GetUtilityShaderHeader();
-
- m_screen_quad_vertex_shader =
- Util::CompileAndCreateVertexShader(header + SCREEN_QUAD_VERTEX_SHADER_SOURCE);
- m_passthrough_vertex_shader =
- Util::CompileAndCreateVertexShader(header + PASSTHROUGH_VERTEX_SHADER_SOURCE);
- if (m_screen_quad_vertex_shader == VK_NULL_HANDLE ||
- m_passthrough_vertex_shader == VK_NULL_HANDLE)
- {
- return false;
- }
-
- if (g_ActiveConfig.iStereoMode != STEREO_OFF && g_vulkan_context->SupportsGeometryShaders())
- {
- m_screen_quad_geometry_shader =
- Util::CompileAndCreateGeometryShader(header + SCREEN_QUAD_GEOMETRY_SHADER_SOURCE);
- m_passthrough_geometry_shader =
- Util::CompileAndCreateGeometryShader(header + PASSTHROUGH_GEOMETRY_SHADER_SOURCE);
- if (m_screen_quad_geometry_shader == VK_NULL_HANDLE ||
- m_passthrough_geometry_shader == VK_NULL_HANDLE)
- {
- return false;
- }
- }
-
- return true;
-}
-
-void ObjectCache::DestroySharedShaders()
-{
- auto DestroyShader = [this](VkShaderModule& shader) {
- if (shader != VK_NULL_HANDLE)
- {
- vkDestroyShaderModule(g_vulkan_context->GetDevice(), shader, nullptr);
- shader = VK_NULL_HANDLE;
- }
- };
-
- DestroyShader(m_screen_quad_vertex_shader);
- DestroyShader(m_passthrough_vertex_shader);
- DestroyShader(m_screen_quad_geometry_shader);
- DestroyShader(m_passthrough_geometry_shader);
-}
}
diff --git a/Source/Core/VideoBackends/Vulkan/ObjectCache.h b/Source/Core/VideoBackends/Vulkan/ObjectCache.h
index 53f0485698..dd7b1ed739 100644
--- a/Source/Core/VideoBackends/Vulkan/ObjectCache.h
+++ b/Source/Core/VideoBackends/Vulkan/ObjectCache.h
@@ -27,52 +27,6 @@ class CommandBufferManager;
class VertexFormat;
class StreamBuffer;
-struct PipelineInfo
-{
- // These are packed in descending order of size, to avoid any padding so that the structure
- // can be copied/compared as a single block of memory. 64-bit pointer size is assumed.
- const VertexFormat* vertex_format;
- VkPipelineLayout pipeline_layout;
- VkShaderModule vs;
- VkShaderModule gs;
- VkShaderModule ps;
- VkRenderPass render_pass;
- BlendingState blend_state;
- RasterizationState rasterization_state;
- DepthStencilState depth_stencil_state;
- VkPrimitiveTopology primitive_topology;
-};
-
-struct PipelineInfoHash
-{
- std::size_t operator()(const PipelineInfo& key) const;
-};
-
-bool operator==(const PipelineInfo& lhs, const PipelineInfo& rhs);
-bool operator!=(const PipelineInfo& lhs, const PipelineInfo& rhs);
-bool operator<(const PipelineInfo& lhs, const PipelineInfo& rhs);
-bool operator>(const PipelineInfo& lhs, const PipelineInfo& rhs);
-bool operator==(const SamplerState& lhs, const SamplerState& rhs);
-bool operator!=(const SamplerState& lhs, const SamplerState& rhs);
-bool operator>(const SamplerState& lhs, const SamplerState& rhs);
-bool operator<(const SamplerState& lhs, const SamplerState& rhs);
-
-struct ComputePipelineInfo
-{
- VkPipelineLayout pipeline_layout;
- VkShaderModule cs;
-};
-
-struct ComputePipelineInfoHash
-{
- std::size_t operator()(const ComputePipelineInfo& key) const;
-};
-
-bool operator==(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs);
-bool operator!=(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs);
-bool operator<(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs);
-bool operator>(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs);
-
class ObjectCache
{
public:
@@ -103,14 +57,6 @@ public:
return m_utility_shader_uniform_buffer.get();
}
- // Get utility shader header based on current config.
- std::string GetUtilityShaderHeader() const;
-
- // Accesses ShaderGen shader caches
- VkShaderModule GetVertexShaderForUid(const VertexShaderUid& uid);
- VkShaderModule GetGeometryShaderForUid(const GeometryShaderUid& uid);
- VkShaderModule GetPixelShaderForUid(const PixelShaderUid& uid);
-
// Static samplers
VkSampler GetPointSampler() const { return m_point_sampler; }
VkSampler GetLinearSampler() const { return m_linear_sampler; }
@@ -119,64 +65,17 @@ public:
// Perform at startup, create descriptor layouts, compiles all static shaders.
bool Initialize();
- // Creates a pipeline for the specified description. The resulting pipeline, if successful
- // is not stored anywhere, this is left up to the caller.
- VkPipeline CreatePipeline(const PipelineInfo& info);
-
- // Find a pipeline by the specified description, if not found, attempts to create it.
- VkPipeline GetPipeline(const PipelineInfo& info);
-
- // Find a pipeline by the specified description, if not found, attempts to create it. If this
- // resulted in a pipeline being created, the second field of the return value will be false,
- // otherwise for a cache hit it will be true.
- std::pair<VkPipeline, bool> GetPipelineWithCacheResult(const PipelineInfo& info);
-
- // Creates a compute pipeline, and does not track the handle.
- VkPipeline CreateComputePipeline(const ComputePipelineInfo& info);
-
- // Find a pipeline by the specified description, if not found, attempts to create it
- VkPipeline GetComputePipeline(const ComputePipelineInfo& info);
-
- // Clears our pipeline cache of all objects. This is necessary when recompiling shaders,
- // as drivers are free to return the same pointer again, which means that we may end up using
- // and old pipeline object if they are not cleared first. Some stutter may be experienced
- // while our cache is rebuilt on use, but the pipeline cache object should mitigate this.
- // NOTE: Ensure that none of these objects are in use before calling.
- void ClearPipelineCache();
-
- // Saves the pipeline cache to disk. Call when shutting down.
- void SavePipelineCache();
-
// Clear sampler cache, use when anisotropy mode changes
// WARNING: Ensure none of the objects from here are in use when calling
void ClearSamplerCache();
- // Recompile shared shaders, call when stereo mode changes.
- void RecompileSharedShaders();
-
- // Reload pipeline cache. This will destroy all pipelines.
- void ReloadShaderAndPipelineCaches();
-
- // Shared shader accessors
- VkShaderModule GetScreenQuadVertexShader() const { return m_screen_quad_vertex_shader; }
- VkShaderModule GetPassthroughVertexShader() const { return m_passthrough_vertex_shader; }
- VkShaderModule GetScreenQuadGeometryShader() const { return m_screen_quad_geometry_shader; }
- VkShaderModule GetPassthroughGeometryShader() const { return m_passthrough_geometry_shader; }
private:
- bool CreatePipelineCache();
- bool LoadPipelineCache();
- bool ValidatePipelineCache(const u8* data, size_t data_length);
- void DestroyPipelineCache();
- void LoadShaderCaches();
- void DestroyShaderCaches();
bool CreateDescriptorSetLayouts();
void DestroyDescriptorSetLayouts();
bool CreatePipelineLayouts();
void DestroyPipelineLayouts();
bool CreateUtilityShaderVertexFormat();
bool CreateStaticSamplers();
- bool CompileSharedShaders();
- void DestroySharedShaders();
void DestroySamplers();
std::array<VkDescriptorSetLayout, NUM_DESCRIPTOR_SET_LAYOUTS> m_descriptor_set_layouts = {};
@@ -186,32 +85,10 @@ private:
std::unique_ptr<StreamBuffer> m_utility_shader_vertex_buffer;
std::unique_ptr<StreamBuffer> m_utility_shader_uniform_buffer;
- template <typename Uid>
- struct ShaderCache
- {
- std::map<Uid, VkShaderModule> shader_map;
- LinearDiskCache<Uid, u32> disk_cache;
- };
- ShaderCache<VertexShaderUid> m_vs_cache;
- ShaderCache<GeometryShaderUid> m_gs_cache;
- ShaderCache<PixelShaderUid> m_ps_cache;
-
- std::unordered_map<PipelineInfo, VkPipeline, PipelineInfoHash> m_pipeline_objects;
- std::unordered_map<ComputePipelineInfo, VkPipeline, ComputePipelineInfoHash>
- m_compute_pipeline_objects;
- VkPipelineCache m_pipeline_cache = VK_NULL_HANDLE;
- std::string m_pipeline_cache_filename;
-
VkSampler m_point_sampler = VK_NULL_HANDLE;
VkSampler m_linear_sampler = VK_NULL_HANDLE;
std::map<SamplerState, VkSampler> m_sampler_cache;
-
- // Utility/shared shaders
- VkShaderModule m_screen_quad_vertex_shader = VK_NULL_HANDLE;
- VkShaderModule m_passthrough_vertex_shader = VK_NULL_HANDLE;
- VkShaderModule m_screen_quad_geometry_shader = VK_NULL_HANDLE;
- VkShaderModule m_passthrough_geometry_shader = VK_NULL_HANDLE;
};
extern std::unique_ptr<ObjectCache> g_object_cache;
diff --git a/Source/Core/VideoBackends/Vulkan/PostProcessing.cpp b/Source/Core/VideoBackends/Vulkan/PostProcessing.cpp
index 971de51180..991117567c 100644
--- a/Source/Core/VideoBackends/Vulkan/PostProcessing.cpp
+++ b/Source/Core/VideoBackends/Vulkan/PostProcessing.cpp
@@ -10,6 +10,7 @@
#include "VideoBackends/Vulkan/CommandBufferManager.h"
#include "VideoBackends/Vulkan/ObjectCache.h"
+#include "VideoBackends/Vulkan/ShaderCache.h"
#include "VideoBackends/Vulkan/Texture2D.h"
#include "VideoBackends/Vulkan/Util.h"
#include "VideoBackends/Vulkan/VulkanContext.h"
@@ -43,12 +44,12 @@ void VulkanPostProcessing::BlitFromTexture(const TargetRectangle& dst, const Tar
{
// If the source layer is negative we simply copy all available layers.
VkShaderModule geometry_shader =
- src_layer < 0 ? g_object_cache->GetPassthroughGeometryShader() : VK_NULL_HANDLE;
+ src_layer < 0 ? g_shader_cache->GetPassthroughGeometryShader() : VK_NULL_HANDLE;
VkShaderModule fragment_shader =
m_fragment_shader != VK_NULL_HANDLE ? m_fragment_shader : m_default_fragment_shader;
UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(),
g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD), render_pass,
- g_object_cache->GetPassthroughVertexShader(), geometry_shader,
+ g_shader_cache->GetPassthroughVertexShader(), geometry_shader,
fragment_shader);
// Source is always bound.
@@ -240,7 +241,7 @@ bool VulkanPostProcessing::RecompileShader()
if (m_fragment_shader != VK_NULL_HANDLE)
{
g_command_buffer_mgr->WaitForGPUIdle();
- g_object_cache->ClearPipelineCache();
+ g_shader_cache->ClearPipelineCache();
vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_fragment_shader, nullptr);
m_fragment_shader = VK_NULL_HANDLE;
}
diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.cpp b/Source/Core/VideoBackends/Vulkan/Renderer.cpp
index 599792d493..4e72d94a61 100644
--- a/Source/Core/VideoBackends/Vulkan/Renderer.cpp
+++ b/Source/Core/VideoBackends/Vulkan/Renderer.cpp
@@ -462,8 +462,8 @@ void Renderer::ClearScreen(const EFBRectangle& rc, bool color_enable, bool alpha
UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(),
g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD),
FramebufferManager::GetInstance()->GetEFBLoadRenderPass(),
- g_object_cache->GetPassthroughVertexShader(),
- g_object_cache->GetPassthroughGeometryShader(), m_clear_fragment_shader);
+ g_shader_cache->GetPassthroughVertexShader(),
+ g_shader_cache->GetPassthroughGeometryShader(), m_clear_fragment_shader);
draw.SetRasterizationState(rs_state);
draw.SetDepthStencilState(depth_state);
@@ -1168,8 +1168,8 @@ void Renderer::CheckForConfigChanges()
BindEFBToStateTracker();
RecompileShaders();
FramebufferManager::GetInstance()->RecompileShaders();
- g_object_cache->ReloadShaderAndPipelineCaches();
- g_object_cache->RecompileSharedShaders();
+ g_shader_cache->ReloadShaderAndPipelineCaches();
+ g_shader_cache->RecompileSharedShaders();
StateTracker::GetInstance()->InvalidateShaderPointers();
StateTracker::GetInstance()->ReloadPipelineUIDCache();
}
@@ -1500,7 +1500,7 @@ bool Renderer::CompileShaders()
)";
- std::string source = g_object_cache->GetUtilityShaderHeader() + CLEAR_FRAGMENT_SHADER_SOURCE;
+ std::string source = g_shader_cache->GetUtilityShaderHeader() + CLEAR_FRAGMENT_SHADER_SOURCE;
m_clear_fragment_shader = Util::CompileAndCreateFragmentShader(source);
return m_clear_fragment_shader != VK_NULL_HANDLE;
diff --git a/Source/Core/VideoBackends/Vulkan/ShaderCache.cpp b/Source/Core/VideoBackends/Vulkan/ShaderCache.cpp
new file mode 100644
index 0000000000..79a77458b6
--- /dev/null
+++ b/Source/Core/VideoBackends/Vulkan/ShaderCache.cpp
@@ -0,0 +1,1029 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "VideoBackends/Vulkan/ShaderCache.h"
+
+#include <algorithm>
+#include <sstream>
+#include <type_traits>
+#include <xxhash.h>
+
+#include "Common/Assert.h"
+#include "Common/CommonFuncs.h"
+#include "Common/LinearDiskCache.h"
+#include "Common/MsgHandler.h"
+
+#include "Core/ConfigManager.h"
+
+#include "VideoBackends/Vulkan/ShaderCompiler.h"
+#include "VideoBackends/Vulkan/StreamBuffer.h"
+#include "VideoBackends/Vulkan/Util.h"
+#include "VideoBackends/Vulkan/VertexFormat.h"
+#include "VideoBackends/Vulkan/VulkanContext.h"
+#include "VideoCommon/Statistics.h"
+
+namespace Vulkan
+{
+std::unique_ptr<ShaderCache> g_shader_cache;
+
+ShaderCache::ShaderCache()
+{
+}
+
+ShaderCache::~ShaderCache()
+{
+ DestroyPipelineCache();
+ DestroyShaderCaches();
+ DestroySharedShaders();
+}
+
+bool ShaderCache::Initialize()
+{
+ if (g_ActiveConfig.bShaderCache)
+ {
+ LoadShaderCaches();
+ if (!LoadPipelineCache())
+ return false;
+ }
+ else
+ {
+ if (!CreatePipelineCache())
+ return false;
+ }
+
+ if (!CompileSharedShaders())
+ return false;
+
+ return true;
+}
+
+static bool IsStripPrimitiveTopology(VkPrimitiveTopology topology)
+{
+ return topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP ||
+ topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP ||
+ topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY ||
+ topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY;
+}
+
+static VkPipelineRasterizationStateCreateInfo
+GetVulkanRasterizationState(const RasterizationState& state)
+{
+ return {
+ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, // VkStructureType sType
+ nullptr, // const void* pNext
+ 0, // VkPipelineRasterizationStateCreateFlags flags
+ state.depth_clamp, // VkBool32 depthClampEnable
+ VK_FALSE, // VkBool32 rasterizerDiscardEnable
+ VK_POLYGON_MODE_FILL, // VkPolygonMode polygonMode
+ state.cull_mode, // VkCullModeFlags cullMode
+ VK_FRONT_FACE_CLOCKWISE, // VkFrontFace frontFace
+ VK_FALSE, // VkBool32 depthBiasEnable
+ 0.0f, // float depthBiasConstantFactor
+ 0.0f, // float depthBiasClamp
+ 0.0f, // float depthBiasSlopeFactor
+ 1.0f // float lineWidth
+ };
+}
+
+static VkPipelineMultisampleStateCreateInfo
+GetVulkanMultisampleState(const RasterizationState& rs_state)
+{
+ return {
+ VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, // VkStructureType sType
+ nullptr, // const void* pNext
+ 0, // VkPipelineMultisampleStateCreateFlags flags
+ rs_state.samples, // VkSampleCountFlagBits rasterizationSamples
+ rs_state.per_sample_shading, // VkBool32 sampleShadingEnable
+ 1.0f, // float minSampleShading
+ nullptr, // const VkSampleMask* pSampleMask;
+ VK_FALSE, // VkBool32 alphaToCoverageEnable
+ VK_FALSE // VkBool32 alphaToOneEnable
+ };
+}
+
+static VkPipelineDepthStencilStateCreateInfo
+GetVulkanDepthStencilState(const DepthStencilState& state)
+{
+ return {
+ VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, // VkStructureType sType
+ nullptr, // const void* pNext
+ 0, // VkPipelineDepthStencilStateCreateFlags flags
+ state.test_enable, // VkBool32 depthTestEnable
+ state.write_enable, // VkBool32 depthWriteEnable
+ state.compare_op, // VkCompareOp depthCompareOp
+ VK_FALSE, // VkBool32 depthBoundsTestEnable
+ VK_FALSE, // VkBool32 stencilTestEnable
+ {}, // VkStencilOpState front
+ {}, // VkStencilOpState back
+ 0.0f, // float minDepthBounds
+ 1.0f // float maxDepthBounds
+ };
+}
+
+static VkPipelineColorBlendAttachmentState GetVulkanAttachmentBlendState(const BlendingState& state)
+{
+ VkPipelineColorBlendAttachmentState vk_state = {};
+ vk_state.blendEnable = static_cast<VkBool32>(state.blendenable);
+ vk_state.colorBlendOp = state.subtract ? VK_BLEND_OP_REVERSE_SUBTRACT : VK_BLEND_OP_ADD;
+ vk_state.alphaBlendOp = state.subtractAlpha ? VK_BLEND_OP_REVERSE_SUBTRACT : VK_BLEND_OP_ADD;
+
+ if (state.usedualsrc && g_vulkan_context->SupportsDualSourceBlend())
+ {
+ static constexpr std::array<VkBlendFactor, 8> src_factors = {
+ {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_DST_COLOR,
+ VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, VK_BLEND_FACTOR_SRC1_ALPHA,
+ VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA, VK_BLEND_FACTOR_DST_ALPHA,
+ VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}};
+ static constexpr std::array<VkBlendFactor, 8> dst_factors = {
+ {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_SRC_COLOR,
+ VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, VK_BLEND_FACTOR_SRC1_ALPHA,
+ VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA, VK_BLEND_FACTOR_DST_ALPHA,
+ VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}};
+
+ vk_state.srcColorBlendFactor = src_factors[state.srcfactor];
+ vk_state.srcAlphaBlendFactor = src_factors[state.srcfactoralpha];
+ vk_state.dstColorBlendFactor = dst_factors[state.dstfactor];
+ vk_state.dstAlphaBlendFactor = dst_factors[state.dstfactoralpha];
+ }
+ else
+ {
+ static constexpr std::array<VkBlendFactor, 8> src_factors = {
+ {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_DST_COLOR,
+ VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, VK_BLEND_FACTOR_SRC_ALPHA,
+ VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_FACTOR_DST_ALPHA,
+ VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}};
+
+ static constexpr std::array<VkBlendFactor, 8> dst_factors = {
+ {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_SRC_COLOR,
+ VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, VK_BLEND_FACTOR_SRC_ALPHA,
+ VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_FACTOR_DST_ALPHA,
+ VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}};
+
+ vk_state.srcColorBlendFactor = src_factors[state.srcfactor];
+ vk_state.srcAlphaBlendFactor = src_factors[state.srcfactoralpha];
+ vk_state.dstColorBlendFactor = dst_factors[state.dstfactor];
+ vk_state.dstAlphaBlendFactor = dst_factors[state.dstfactoralpha];
+ }
+
+ if (state.colorupdate)
+ {
+ vk_state.colorWriteMask =
+ VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT;
+ }
+ else
+ {
+ vk_state.colorWriteMask = 0;
+ }
+
+ if (state.alphaupdate)
+ vk_state.colorWriteMask |= VK_COLOR_COMPONENT_A_BIT;
+
+ return vk_state;
+}
+
+static VkPipelineColorBlendStateCreateInfo
+GetVulkanColorBlendState(const BlendingState& state,
+ const VkPipelineColorBlendAttachmentState* attachments,
+ uint32_t num_attachments)
+{
+ static constexpr std::array<VkLogicOp, 16> vk_logic_ops = {
+ {VK_LOGIC_OP_CLEAR, VK_LOGIC_OP_AND, VK_LOGIC_OP_AND_REVERSE, VK_LOGIC_OP_COPY,
+ VK_LOGIC_OP_AND_INVERTED, VK_LOGIC_OP_NO_OP, VK_LOGIC_OP_XOR, VK_LOGIC_OP_OR,
+ VK_LOGIC_OP_NOR, VK_LOGIC_OP_EQUIVALENT, VK_LOGIC_OP_INVERT, VK_LOGIC_OP_OR_REVERSE,
+ VK_LOGIC_OP_COPY_INVERTED, VK_LOGIC_OP_OR_INVERTED, VK_LOGIC_OP_NAND, VK_LOGIC_OP_SET}};
+
+ VkBool32 vk_logic_op_enable = static_cast<VkBool32>(state.logicopenable);
+ if (vk_logic_op_enable && !g_vulkan_context->SupportsLogicOps())
+ {
+ // At the time of writing, Adreno and Mali drivers didn't support logic ops.
+ // The "emulation" through blending path has been removed, so just disable it completely.
+ // These drivers don't support dual-source blend either, so issues are to be expected.
+ vk_logic_op_enable = VK_FALSE;
+ }
+
+ VkLogicOp vk_logic_op = vk_logic_op_enable ? vk_logic_ops[state.logicmode] : VK_LOGIC_OP_CLEAR;
+
+ VkPipelineColorBlendStateCreateInfo vk_state = {
+ VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, // VkStructureType sType
+ nullptr, // const void* pNext
+ 0, // VkPipelineColorBlendStateCreateFlags flags
+ vk_logic_op_enable, // VkBool32 logicOpEnable
+ vk_logic_op, // VkLogicOp logicOp
+ num_attachments, // uint32_t attachmentCount
+ attachments, // const VkPipelineColorBlendAttachmentState* pAttachments
+ {1.0f, 1.0f, 1.0f, 1.0f} // float blendConstants[4]
+ };
+
+ return vk_state;
+}
+
+VkPipeline ShaderCache::CreatePipeline(const PipelineInfo& info)
+{
+ // Declare descriptors for empty vertex buffers/attributes
+ static const VkPipelineVertexInputStateCreateInfo empty_vertex_input_state = {
+ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, // VkStructureType sType
+ nullptr, // const void* pNext
+ 0, // VkPipelineVertexInputStateCreateFlags flags
+ 0, // uint32_t vertexBindingDescriptionCount
+ nullptr, // const VkVertexInputBindingDescription* pVertexBindingDescriptions
+ 0, // uint32_t vertexAttributeDescriptionCount
+ nullptr // const VkVertexInputAttributeDescription* pVertexAttributeDescriptions
+ };
+
+ // Vertex inputs
+ const VkPipelineVertexInputStateCreateInfo& vertex_input_state =
+ info.vertex_format ? info.vertex_format->GetVertexInputStateInfo() : empty_vertex_input_state;
+
+ // Input assembly
+ VkPipelineInputAssemblyStateCreateInfo input_assembly_state = {
+ VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, // VkStructureType sType
+ nullptr, // const void* pNext
+ 0, // VkPipelineInputAssemblyStateCreateFlags flags
+ info.primitive_topology, // VkPrimitiveTopology topology
+ VK_FALSE // VkBool32 primitiveRestartEnable
+ };
+
+ // See Vulkan spec, section 19:
+ // If topology is VK_PRIMITIVE_TOPOLOGY_POINT_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST,
+ // VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY,
+ // VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY or VK_PRIMITIVE_TOPOLOGY_PATCH_LIST,
+ // primitiveRestartEnable must be VK_FALSE
+ if (g_ActiveConfig.backend_info.bSupportsPrimitiveRestart &&
+ IsStripPrimitiveTopology(info.primitive_topology))
+ {
+ input_assembly_state.primitiveRestartEnable = VK_TRUE;
+ }
+
+ // Shaders to stages
+ VkPipelineShaderStageCreateInfo shader_stages[3];
+ uint32_t num_shader_stages = 0;
+ if (info.vs != VK_NULL_HANDLE)
+ {
+ shader_stages[num_shader_stages++] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ nullptr,
+ 0,
+ VK_SHADER_STAGE_VERTEX_BIT,
+ info.vs,
+ "main"};
+ }
+ if (info.gs != VK_NULL_HANDLE)
+ {
+ shader_stages[num_shader_stages++] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ nullptr,
+ 0,
+ VK_SHADER_STAGE_GEOMETRY_BIT,
+ info.gs,
+ "main"};
+ }
+ if (info.ps != VK_NULL_HANDLE)
+ {
+ shader_stages[num_shader_stages++] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ nullptr,
+ 0,
+ VK_SHADER_STAGE_FRAGMENT_BIT,
+ info.ps,
+ "main"};
+ }
+
+ // Fill in Vulkan descriptor structs from our state structures.
+ VkPipelineRasterizationStateCreateInfo rasterization_state =
+ GetVulkanRasterizationState(info.rasterization_state);
+ VkPipelineMultisampleStateCreateInfo multisample_state =
+ GetVulkanMultisampleState(info.rasterization_state);
+ VkPipelineDepthStencilStateCreateInfo depth_stencil_state =
+ GetVulkanDepthStencilState(info.depth_stencil_state);
+ VkPipelineColorBlendAttachmentState blend_attachment_state =
+ GetVulkanAttachmentBlendState(info.blend_state);
+ VkPipelineColorBlendStateCreateInfo blend_state =
+ GetVulkanColorBlendState(info.blend_state, &blend_attachment_state, 1);
+
+ // This viewport isn't used, but needs to be specified anyway.
+ static const VkViewport viewport = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
+ static const VkRect2D scissor = {{0, 0}, {1, 1}};
+ static const VkPipelineViewportStateCreateInfo viewport_state = {
+ VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ nullptr,
+ 0, // VkPipelineViewportStateCreateFlags flags;
+ 1, // uint32_t viewportCount
+ &viewport, // const VkViewport* pViewports
+ 1, // uint32_t scissorCount
+ &scissor // const VkRect2D* pScissors
+ };
+
+ // Set viewport and scissor dynamic state so we can change it elsewhere.
+ static const VkDynamicState dynamic_states[] = {VK_DYNAMIC_STATE_VIEWPORT,
+ VK_DYNAMIC_STATE_SCISSOR};
+ static const VkPipelineDynamicStateCreateInfo dynamic_state = {
+ VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, nullptr,
+ 0, // VkPipelineDynamicStateCreateFlags flags
+ static_cast<u32>(ArraySize(dynamic_states)), // uint32_t dynamicStateCount
+ dynamic_states // const VkDynamicState* pDynamicStates
+ };
+
+ // Combine to full pipeline info structure.
+ VkGraphicsPipelineCreateInfo pipeline_info = {
+ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ nullptr, // VkStructureType sType
+ 0, // VkPipelineCreateFlags flags
+ num_shader_stages, // uint32_t stageCount
+ shader_stages, // const VkPipelineShaderStageCreateInfo* pStages
+ &vertex_input_state, // const VkPipelineVertexInputStateCreateInfo* pVertexInputState
+ &input_assembly_state, // const VkPipelineInputAssemblyStateCreateInfo* pInputAssemblyState
+ nullptr, // const VkPipelineTessellationStateCreateInfo* pTessellationState
+ &viewport_state, // const VkPipelineViewportStateCreateInfo* pViewportState
+ &rasterization_state, // const VkPipelineRasterizationStateCreateInfo* pRasterizationState
+ &multisample_state, // const VkPipelineMultisampleStateCreateInfo* pMultisampleState
+ &depth_stencil_state, // const VkPipelineDepthStencilStateCreateInfo* pDepthStencilState
+ &blend_state, // const VkPipelineColorBlendStateCreateInfo* pColorBlendState
+ &dynamic_state, // const VkPipelineDynamicStateCreateInfo* pDynamicState
+ info.pipeline_layout, // VkPipelineLayout layout
+ info.render_pass, // VkRenderPass renderPass
+ 0, // uint32_t subpass
+ VK_NULL_HANDLE, // VkPipeline basePipelineHandle
+ -1 // int32_t basePipelineIndex
+ };
+
+ VkPipeline pipeline;
+ VkResult res = vkCreateGraphicsPipelines(g_vulkan_context->GetDevice(), m_pipeline_cache, 1,
+ &pipeline_info, nullptr, &pipeline);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateGraphicsPipelines failed: ");
+ return VK_NULL_HANDLE;
+ }
+
+ return pipeline;
+}
+
+VkPipeline ShaderCache::GetPipeline(const PipelineInfo& info)
+{
+ return GetPipelineWithCacheResult(info).first;
+}
+
+std::pair<VkPipeline, bool> ShaderCache::GetPipelineWithCacheResult(const PipelineInfo& info)
+{
+ auto iter = m_pipeline_objects.find(info);
+ if (iter != m_pipeline_objects.end())
+ return {iter->second, true};
+
+ VkPipeline pipeline = CreatePipeline(info);
+ m_pipeline_objects.emplace(info, pipeline);
+ return {pipeline, false};
+}
+
+VkPipeline ShaderCache::CreateComputePipeline(const ComputePipelineInfo& info)
+{
+ VkComputePipelineCreateInfo pipeline_info = {VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
+ nullptr,
+ 0,
+ {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ nullptr, 0, VK_SHADER_STAGE_COMPUTE_BIT, info.cs,
+ "main", nullptr},
+ info.pipeline_layout,
+ VK_NULL_HANDLE,
+ -1};
+
+ VkPipeline pipeline;
+ VkResult res = vkCreateComputePipelines(g_vulkan_context->GetDevice(), VK_NULL_HANDLE, 1,
+ &pipeline_info, nullptr, &pipeline);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateComputePipelines failed: ");
+ return VK_NULL_HANDLE;
+ }
+
+ return pipeline;
+}
+
+VkPipeline ShaderCache::GetComputePipeline(const ComputePipelineInfo& info)
+{
+ auto iter = m_compute_pipeline_objects.find(info);
+ if (iter != m_compute_pipeline_objects.end())
+ return iter->second;
+
+ VkPipeline pipeline = CreateComputePipeline(info);
+ m_compute_pipeline_objects.emplace(info, pipeline);
+ return pipeline;
+}
+
+void ShaderCache::ClearPipelineCache()
+{
+ for (const auto& it : m_pipeline_objects)
+ {
+ if (it.second != VK_NULL_HANDLE)
+ vkDestroyPipeline(g_vulkan_context->GetDevice(), it.second, nullptr);
+ }
+ m_pipeline_objects.clear();
+
+ for (const auto& it : m_compute_pipeline_objects)
+ {
+ if (it.second != VK_NULL_HANDLE)
+ vkDestroyPipeline(g_vulkan_context->GetDevice(), it.second, nullptr);
+ }
+ m_compute_pipeline_objects.clear();
+}
+
+class PipelineCacheReadCallback : public LinearDiskCacheReader<u32, u8>
+{
+public:
+ PipelineCacheReadCallback(std::vector<u8>* data) : m_data(data) {}
+ void Read(const u32& key, const u8* value, u32 value_size) override
+ {
+ m_data->resize(value_size);
+ if (value_size > 0)
+ memcpy(m_data->data(), value, value_size);
+ }
+
+private:
+ std::vector<u8>* m_data;
+};
+
+class PipelineCacheReadIgnoreCallback : public LinearDiskCacheReader<u32, u8>
+{
+public:
+ void Read(const u32& key, const u8* value, u32 value_size) override {}
+};
+
+bool ShaderCache::CreatePipelineCache()
+{
+ // Vulkan pipeline caches can be shared between games for shader compile time reduction.
+ // This assumes that drivers don't create all pipelines in the cache on load time, only
+ // when a lookup occurs that matches a pipeline (or pipeline data) in the cache.
+ m_pipeline_cache_filename = GetDiskShaderCacheFileName(APIType::Vulkan, "Pipeline", false, true);
+
+ VkPipelineCacheCreateInfo info = {
+ VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, // VkStructureType sType
+ nullptr, // const void* pNext
+ 0, // VkPipelineCacheCreateFlags flags
+ 0, // size_t initialDataSize
+ nullptr // const void* pInitialData
+ };
+
+ VkResult res =
+ vkCreatePipelineCache(g_vulkan_context->GetDevice(), &info, nullptr, &m_pipeline_cache);
+ if (res == VK_SUCCESS)
+ return true;
+
+ LOG_VULKAN_ERROR(res, "vkCreatePipelineCache failed: ");
+ return false;
+}
+
+bool ShaderCache::LoadPipelineCache()
+{
+ // We have to keep the pipeline cache file name around since when we save it
+ // we delete the old one, by which time the game's unique ID is already cleared.
+ m_pipeline_cache_filename = GetDiskShaderCacheFileName(APIType::Vulkan, "Pipeline", false, true);
+
+ std::vector<u8> disk_data;
+ LinearDiskCache<u32, u8> disk_cache;
+ PipelineCacheReadCallback read_callback(&disk_data);
+ if (disk_cache.OpenAndRead(m_pipeline_cache_filename, read_callback) != 1)
+ disk_data.clear();
+
+ if (!disk_data.empty() && !ValidatePipelineCache(disk_data.data(), disk_data.size()))
+ {
+ // Don't use this data. In fact, we should delete it to prevent it from being used next time.
+ File::Delete(m_pipeline_cache_filename);
+ return CreatePipelineCache();
+ }
+
+ VkPipelineCacheCreateInfo info = {
+ VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, // VkStructureType sType
+ nullptr, // const void* pNext
+ 0, // VkPipelineCacheCreateFlags flags
+ disk_data.size(), // size_t initialDataSize
+ disk_data.data() // const void* pInitialData
+ };
+
+ VkResult res =
+ vkCreatePipelineCache(g_vulkan_context->GetDevice(), &info, nullptr, &m_pipeline_cache);
+ if (res == VK_SUCCESS)
+ return true;
+
+ // Failed to create pipeline cache, try with it empty.
+ LOG_VULKAN_ERROR(res, "vkCreatePipelineCache failed, trying empty cache: ");
+ return CreatePipelineCache();
+}
+
+// Based on Vulkan 1.0 specification,
+// Table 9.1. Layout for pipeline cache header version VK_PIPELINE_CACHE_HEADER_VERSION_ONE
+// NOTE: This data is assumed to be in little-endian format.
+#pragma pack(push, 4)
+struct VK_PIPELINE_CACHE_HEADER
+{
+ u32 header_length;
+ u32 header_version;
+ u32 vendor_id;
+ u32 device_id;
+ u8 uuid[VK_UUID_SIZE];
+};
+#pragma pack(pop)
+// TODO: Remove the #if here when GCC 5 is a minimum build requirement.
+#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5
+static_assert(std::has_trivial_copy_constructor<VK_PIPELINE_CACHE_HEADER>::value,
+ "VK_PIPELINE_CACHE_HEADER must be trivially copyable");
+#else
+static_assert(std::is_trivially_copyable<VK_PIPELINE_CACHE_HEADER>::value,
+ "VK_PIPELINE_CACHE_HEADER must be trivially copyable");
+#endif
+
+bool ShaderCache::ValidatePipelineCache(const u8* data, size_t data_length)
+{
+ if (data_length < sizeof(VK_PIPELINE_CACHE_HEADER))
+ {
+ ERROR_LOG(VIDEO, "Pipeline cache failed validation: Invalid header");
+ return false;
+ }
+
+ VK_PIPELINE_CACHE_HEADER header;
+ std::memcpy(&header, data, sizeof(header));
+ if (header.header_length < sizeof(VK_PIPELINE_CACHE_HEADER))
+ {
+ ERROR_LOG(VIDEO, "Pipeline cache failed validation: Invalid header length");
+ return false;
+ }
+
+ if (header.header_version != VK_PIPELINE_CACHE_HEADER_VERSION_ONE)
+ {
+ ERROR_LOG(VIDEO, "Pipeline cache failed validation: Invalid header version");
+ return false;
+ }
+
+ if (header.vendor_id != g_vulkan_context->GetDeviceProperties().vendorID)
+ {
+ ERROR_LOG(VIDEO,
+ "Pipeline cache failed validation: Incorrect vendor ID (file: 0x%X, device: 0x%X)",
+ header.vendor_id, g_vulkan_context->GetDeviceProperties().vendorID);
+ return false;
+ }
+
+ if (header.device_id != g_vulkan_context->GetDeviceProperties().deviceID)
+ {
+ ERROR_LOG(VIDEO,
+ "Pipeline cache failed validation: Incorrect device ID (file: 0x%X, device: 0x%X)",
+ header.device_id, g_vulkan_context->GetDeviceProperties().deviceID);
+ return false;
+ }
+
+ if (std::memcmp(header.uuid, g_vulkan_context->GetDeviceProperties().pipelineCacheUUID,
+ VK_UUID_SIZE) != 0)
+ {
+ ERROR_LOG(VIDEO, "Pipeline cache failed validation: Incorrect UUID");
+ return false;
+ }
+
+ return true;
+}
+
+void ShaderCache::DestroyPipelineCache()
+{
+ ClearPipelineCache();
+ vkDestroyPipelineCache(g_vulkan_context->GetDevice(), m_pipeline_cache, nullptr);
+ m_pipeline_cache = VK_NULL_HANDLE;
+}
+
+void ShaderCache::SavePipelineCache()
+{
+ size_t data_size;
+ VkResult res =
+ vkGetPipelineCacheData(g_vulkan_context->GetDevice(), m_pipeline_cache, &data_size, nullptr);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkGetPipelineCacheData failed: ");
+ return;
+ }
+
+ std::vector<u8> data(data_size);
+ res = vkGetPipelineCacheData(g_vulkan_context->GetDevice(), m_pipeline_cache, &data_size,
+ data.data());
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkGetPipelineCacheData failed: ");
+ return;
+ }
+
+ // Delete the old cache and re-create.
+ File::Delete(m_pipeline_cache_filename);
+
+ // We write a single key of 1, with the entire pipeline cache data.
+ // Not ideal, but our disk cache class does not support just writing a single blob
+ // of data without specifying a key.
+ LinearDiskCache<u32, u8> disk_cache;
+ PipelineCacheReadIgnoreCallback callback;
+ disk_cache.OpenAndRead(m_pipeline_cache_filename, callback);
+ disk_cache.Append(1, data.data(), static_cast<u32>(data.size()));
+ disk_cache.Close();
+}
+
+// Cache inserter that is called back when reading from the file
+template <typename Uid>
+struct ShaderCacheReader : public LinearDiskCacheReader<Uid, u32>
+{
+ ShaderCacheReader(std::map<Uid, VkShaderModule>& shader_map) : m_shader_map(shader_map) {}
+ void Read(const Uid& key, const u32* value, u32 value_size) override
+ {
+ // We don't insert null modules into the shader map since creation could succeed later on.
+ // e.g. we're generating bad code, but fix this in a later version, and for some reason
+ // the cache is not invalidated.
+ VkShaderModule module = Util::CreateShaderModule(value, value_size);
+ if (module == VK_NULL_HANDLE)
+ return;
+
+ m_shader_map.emplace(key, module);
+ }
+
+ std::map<Uid, VkShaderModule>& m_shader_map;
+};
+
+void ShaderCache::LoadShaderCaches()
+{
+ ShaderCacheReader<VertexShaderUid> vs_reader(m_vs_cache.shader_map);
+ m_vs_cache.disk_cache.OpenAndRead(GetDiskShaderCacheFileName(APIType::Vulkan, "VS", true, true),
+ vs_reader);
+
+ ShaderCacheReader<PixelShaderUid> ps_reader(m_ps_cache.shader_map);
+ m_ps_cache.disk_cache.OpenAndRead(GetDiskShaderCacheFileName(APIType::Vulkan, "PS", true, true),
+ ps_reader);
+
+ if (g_vulkan_context->SupportsGeometryShaders())
+ {
+ ShaderCacheReader<GeometryShaderUid> gs_reader(m_gs_cache.shader_map);
+ m_gs_cache.disk_cache.OpenAndRead(GetDiskShaderCacheFileName(APIType::Vulkan, "GS", true, true),
+ gs_reader);
+ }
+
+ SETSTAT(stats.numPixelShadersCreated, static_cast<int>(m_ps_cache.shader_map.size()));
+ SETSTAT(stats.numPixelShadersAlive, static_cast<int>(m_ps_cache.shader_map.size()));
+ SETSTAT(stats.numVertexShadersCreated, static_cast<int>(m_vs_cache.shader_map.size()));
+ SETSTAT(stats.numVertexShadersAlive, static_cast<int>(m_vs_cache.shader_map.size()));
+}
+
+template <typename T>
+static void DestroyShaderCache(T& cache)
+{
+ cache.disk_cache.Sync();
+ cache.disk_cache.Close();
+ for (const auto& it : cache.shader_map)
+ {
+ if (it.second != VK_NULL_HANDLE)
+ vkDestroyShaderModule(g_vulkan_context->GetDevice(), it.second, nullptr);
+ }
+ cache.shader_map.clear();
+}
+
+void ShaderCache::DestroyShaderCaches()
+{
+ DestroyShaderCache(m_vs_cache);
+ DestroyShaderCache(m_ps_cache);
+
+ if (g_vulkan_context->SupportsGeometryShaders())
+ DestroyShaderCache(m_gs_cache);
+
+ SETSTAT(stats.numPixelShadersCreated, 0);
+ SETSTAT(stats.numPixelShadersAlive, 0);
+ SETSTAT(stats.numVertexShadersCreated, 0);
+ SETSTAT(stats.numVertexShadersAlive, 0);
+}
+
+VkShaderModule ShaderCache::GetVertexShaderForUid(const VertexShaderUid& uid)
+{
+ auto it = m_vs_cache.shader_map.find(uid);
+ if (it != m_vs_cache.shader_map.end())
+ return it->second;
+
+ // Not in the cache, so compile the shader.
+ ShaderCompiler::SPIRVCodeVector spv;
+ VkShaderModule module = VK_NULL_HANDLE;
+ ShaderCode source_code =
+ GenerateVertexShaderCode(APIType::Vulkan, ShaderHostConfig::GetCurrent(), uid.GetUidData());
+ if (ShaderCompiler::CompileVertexShader(&spv, source_code.GetBuffer().c_str(),
+ source_code.GetBuffer().length()))
+ {
+ module = Util::CreateShaderModule(spv.data(), spv.size());
+
+ // Append to shader cache if it created successfully.
+ if (module != VK_NULL_HANDLE)
+ {
+ m_vs_cache.disk_cache.Append(uid, spv.data(), static_cast<u32>(spv.size()));
+ INCSTAT(stats.numVertexShadersCreated);
+ INCSTAT(stats.numVertexShadersAlive);
+ }
+ }
+
+ // We still insert null entries to prevent further compilation attempts.
+ m_vs_cache.shader_map.emplace(uid, module);
+ return module;
+}
+
+VkShaderModule ShaderCache::GetGeometryShaderForUid(const GeometryShaderUid& uid)
+{
+ _assert_(g_vulkan_context->SupportsGeometryShaders());
+ auto it = m_gs_cache.shader_map.find(uid);
+ if (it != m_gs_cache.shader_map.end())
+ return it->second;
+
+ // Not in the cache, so compile the shader.
+ ShaderCompiler::SPIRVCodeVector spv;
+ VkShaderModule module = VK_NULL_HANDLE;
+ ShaderCode source_code =
+ GenerateGeometryShaderCode(APIType::Vulkan, ShaderHostConfig::GetCurrent(), uid.GetUidData());
+ if (ShaderCompiler::CompileGeometryShader(&spv, source_code.GetBuffer().c_str(),
+ source_code.GetBuffer().length()))
+ {
+ module = Util::CreateShaderModule(spv.data(), spv.size());
+
+ // Append to shader cache if it created successfully.
+ if (module != VK_NULL_HANDLE)
+ m_gs_cache.disk_cache.Append(uid, spv.data(), static_cast<u32>(spv.size()));
+ }
+
+ // We still insert null entries to prevent further compilation attempts.
+ m_gs_cache.shader_map.emplace(uid, module);
+ return module;
+}
+
+VkShaderModule ShaderCache::GetPixelShaderForUid(const PixelShaderUid& uid)
+{
+ auto it = m_ps_cache.shader_map.find(uid);
+ if (it != m_ps_cache.shader_map.end())
+ return it->second;
+
+ // Not in the cache, so compile the shader.
+ ShaderCompiler::SPIRVCodeVector spv;
+ VkShaderModule module = VK_NULL_HANDLE;
+ ShaderCode source_code =
+ GeneratePixelShaderCode(APIType::Vulkan, ShaderHostConfig::GetCurrent(), uid.GetUidData());
+ if (ShaderCompiler::CompileFragmentShader(&spv, source_code.GetBuffer().c_str(),
+ source_code.GetBuffer().length()))
+ {
+ module = Util::CreateShaderModule(spv.data(), spv.size());
+
+ // Append to shader cache if it created successfully.
+ if (module != VK_NULL_HANDLE)
+ {
+ m_ps_cache.disk_cache.Append(uid, spv.data(), static_cast<u32>(spv.size()));
+ INCSTAT(stats.numPixelShadersCreated);
+ INCSTAT(stats.numPixelShadersAlive);
+ }
+ }
+
+ // We still insert null entries to prevent further compilation attempts.
+ m_ps_cache.shader_map.emplace(uid, module);
+ return module;
+}
+
+void ShaderCache::RecompileSharedShaders()
+{
+ DestroySharedShaders();
+ if (!CompileSharedShaders())
+ PanicAlert("Failed to recompile shared shaders.");
+}
+
+void ShaderCache::ReloadShaderAndPipelineCaches()
+{
+ SavePipelineCache();
+ DestroyShaderCaches();
+ DestroyPipelineCache();
+
+ if (g_ActiveConfig.bShaderCache)
+ {
+ LoadShaderCaches();
+ LoadPipelineCache();
+ }
+ else
+ {
+ CreatePipelineCache();
+ }
+}
+
+std::string ShaderCache::GetUtilityShaderHeader() const
+{
+ std::stringstream ss;
+ if (g_ActiveConfig.iMultisamples > 1)
+ {
+ ss << "#define MSAA_ENABLED 1" << std::endl;
+ ss << "#define MSAA_SAMPLES " << g_ActiveConfig.iMultisamples << std::endl;
+ if (g_ActiveConfig.bSSAA)
+ ss << "#define SSAA_ENABLED 1" << std::endl;
+ }
+
+ u32 efb_layers = (g_ActiveConfig.iStereoMode != STEREO_OFF) ? 2 : 1;
+ ss << "#define EFB_LAYERS " << efb_layers << std::endl;
+
+ return ss.str();
+}
+
+// Comparison operators for PipelineInfos
+// Since these all boil down to POD types, we can just memcmp the entire thing for speed
+// The is_trivially_copyable check fails on MSVC due to BitField.
+// TODO: Can we work around this any way?
+#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5 && !defined(_MSC_VER)
+static_assert(std::has_trivial_copy_constructor<PipelineInfo>::value,
+ "PipelineInfo is trivially copyable");
+#elif !defined(_MSC_VER)
+static_assert(std::is_trivially_copyable<PipelineInfo>::value,
+ "PipelineInfo is trivially copyable");
+#endif
+
+std::size_t PipelineInfoHash::operator()(const PipelineInfo& key) const
+{
+ return static_cast<std::size_t>(XXH64(&key, sizeof(key), 0));
+}
+
+bool operator==(const PipelineInfo& lhs, const PipelineInfo& rhs)
+{
+ return std::memcmp(&lhs, &rhs, sizeof(lhs)) == 0;
+}
+
+bool operator!=(const PipelineInfo& lhs, const PipelineInfo& rhs)
+{
+ return !operator==(lhs, rhs);
+}
+
+bool operator<(const PipelineInfo& lhs, const PipelineInfo& rhs)
+{
+ return std::memcmp(&lhs, &rhs, sizeof(lhs)) < 0;
+}
+
+bool operator>(const PipelineInfo& lhs, const PipelineInfo& rhs)
+{
+ return std::memcmp(&lhs, &rhs, sizeof(lhs)) > 0;
+}
+
+bool operator==(const SamplerState& lhs, const SamplerState& rhs)
+{
+ return lhs.bits == rhs.bits;
+}
+
+bool operator!=(const SamplerState& lhs, const SamplerState& rhs)
+{
+ return !operator==(lhs, rhs);
+}
+
+bool operator>(const SamplerState& lhs, const SamplerState& rhs)
+{
+ return lhs.bits > rhs.bits;
+}
+
+bool operator<(const SamplerState& lhs, const SamplerState& rhs)
+{
+ return lhs.bits < rhs.bits;
+}
+
+std::size_t ComputePipelineInfoHash::operator()(const ComputePipelineInfo& key) const
+{
+ return static_cast<std::size_t>(XXH64(&key, sizeof(key), 0));
+}
+
+bool operator==(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs)
+{
+ return std::memcmp(&lhs, &rhs, sizeof(lhs)) == 0;
+}
+
+bool operator!=(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs)
+{
+ return !operator==(lhs, rhs);
+}
+
+bool operator<(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs)
+{
+ return std::memcmp(&lhs, &rhs, sizeof(lhs)) < 0;
+}
+
+bool operator>(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs)
+{
+ return std::memcmp(&lhs, &rhs, sizeof(lhs)) > 0;
+}
+
+bool ShaderCache::CompileSharedShaders()
+{
+ static const char PASSTHROUGH_VERTEX_SHADER_SOURCE[] = R"(
+ layout(location = 0) in vec4 ipos;
+ layout(location = 5) in vec4 icol0;
+ layout(location = 8) in vec3 itex0;
+
+ layout(location = 0) out vec3 uv0;
+ layout(location = 1) out vec4 col0;
+
+ void main()
+ {
+ gl_Position = ipos;
+ uv0 = itex0;
+ col0 = icol0;
+ }
+ )";
+
+ static const char PASSTHROUGH_GEOMETRY_SHADER_SOURCE[] = R"(
+ layout(triangles) in;
+ layout(triangle_strip, max_vertices = EFB_LAYERS * 3) out;
+
+ layout(location = 0) in vec3 in_uv0[];
+ layout(location = 1) in vec4 in_col0[];
+
+ layout(location = 0) out vec3 out_uv0;
+ layout(location = 1) out vec4 out_col0;
+
+ void main()
+ {
+ for (int j = 0; j < EFB_LAYERS; j++)
+ {
+ for (int i = 0; i < 3; i++)
+ {
+ gl_Layer = j;
+ gl_Position = gl_in[i].gl_Position;
+ out_uv0 = vec3(in_uv0[i].xy, float(j));
+ out_col0 = in_col0[i];
+ EmitVertex();
+ }
+ EndPrimitive();
+ }
+ }
+ )";
+
+ static const char SCREEN_QUAD_VERTEX_SHADER_SOURCE[] = R"(
+ layout(location = 0) out vec3 uv0;
+
+ void main()
+ {
+ /*
+ * id &1 &2 clamp(*2-1)
+ * 0 0,0 0,0 -1,-1 TL
+ * 1 1,0 1,0 1,-1 TR
+ * 2 0,2 0,1 -1,1 BL
+ * 3 1,2 1,1 1,1 BR
+ */
+ vec2 rawpos = vec2(float(gl_VertexID & 1), clamp(float(gl_VertexID & 2), 0.0f, 1.0f));
+ gl_Position = vec4(rawpos * 2.0f - 1.0f, 0.0f, 1.0f);
+ uv0 = vec3(rawpos, 0.0f);
+ }
+ )";
+
+ static const char SCREEN_QUAD_GEOMETRY_SHADER_SOURCE[] = R"(
+ layout(triangles) in;
+ layout(triangle_strip, max_vertices = EFB_LAYERS * 3) out;
+
+ layout(location = 0) in vec3 in_uv0[];
+
+ layout(location = 0) out vec3 out_uv0;
+
+ void main()
+ {
+ for (int j = 0; j < EFB_LAYERS; j++)
+ {
+ for (int i = 0; i < 3; i++)
+ {
+ gl_Layer = j;
+ gl_Position = gl_in[i].gl_Position;
+ out_uv0 = vec3(in_uv0[i].xy, float(j));
+ EmitVertex();
+ }
+ EndPrimitive();
+ }
+ }
+ )";
+
+ std::string header = GetUtilityShaderHeader();
+
+ m_screen_quad_vertex_shader =
+ Util::CompileAndCreateVertexShader(header + SCREEN_QUAD_VERTEX_SHADER_SOURCE);
+ m_passthrough_vertex_shader =
+ Util::CompileAndCreateVertexShader(header + PASSTHROUGH_VERTEX_SHADER_SOURCE);
+ if (m_screen_quad_vertex_shader == VK_NULL_HANDLE ||
+ m_passthrough_vertex_shader == VK_NULL_HANDLE)
+ {
+ return false;
+ }
+
+ if (g_ActiveConfig.iStereoMode != STEREO_OFF && g_vulkan_context->SupportsGeometryShaders())
+ {
+ m_screen_quad_geometry_shader =
+ Util::CompileAndCreateGeometryShader(header + SCREEN_QUAD_GEOMETRY_SHADER_SOURCE);
+ m_passthrough_geometry_shader =
+ Util::CompileAndCreateGeometryShader(header + PASSTHROUGH_GEOMETRY_SHADER_SOURCE);
+ if (m_screen_quad_geometry_shader == VK_NULL_HANDLE ||
+ m_passthrough_geometry_shader == VK_NULL_HANDLE)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void ShaderCache::DestroySharedShaders()
+{
+ auto DestroyShader = [this](VkShaderModule& shader) {
+ if (shader != VK_NULL_HANDLE)
+ {
+ vkDestroyShaderModule(g_vulkan_context->GetDevice(), shader, nullptr);
+ shader = VK_NULL_HANDLE;
+ }
+ };
+
+ DestroyShader(m_screen_quad_vertex_shader);
+ DestroyShader(m_passthrough_vertex_shader);
+ DestroyShader(m_screen_quad_geometry_shader);
+ DestroyShader(m_passthrough_geometry_shader);
+}
+}
diff --git a/Source/Core/VideoBackends/Vulkan/ShaderCache.h b/Source/Core/VideoBackends/Vulkan/ShaderCache.h
new file mode 100644
index 0000000000..7a83472e38
--- /dev/null
+++ b/Source/Core/VideoBackends/Vulkan/ShaderCache.h
@@ -0,0 +1,172 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "Common/CommonTypes.h"
+#include "Common/LinearDiskCache.h"
+
+#include "VideoBackends/Vulkan/Constants.h"
+#include "VideoBackends/Vulkan/ObjectCache.h"
+
+#include "VideoCommon/GeometryShaderGen.h"
+#include "VideoCommon/PixelShaderGen.h"
+#include "VideoCommon/RenderState.h"
+#include "VideoCommon/VertexShaderGen.h"
+
+namespace Vulkan
+{
+class CommandBufferManager;
+class VertexFormat;
+class StreamBuffer;
+
+class CommandBufferManager;
+class VertexFormat;
+class StreamBuffer;
+
+struct PipelineInfo
+{
+ // These are packed in descending order of size, to avoid any padding so that the structure
+ // can be copied/compared as a single block of memory. 64-bit pointer size is assumed.
+ const VertexFormat* vertex_format;
+ VkPipelineLayout pipeline_layout;
+ VkShaderModule vs;
+ VkShaderModule gs;
+ VkShaderModule ps;
+ VkRenderPass render_pass;
+ BlendingState blend_state;
+ RasterizationState rasterization_state;
+ DepthStencilState depth_stencil_state;
+ VkPrimitiveTopology primitive_topology;
+};
+
+struct PipelineInfoHash
+{
+ std::size_t operator()(const PipelineInfo& key) const;
+};
+
+bool operator==(const PipelineInfo& lhs, const PipelineInfo& rhs);
+bool operator!=(const PipelineInfo& lhs, const PipelineInfo& rhs);
+bool operator<(const PipelineInfo& lhs, const PipelineInfo& rhs);
+bool operator>(const PipelineInfo& lhs, const PipelineInfo& rhs);
+bool operator==(const SamplerState& lhs, const SamplerState& rhs);
+bool operator!=(const SamplerState& lhs, const SamplerState& rhs);
+bool operator>(const SamplerState& lhs, const SamplerState& rhs);
+bool operator<(const SamplerState& lhs, const SamplerState& rhs);
+
+struct ComputePipelineInfo
+{
+ VkPipelineLayout pipeline_layout;
+ VkShaderModule cs;
+};
+
+struct ComputePipelineInfoHash
+{
+ std::size_t operator()(const ComputePipelineInfo& key) const;
+};
+
+bool operator==(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs);
+bool operator!=(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs);
+bool operator<(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs);
+bool operator>(const ComputePipelineInfo& lhs, const ComputePipelineInfo& rhs);
+
+class ShaderCache
+{
+public:
+ ShaderCache();
+ ~ShaderCache();
+
+ // Get utility shader header based on current config.
+ std::string GetUtilityShaderHeader() const;
+
+ // Accesses ShaderGen shader caches
+ VkShaderModule GetVertexShaderForUid(const VertexShaderUid& uid);
+ VkShaderModule GetGeometryShaderForUid(const GeometryShaderUid& uid);
+ VkShaderModule GetPixelShaderForUid(const PixelShaderUid& uid);
+
+ // Perform at startup, create descriptor layouts, compiles all static shaders.
+ bool Initialize();
+
+ // Creates a pipeline for the specified description. The resulting pipeline, if successful
+ // is not stored anywhere, this is left up to the caller.
+ VkPipeline CreatePipeline(const PipelineInfo& info);
+
+ // Find a pipeline by the specified description, if not found, attempts to create it.
+ VkPipeline GetPipeline(const PipelineInfo& info);
+
+ // Find a pipeline by the specified description, if not found, attempts to create it. If this
+ // resulted in a pipeline being created, the second field of the return value will be false,
+ // otherwise for a cache hit it will be true.
+ std::pair<VkPipeline, bool> GetPipelineWithCacheResult(const PipelineInfo& info);
+
+ // Creates a compute pipeline, and does not track the handle.
+ VkPipeline CreateComputePipeline(const ComputePipelineInfo& info);
+
+ // Find a pipeline by the specified description, if not found, attempts to create it
+ VkPipeline GetComputePipeline(const ComputePipelineInfo& info);
+
+ // Clears our pipeline cache of all objects. This is necessary when recompiling shaders,
+ // as drivers are free to return the same pointer again, which means that we may end up using
+ // and old pipeline object if they are not cleared first. Some stutter may be experienced
+ // while our cache is rebuilt on use, but the pipeline cache object should mitigate this.
+ // NOTE: Ensure that none of these objects are in use before calling.
+ void ClearPipelineCache();
+
+ // Saves the pipeline cache to disk. Call when shutting down.
+ void SavePipelineCache();
+
+ // Recompile shared shaders, call when stereo mode changes.
+ void RecompileSharedShaders();
+
+ // Reload pipeline cache. This will destroy all pipelines.
+ void ReloadShaderAndPipelineCaches();
+
+ // Shared shader accessors
+ VkShaderModule GetScreenQuadVertexShader() const { return m_screen_quad_vertex_shader; }
+ VkShaderModule GetPassthroughVertexShader() const { return m_passthrough_vertex_shader; }
+ VkShaderModule GetScreenQuadGeometryShader() const { return m_screen_quad_geometry_shader; }
+ VkShaderModule GetPassthroughGeometryShader() const { return m_passthrough_geometry_shader; }
+private:
+ bool CreatePipelineCache();
+ bool LoadPipelineCache();
+ bool ValidatePipelineCache(const u8* data, size_t data_length);
+ void DestroyPipelineCache();
+ void LoadShaderCaches();
+ void DestroyShaderCaches();
+ bool CompileSharedShaders();
+ void DestroySharedShaders();
+
+ template <typename Uid>
+ struct ShaderModuleCache
+ {
+ std::map<Uid, VkShaderModule> shader_map;
+ LinearDiskCache<Uid, u32> disk_cache;
+ };
+ ShaderModuleCache<VertexShaderUid> m_vs_cache;
+ ShaderModuleCache<GeometryShaderUid> m_gs_cache;
+ ShaderModuleCache<PixelShaderUid> m_ps_cache;
+
+ std::unordered_map<PipelineInfo, VkPipeline, PipelineInfoHash> m_pipeline_objects;
+ std::unordered_map<ComputePipelineInfo, VkPipeline, ComputePipelineInfoHash>
+ m_compute_pipeline_objects;
+ VkPipelineCache m_pipeline_cache = VK_NULL_HANDLE;
+ std::string m_pipeline_cache_filename;
+
+ // Utility/shared shaders
+ VkShaderModule m_screen_quad_vertex_shader = VK_NULL_HANDLE;
+ VkShaderModule m_passthrough_vertex_shader = VK_NULL_HANDLE;
+ VkShaderModule m_screen_quad_geometry_shader = VK_NULL_HANDLE;
+ VkShaderModule m_passthrough_geometry_shader = VK_NULL_HANDLE;
+};
+
+extern std::unique_ptr<ShaderCache> g_shader_cache;
+
+} // namespace Vulkan
diff --git a/Source/Core/VideoBackends/Vulkan/ShaderCompiler.cpp b/Source/Core/VideoBackends/Vulkan/ShaderCompiler.cpp
index 75bf99a3ba..1846ebbbcd 100644
--- a/Source/Core/VideoBackends/Vulkan/ShaderCompiler.cpp
+++ b/Source/Core/VideoBackends/Vulkan/ShaderCompiler.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include "VideoBackends/Vulkan/ShaderCompiler.h"
+#include "VideoBackends/Vulkan/VulkanContext.h"
#include <cstddef>
#include <cstdlib>
@@ -37,6 +38,11 @@ static bool CompileShaderToSPV(SPIRVCodeVector* out_code, EShLanguage stage,
const char* stage_filename, const char* source_code,
size_t source_code_length, const char* header, size_t header_length);
+// Copy GLSL source code to a SPIRVCodeVector, for use with VK_NV_glsl_shader.
+static void CopyGLSLToSPVVector(SPIRVCodeVector* out_code, const char* stage_filename,
+ const char* source_code, size_t source_code_length,
+ const char* header, size_t header_length);
+
// Regarding the UBO bind points, we subtract one from the binding index because
// the OpenGL backend requires UBO #0 for non-block uniforms (at least on NV).
// This allows us to share the same shaders but use bind point #0 in the Vulkan
@@ -219,6 +225,42 @@ bool CompileShaderToSPV(SPIRVCodeVector* out_code, EShLanguage stage, const char
return true;
}
+void CopyGLSLToSPVVector(SPIRVCodeVector* out_code, const char* stage_filename,
+ const char* source_code, size_t source_code_length, const char* header,
+ size_t header_length)
+{
+ std::string full_source_code;
+ if (header_length > 0)
+ {
+ full_source_code.reserve(header_length + source_code_length);
+ full_source_code.append(header, header_length);
+ full_source_code.append(source_code, source_code_length);
+ }
+ else
+ {
+ full_source_code.append(source_code, source_code_length);
+ }
+
+ if (g_ActiveConfig.iLog & CONF_SAVESHADERS)
+ {
+ static int counter = 0;
+ std::string filename = StringFromFormat("%s%s_%04i.txt", File::GetUserPath(D_DUMP_IDX).c_str(),
+ stage_filename, counter++);
+
+ std::ofstream stream;
+ File::OpenFStream(stream, filename, std::ios_base::out);
+ if (stream.good())
+ stream << full_source_code << std::endl;
+ }
+
+ size_t padding = full_source_code.size() % 4;
+ if (padding != 0)
+ full_source_code.append(4 - padding, '\n');
+
+ out_code->resize(full_source_code.size() / 4);
+ std::memcpy(out_code->data(), full_source_code.c_str(), full_source_code.size());
+}
+
bool InitializeGlslang()
{
static bool glslang_initialized = false;
@@ -338,29 +380,57 @@ const TBuiltInResource* GetCompilerResourceLimits()
}
bool CompileVertexShader(SPIRVCodeVector* out_code, const char* source_code,
- size_t source_code_length, bool prepend_header)
+ size_t source_code_length)
{
+ if (g_vulkan_context->SupportsNVGLSLExtension())
+ {
+ CopyGLSLToSPVVector(out_code, "vs", source_code, source_code_length, SHADER_HEADER,
+ sizeof(SHADER_HEADER) - 1);
+ return true;
+ }
+
return CompileShaderToSPV(out_code, EShLangVertex, "vs", source_code, source_code_length,
SHADER_HEADER, sizeof(SHADER_HEADER) - 1);
}
bool CompileGeometryShader(SPIRVCodeVector* out_code, const char* source_code,
- size_t source_code_length, bool prepend_header)
+ size_t source_code_length)
{
+ if (g_vulkan_context->SupportsNVGLSLExtension())
+ {
+ CopyGLSLToSPVVector(out_code, "gs", source_code, source_code_length, SHADER_HEADER,
+ sizeof(SHADER_HEADER) - 1);
+ return true;
+ }
+
return CompileShaderToSPV(out_code, EShLangGeometry, "gs", source_code, source_code_length,
SHADER_HEADER, sizeof(SHADER_HEADER) - 1);
}
bool CompileFragmentShader(SPIRVCodeVector* out_code, const char* source_code,
- size_t source_code_length, bool prepend_header)
+ size_t source_code_length)
{
+ if (g_vulkan_context->SupportsNVGLSLExtension())
+ {
+ CopyGLSLToSPVVector(out_code, "ps", source_code, source_code_length, SHADER_HEADER,
+ sizeof(SHADER_HEADER) - 1);
+ return true;
+ }
+
return CompileShaderToSPV(out_code, EShLangFragment, "ps", source_code, source_code_length,
SHADER_HEADER, sizeof(SHADER_HEADER) - 1);
}
bool CompileComputeShader(SPIRVCodeVector* out_code, const char* source_code,
- size_t source_code_length, bool prepend_header)
+ size_t source_code_length)
{
+ if (g_vulkan_context->SupportsNVGLSLExtension())
+ {
+ CopyGLSLToSPVVector(out_code, "cs", source_code, source_code_length, COMPUTE_SHADER_HEADER,
+ sizeof(COMPUTE_SHADER_HEADER) - 1);
+ return true;
+ }
+
return CompileShaderToSPV(out_code, EShLangCompute, "cs", source_code, source_code_length,
COMPUTE_SHADER_HEADER, sizeof(COMPUTE_SHADER_HEADER) - 1);
}
diff --git a/Source/Core/VideoBackends/Vulkan/ShaderCompiler.h b/Source/Core/VideoBackends/Vulkan/ShaderCompiler.h
index 197dc1787c..84434b1ad6 100644
--- a/Source/Core/VideoBackends/Vulkan/ShaderCompiler.h
+++ b/Source/Core/VideoBackends/Vulkan/ShaderCompiler.h
@@ -19,19 +19,19 @@ using SPIRVCodeVector = std::vector<SPIRVCodeType>;
// Compile a vertex shader to SPIR-V.
bool CompileVertexShader(SPIRVCodeVector* out_code, const char* source_code,
- size_t source_code_length, bool prepend_header = true);
+ size_t source_code_length);
// Compile a geometry shader to SPIR-V.
bool CompileGeometryShader(SPIRVCodeVector* out_code, const char* source_code,
- size_t source_code_length, bool prepend_header = true);
+ size_t source_code_length);
// Compile a fragment shader to SPIR-V.
bool CompileFragmentShader(SPIRVCodeVector* out_code, const char* source_code,
- size_t source_code_length, bool prepend_header = true);
+ size_t source_code_length);
// Compile a compute shader to SPIR-V.
bool CompileComputeShader(SPIRVCodeVector* out_code, const char* source_code,
- size_t source_code_length, bool prepend_header = true);
+ size_t source_code_length);
} // namespace ShaderCompiler
} // namespace Vulkan
diff --git a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp
index 0b7833be5c..a03437a697 100644
--- a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp
+++ b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp
@@ -13,6 +13,7 @@
#include "VideoBackends/Vulkan/Constants.h"
#include "VideoBackends/Vulkan/FramebufferManager.h"
#include "VideoBackends/Vulkan/ObjectCache.h"
+#include "VideoBackends/Vulkan/ShaderCache.h"
#include "VideoBackends/Vulkan/StreamBuffer.h"
#include "VideoBackends/Vulkan/Util.h"
#include "VideoBackends/Vulkan/VertexFormat.h"
@@ -181,7 +182,7 @@ bool StateTracker::PrecachePipelineUID(const SerializedPipelineUID& uid)
pinfo.pipeline_layout = uid.ps_uid.GetUidData()->bounding_box ?
g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_BBOX) :
g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD);
- pinfo.vs = g_object_cache->GetVertexShaderForUid(uid.vs_uid);
+ pinfo.vs = g_shader_cache->GetVertexShaderForUid(uid.vs_uid);
if (pinfo.vs == VK_NULL_HANDLE)
{
WARN_LOG(VIDEO, "Failed to get vertex shader from cached UID.");
@@ -189,14 +190,14 @@ bool StateTracker::PrecachePipelineUID(const SerializedPipelineUID& uid)
}
if (g_vulkan_context->SupportsGeometryShaders() && !uid.gs_uid.GetUidData()->IsPassthrough())
{
- pinfo.gs = g_object_cache->GetGeometryShaderForUid(uid.gs_uid);
+ pinfo.gs = g_shader_cache->GetGeometryShaderForUid(uid.gs_uid);
if (pinfo.gs == VK_NULL_HANDLE)
{
WARN_LOG(VIDEO, "Failed to get geometry shader from cached UID.");
return false;
}
}
- pinfo.ps = g_object_cache->GetPixelShaderForUid(uid.ps_uid);
+ pinfo.ps = g_shader_cache->GetPixelShaderForUid(uid.ps_uid);
if (pinfo.ps == VK_NULL_HANDLE)
{
WARN_LOG(VIDEO, "Failed to get pixel shader from cached UID.");
@@ -208,7 +209,7 @@ bool StateTracker::PrecachePipelineUID(const SerializedPipelineUID& uid)
pinfo.blend_state.hex = uid.blend_state_bits;
pinfo.primitive_topology = uid.primitive_topology;
- VkPipeline pipeline = g_object_cache->GetPipeline(pinfo);
+ VkPipeline pipeline = g_shader_cache->GetPipeline(pinfo);
if (pipeline == VK_NULL_HANDLE)
{
WARN_LOG(VIDEO, "Failed to get pipeline from cached UID.");
@@ -327,7 +328,7 @@ bool StateTracker::CheckForShaderChanges(u32 gx_primitive_type)
if (vs_uid != m_vs_uid)
{
- m_pipeline_state.vs = g_object_cache->GetVertexShaderForUid(vs_uid);
+ m_pipeline_state.vs = g_shader_cache->GetVertexShaderForUid(vs_uid);
m_vs_uid = vs_uid;
changed = true;
}
@@ -340,7 +341,7 @@ bool StateTracker::CheckForShaderChanges(u32 gx_primitive_type)
if (gs_uid.GetUidData()->IsPassthrough())
m_pipeline_state.gs = VK_NULL_HANDLE;
else
- m_pipeline_state.gs = g_object_cache->GetGeometryShaderForUid(gs_uid);
+ m_pipeline_state.gs = g_shader_cache->GetGeometryShaderForUid(gs_uid);
m_gs_uid = gs_uid;
changed = true;
@@ -349,7 +350,7 @@ bool StateTracker::CheckForShaderChanges(u32 gx_primitive_type)
if (ps_uid != m_ps_uid)
{
- m_pipeline_state.ps = g_object_cache->GetPixelShaderForUid(ps_uid);
+ m_pipeline_state.ps = g_shader_cache->GetPixelShaderForUid(ps_uid);
m_ps_uid = ps_uid;
changed = true;
}
@@ -887,7 +888,7 @@ void StateTracker::EndClearRenderPass()
VkPipeline StateTracker::GetPipelineAndCacheUID(const PipelineInfo& info)
{
- auto result = g_object_cache->GetPipelineWithCacheResult(info);
+ auto result = g_shader_cache->GetPipelineWithCacheResult(info);
// Add to the UID cache if it is a new pipeline.
if (!result.second && g_ActiveConfig.bShaderCache)
diff --git a/Source/Core/VideoBackends/Vulkan/StateTracker.h b/Source/Core/VideoBackends/Vulkan/StateTracker.h
index ef09608fe8..03d7464cee 100644
--- a/Source/Core/VideoBackends/Vulkan/StateTracker.h
+++ b/Source/Core/VideoBackends/Vulkan/StateTracker.h
@@ -11,7 +11,7 @@
#include "Common/CommonTypes.h"
#include "Common/LinearDiskCache.h"
#include "VideoBackends/Vulkan/Constants.h"
-#include "VideoBackends/Vulkan/ObjectCache.h"
+#include "VideoBackends/Vulkan/ShaderCache.h"
#include "VideoCommon/GeometryShaderGen.h"
#include "VideoCommon/NativeVertexFormat.h"
#include "VideoCommon/PixelShaderGen.h"
diff --git a/Source/Core/VideoBackends/Vulkan/TextureCache.cpp b/Source/Core/VideoBackends/Vulkan/TextureCache.cpp
index 30d251d343..30817706b7 100644
--- a/Source/Core/VideoBackends/Vulkan/TextureCache.cpp
+++ b/Source/Core/VideoBackends/Vulkan/TextureCache.cpp
@@ -299,7 +299,7 @@ bool TextureCache::CompileShaders()
}
)";
- std::string header = g_object_cache->GetUtilityShaderHeader();
+ std::string header = g_shader_cache->GetUtilityShaderHeader();
std::string source;
source = header + COPY_SHADER_SOURCE;
@@ -385,8 +385,8 @@ void TextureCache::CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_copy,
UtilityShaderDraw draw(command_buffer,
g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_PUSH_CONSTANT),
- m_render_pass, g_object_cache->GetPassthroughVertexShader(),
- g_object_cache->GetPassthroughGeometryShader(),
+ m_render_pass, g_shader_cache->GetPassthroughVertexShader(),
+ g_shader_cache->GetPassthroughGeometryShader(),
is_depth_copy ? m_efb_depth_to_tex_shader : m_efb_color_to_tex_shader);
draw.SetPushConstants(colmat, (is_depth_copy ? sizeof(float) * 20 : sizeof(float) * 28));
diff --git a/Source/Core/VideoBackends/Vulkan/TextureConverter.cpp b/Source/Core/VideoBackends/Vulkan/TextureConverter.cpp
index d5dd9b7fd4..ca045ec0ce 100644
--- a/Source/Core/VideoBackends/Vulkan/TextureConverter.cpp
+++ b/Source/Core/VideoBackends/Vulkan/TextureConverter.cpp
@@ -200,7 +200,7 @@ void TextureConverter::ConvertTexture(TextureCacheBase::TCacheEntry* dst_entry,
// Bind and draw to the destination.
UtilityShaderDraw draw(command_buffer,
g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_TEXTURE_CONVERSION),
- render_pass, g_object_cache->GetScreenQuadVertexShader(), VK_NULL_HANDLE,
+ render_pass, g_shader_cache->GetScreenQuadVertexShader(), VK_NULL_HANDLE,
m_palette_conversion_shaders[palette_format]);
VkRect2D region = {{0, 0}, {dst_entry->GetWidth(), dst_entry->GetHeight()}};
@@ -240,7 +240,7 @@ void TextureConverter::EncodeTextureToMemory(VkImageView src_texture, u8* dest_p
UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(),
g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_PUSH_CONSTANT),
- m_encoding_render_pass, g_object_cache->GetScreenQuadVertexShader(),
+ m_encoding_render_pass, g_shader_cache->GetScreenQuadVertexShader(),
VK_NULL_HANDLE, shader);
// Uniform - int4 of left,top,native_width,scale
@@ -299,7 +299,7 @@ void TextureConverter::EncodeTextureToMemoryYUYV(void* dst_ptr, u32 dst_width, u
u32 output_width = dst_width / 2;
UtilityShaderDraw draw(command_buffer,
g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD),
- m_encoding_render_pass, g_object_cache->GetPassthroughVertexShader(),
+ m_encoding_render_pass, g_shader_cache->GetPassthroughVertexShader(),
VK_NULL_HANDLE, m_rgb_to_yuyv_shader);
VkRect2D region = {{0, 0}, {output_width, dst_height}};
draw.BeginRenderPass(m_encoding_render_framebuffer, region);
@@ -376,7 +376,7 @@ void TextureConverter::DecodeYUYVTextureFromMemory(VKTexture* dst_texture, const
// Convert from the YUYV data now in the intermediate texture to RGBA in the destination.
UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(),
g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_TEXTURE_CONVERSION),
- m_encoding_render_pass, g_object_cache->GetScreenQuadVertexShader(),
+ m_encoding_render_pass, g_shader_cache->GetScreenQuadVertexShader(),
VK_NULL_HANDLE, m_yuyv_to_rgb_shader);
VkRect2D region = {{0, 0}, {src_width, src_height}};
draw.BeginRenderPass(dst_texture->GetFramebuffer(), region);
@@ -408,7 +408,7 @@ bool TextureConverter::SupportsTextureDecoding(TextureFormat format, TlutFormat
std::string shader_source =
TextureConversionShader::GenerateDecodingShader(format, palette_format, APIType::Vulkan);
- pipeline.compute_shader = Util::CompileAndCreateComputeShader(shader_source, true);
+ pipeline.compute_shader = Util::CompileAndCreateComputeShader(shader_source);
if (pipeline.compute_shader == VK_NULL_HANDLE)
{
m_decoding_pipelines.emplace(key, pipeline);
@@ -782,8 +782,19 @@ bool TextureConverter::CreateDecodingTexture()
m_decoding_texture = Texture2D::Create(
DECODING_TEXTURE_WIDTH, DECODING_TEXTURE_HEIGHT, 1, 1, VK_FORMAT_R8G8B8A8_UNORM,
VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D_ARRAY, VK_IMAGE_TILING_OPTIMAL,
- VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
- return static_cast<bool>(m_decoding_texture);
+ VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
+ if (!m_decoding_texture)
+ return false;
+
+ VkClearColorValue clear_value = {{0.0f, 0.0f, 0.0f, 1.0f}};
+ VkImageSubresourceRange clear_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
+ m_decoding_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+ vkCmdClearColorImage(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
+ m_decoding_texture->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ &clear_value, 1, &clear_range);
+ return true;
}
bool TextureConverter::CompileYUYVConversionShaders()
@@ -838,7 +849,7 @@ bool TextureConverter::CompileYUYVConversionShaders()
}
)";
- std::string header = g_object_cache->GetUtilityShaderHeader();
+ std::string header = g_shader_cache->GetUtilityShaderHeader();
std::string source = header + RGB_TO_YUYV_SHADER_SOURCE;
m_rgb_to_yuyv_shader = Util::CompileAndCreateFragmentShader(source);
source = header + YUYV_TO_RGB_SHADER_SOURCE;
diff --git a/Source/Core/VideoBackends/Vulkan/Util.cpp b/Source/Core/VideoBackends/Vulkan/Util.cpp
index dd676d0b6c..f1f4f42b1b 100644
--- a/Source/Core/VideoBackends/Vulkan/Util.cpp
+++ b/Source/Core/VideoBackends/Vulkan/Util.cpp
@@ -12,6 +12,7 @@
#include "VideoBackends/Vulkan/CommandBufferManager.h"
#include "VideoBackends/Vulkan/ObjectCache.h"
+#include "VideoBackends/Vulkan/ShaderCache.h"
#include "VideoBackends/Vulkan/ShaderCompiler.h"
#include "VideoBackends/Vulkan/StateTracker.h"
#include "VideoBackends/Vulkan/StreamBuffer.h"
@@ -275,50 +276,38 @@ VkShaderModule CreateShaderModule(const u32* spv, size_t spv_word_count)
return module;
}
-VkShaderModule CompileAndCreateVertexShader(const std::string& source_code, bool prepend_header)
+VkShaderModule CompileAndCreateVertexShader(const std::string& source_code)
{
ShaderCompiler::SPIRVCodeVector code;
- if (!ShaderCompiler::CompileVertexShader(&code, source_code.c_str(), source_code.length(),
- prepend_header))
- {
+ if (!ShaderCompiler::CompileVertexShader(&code, source_code.c_str(), source_code.length()))
return VK_NULL_HANDLE;
- }
return CreateShaderModule(code.data(), code.size());
}
-VkShaderModule CompileAndCreateGeometryShader(const std::string& source_code, bool prepend_header)
+VkShaderModule CompileAndCreateGeometryShader(const std::string& source_code)
{
ShaderCompiler::SPIRVCodeVector code;
- if (!ShaderCompiler::CompileGeometryShader(&code, source_code.c_str(), source_code.length(),
- prepend_header))
- {
+ if (!ShaderCompiler::CompileGeometryShader(&code, source_code.c_str(), source_code.length()))
return VK_NULL_HANDLE;
- }
return CreateShaderModule(code.data(), code.size());
}
-VkShaderModule CompileAndCreateFragmentShader(const std::string& source_code, bool prepend_header)
+VkShaderModule CompileAndCreateFragmentShader(const std::string& source_code)
{
ShaderCompiler::SPIRVCodeVector code;
- if (!ShaderCompiler::CompileFragmentShader(&code, source_code.c_str(), source_code.length(),
- prepend_header))
- {
+ if (!ShaderCompiler::CompileFragmentShader(&code, source_code.c_str(), source_code.length()))
return VK_NULL_HANDLE;
- }
return CreateShaderModule(code.data(), code.size());
}
-VkShaderModule CompileAndCreateComputeShader(const std::string& source_code, bool prepend_header)
+VkShaderModule CompileAndCreateComputeShader(const std::string& source_code)
{
ShaderCompiler::SPIRVCodeVector code;
- if (!ShaderCompiler::CompileComputeShader(&code, source_code.c_str(), source_code.length(),
- prepend_header))
- {
+ if (!ShaderCompiler::CompileComputeShader(&code, source_code.c_str(), source_code.length()))
return VK_NULL_HANDLE;
- }
return CreateShaderModule(code.data(), code.size());
}
@@ -732,7 +721,7 @@ void UtilityShaderDraw::BindDescriptors()
bool UtilityShaderDraw::BindPipeline()
{
- VkPipeline pipeline = g_object_cache->GetPipeline(m_pipeline_info);
+ VkPipeline pipeline = g_shader_cache->GetPipeline(m_pipeline_info);
if (pipeline == VK_NULL_HANDLE)
{
PanicAlert("Failed to get pipeline for backend shader draw");
@@ -885,7 +874,7 @@ void ComputeShaderDispatcher::BindDescriptors()
bool ComputeShaderDispatcher::BindPipeline()
{
- VkPipeline pipeline = g_object_cache->GetComputePipeline(m_pipeline_info);
+ VkPipeline pipeline = g_shader_cache->GetComputePipeline(m_pipeline_info);
if (pipeline == VK_NULL_HANDLE)
{
PanicAlert("Failed to get pipeline for backend compute dispatch");
diff --git a/Source/Core/VideoBackends/Vulkan/Util.h b/Source/Core/VideoBackends/Vulkan/Util.h
index 79b070cbb8..6847409e9b 100644
--- a/Source/Core/VideoBackends/Vulkan/Util.h
+++ b/Source/Core/VideoBackends/Vulkan/Util.h
@@ -10,13 +10,13 @@
#include "Common/CommonTypes.h"
#include "VideoBackends/Vulkan/Constants.h"
#include "VideoBackends/Vulkan/ObjectCache.h"
+#include "VideoBackends/Vulkan/ShaderCache.h"
#include "VideoCommon/RenderState.h"
#include "VideoCommon/TextureConfig.h"
namespace Vulkan
{
class CommandBufferManager;
-class ObjectCache;
class StateTracker;
namespace Util
@@ -61,20 +61,16 @@ void ExecuteCurrentCommandsAndRestoreState(bool execute_off_thread,
VkShaderModule CreateShaderModule(const u32* spv, size_t spv_word_count);
// Compile a vertex shader and create a shader module, discarding the intermediate SPIR-V.
-VkShaderModule CompileAndCreateVertexShader(const std::string& source_code,
- bool prepend_header = true);
+VkShaderModule CompileAndCreateVertexShader(const std::string& source_code);
// Compile a geometry shader and create a shader module, discarding the intermediate SPIR-V.
-VkShaderModule CompileAndCreateGeometryShader(const std::string& source_code,
- bool prepend_header = true);
+VkShaderModule CompileAndCreateGeometryShader(const std::string& source_code);
// Compile a fragment shader and create a shader module, discarding the intermediate SPIR-V.
-VkShaderModule CompileAndCreateFragmentShader(const std::string& source_code,
- bool prepend_header = true);
+VkShaderModule CompileAndCreateFragmentShader(const std::string& source_code);
// Compile a compute shader and create a shader module, discarding the intermediate SPIR-V.
-VkShaderModule CompileAndCreateComputeShader(const std::string& source_code,
- bool prepend_header = true);
+VkShaderModule CompileAndCreateComputeShader(const std::string& source_code);
}
// Utility shader vertex format
diff --git a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp
index e937f06f33..36292751b0 100644
--- a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp
+++ b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp
@@ -225,8 +225,8 @@ void VKTexture::ScaleTextureRectangle(const MathUtil::Rectangle<int>& dst_rect,
UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(),
g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD),
TextureCache::GetInstance()->GetTextureCopyRenderPass(),
- g_object_cache->GetPassthroughVertexShader(),
- g_object_cache->GetPassthroughGeometryShader(),
+ g_shader_cache->GetPassthroughVertexShader(),
+ g_shader_cache->GetPassthroughGeometryShader(),
TextureCache::GetInstance()->GetCopyShader());
VkRect2D region = {
diff --git a/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj b/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj
index f6ec82bb66..3492f7b0f2 100644
--- a/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj
+++ b/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj
@@ -41,6 +41,7 @@
<ClCompile Include="FramebufferManager.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="PostProcessing.cpp" />
+ <ClCompile Include="ShaderCache.cpp" />
<ClCompile Include="TextureConverter.cpp" />
<ClCompile Include="PerfQuery.cpp" />
<ClCompile Include="RasterFont.cpp" />
@@ -67,6 +68,7 @@
<ClInclude Include="FramebufferManager.h" />
<ClInclude Include="Constants.h" />
<ClInclude Include="PostProcessing.h" />
+ <ClInclude Include="ShaderCache.h" />
<ClInclude Include="TextureConverter.h" />
<ClInclude Include="RasterFont.h" />
<ClInclude Include="StagingBuffer.h" />
diff --git a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp
index 8978cd2df6..8ab3245455 100644
--- a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp
+++ b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp
@@ -397,7 +397,8 @@ bool VulkanContext::SelectDeviceExtensions(ExtensionList* extension_list, bool e
for (const auto& extension_properties : available_extension_list)
INFO_LOG(VIDEO, "Available extension: %s", extension_properties.extensionName);
- auto CheckForExtension = [&](const char* name, bool required) -> bool {
+ auto CheckForExtension = [&](const char* name, bool required,
+ bool* has_extension = nullptr) -> bool {
if (std::find_if(available_extension_list.begin(), available_extension_list.end(),
[&](const VkExtensionProperties& properties) {
return !strcmp(name, properties.extensionName);
@@ -405,9 +406,14 @@ bool VulkanContext::SelectDeviceExtensions(ExtensionList* extension_list, bool e
{
INFO_LOG(VIDEO, "Enabling extension: %s", name);
extension_list->push_back(name);
+ if (has_extension)
+ *has_extension = true;
return true;
}
+ if (has_extension)
+ *has_extension = false;
+
if (required)
{
ERROR_LOG(VIDEO, "Vulkan: Missing required extension %s.", name);
@@ -420,6 +426,7 @@ bool VulkanContext::SelectDeviceExtensions(ExtensionList* extension_list, bool e
if (enable_surface && !CheckForExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME, true))
return false;
+ CheckForExtension(VK_NV_GLSL_SHADER_EXTENSION_NAME, false, &m_supports_nv_glsl_extension);
return true;
}
diff --git a/Source/Core/VideoBackends/Vulkan/VulkanContext.h b/Source/Core/VideoBackends/Vulkan/VulkanContext.h
index 538573fc0a..17a4a25d2c 100644
--- a/Source/Core/VideoBackends/Vulkan/VulkanContext.h
+++ b/Source/Core/VideoBackends/Vulkan/VulkanContext.h
@@ -81,6 +81,7 @@ public:
{
return m_device_features.occlusionQueryPrecise == VK_TRUE;
}
+ bool SupportsNVGLSLExtension() const { return m_supports_nv_glsl_extension; }
// Helpers for getting constants
VkDeviceSize GetUniformBufferAlignment() const
{
@@ -125,6 +126,8 @@ private:
VkPhysicalDeviceFeatures m_device_features = {};
VkPhysicalDeviceProperties m_device_properties = {};
VkPhysicalDeviceMemoryProperties m_device_memory_properties = {};
+
+ bool m_supports_nv_glsl_extension = false;
};
extern std::unique_ptr<VulkanContext> g_vulkan_context;
diff --git a/Source/Core/VideoBackends/Vulkan/main.cpp b/Source/Core/VideoBackends/Vulkan/main.cpp
index 2fdcae7e98..4354a7a58b 100644
--- a/Source/Core/VideoBackends/Vulkan/main.cpp
+++ b/Source/Core/VideoBackends/Vulkan/main.cpp
@@ -215,19 +215,22 @@ bool VideoBackend::Initialize(void* window_handle)
// Create main wrapper instances.
g_object_cache = std::make_unique<ObjectCache>();
+ g_shader_cache = std::make_unique<ShaderCache>();
g_framebuffer_manager = std::make_unique<FramebufferManager>();
g_renderer = std::make_unique<Renderer>(std::move(swap_chain));
// Invoke init methods on main wrapper classes.
// These have to be done before the others because the destructors
// for the remaining classes may call methods on these.
- if (!g_object_cache->Initialize() || !FramebufferManager::GetInstance()->Initialize() ||
- !StateTracker::CreateInstance() || !Renderer::GetInstance()->Initialize())
+ if (!g_object_cache->Initialize() || !g_shader_cache->Initialize() ||
+ !FramebufferManager::GetInstance()->Initialize() || !StateTracker::CreateInstance() ||
+ !Renderer::GetInstance()->Initialize())
{
PanicAlert("Failed to initialize Vulkan classes.");
g_renderer.reset();
StateTracker::DestroyInstance();
g_framebuffer_manager.reset();
+ g_shader_cache.reset();
g_object_cache.reset();
g_command_buffer_mgr.reset();
g_vulkan_context.reset();
@@ -250,6 +253,7 @@ bool VideoBackend::Initialize(void* window_handle)
g_renderer.reset();
StateTracker::DestroyInstance();
g_framebuffer_manager.reset();
+ g_shader_cache.reset();
g_object_cache.reset();
g_command_buffer_mgr.reset();
g_vulkan_context.reset();
@@ -276,6 +280,7 @@ void VideoBackend::Shutdown()
{
g_command_buffer_mgr->WaitForGPUIdle();
+ g_shader_cache.reset();
g_object_cache.reset();
g_command_buffer_mgr.reset();
g_vulkan_context.reset();
@@ -291,7 +296,7 @@ void VideoBackend::Video_Cleanup()
// Save all cached pipelines out to disk for next time.
if (g_ActiveConfig.bShaderCache)
- g_object_cache->SavePipelineCache();
+ g_shader_cache->SavePipelineCache();
g_perf_query.reset();
g_texture_cache.reset();
diff --git a/Source/Core/VideoCommon/FPSCounter.cpp b/Source/Core/VideoCommon/FPSCounter.cpp
index 2d17d00a41..86b3ab5c85 100644
--- a/Source/Core/VideoCommon/FPSCounter.cpp
+++ b/Source/Core/VideoCommon/FPSCounter.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <fstream>
+#include <iomanip>
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
@@ -10,12 +11,11 @@
#include "VideoCommon/FPSCounter.h"
#include "VideoCommon/VideoConfig.h"
-static constexpr u64 FPS_REFRESH_INTERVAL = 1000;
+static constexpr u64 FPS_REFRESH_INTERVAL = 250000;
FPSCounter::FPSCounter()
{
- m_update_time.Update();
- m_render_time.Update();
+ m_last_time = Common::Timer::GetTimeUs();
}
void FPSCounter::LogRenderTimeToFile(u64 val)
@@ -23,24 +23,24 @@ void FPSCounter::LogRenderTimeToFile(u64 val)
if (!m_bench_file.is_open())
m_bench_file.open(File::GetUserPath(D_LOGS_IDX) + "render_time.txt");
- m_bench_file << val << std::endl;
+ m_bench_file << std::fixed << std::setprecision(8) << (val / 1000.0) << std::endl;
}
void FPSCounter::Update()
{
- if (m_update_time.GetTimeDifference() >= FPS_REFRESH_INTERVAL)
- {
- m_update_time.Update();
- m_fps = m_counter - m_fps_last_counter;
- m_fps_last_counter = m_counter;
- m_bench_file.flush();
- }
-
+ u64 time = Common::Timer::GetTimeUs();
+ u64 diff = time - m_last_time;
if (g_ActiveConfig.bLogRenderTimeToFile)
+ LogRenderTimeToFile(diff);
+
+ m_frame_counter++;
+ m_time_since_update += diff;
+ m_last_time = time;
+
+ if (m_time_since_update >= FPS_REFRESH_INTERVAL)
{
- LogRenderTimeToFile(m_render_time.GetTimeDifference());
- m_render_time.Update();
+ m_fps = m_frame_counter / (m_time_since_update / 1000000.0);
+ m_frame_counter = 0;
+ m_time_since_update = 0;
}
-
- m_counter++;
}
diff --git a/Source/Core/VideoCommon/FPSCounter.h b/Source/Core/VideoCommon/FPSCounter.h
index 860734d25b..112256e414 100644
--- a/Source/Core/VideoCommon/FPSCounter.h
+++ b/Source/Core/VideoCommon/FPSCounter.h
@@ -6,7 +6,7 @@
#include <fstream>
-#include "Common/Timer.h"
+#include "Common/CommonTypes.h"
class FPSCounter
{
@@ -17,14 +17,12 @@ public:
// Called when a frame is rendered (updated every second).
void Update();
- unsigned int GetFPS() const { return m_fps; }
+ float GetFPS() const { return m_fps; }
private:
- unsigned int m_fps = 0;
- unsigned int m_counter = 0;
- unsigned int m_fps_last_counter = 0;
- Common::Timer m_update_time;
-
- Common::Timer m_render_time;
+ u64 m_last_time = 0;
+ u64 m_time_since_update = 0;
+ u32 m_frame_counter = 0;
+ float m_fps = 0;
std::ofstream m_bench_file;
void LogRenderTimeToFile(u64 val);
diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp
index 45e2006cb6..9f425e4cd2 100644
--- a/Source/Core/VideoCommon/RenderBase.cpp
+++ b/Source/Core/VideoCommon/RenderBase.cpp
@@ -337,7 +337,7 @@ void Renderer::DrawDebugText()
if (g_ActiveConfig.bShowFPS || SConfig::GetInstance().m_ShowFrameCount)
{
if (g_ActiveConfig.bShowFPS)
- final_cyan += StringFromFormat("FPS: %u", m_fps_counter.GetFPS());
+ final_cyan += StringFromFormat("FPS: %.2f", m_fps_counter.GetFPS());
if (g_ActiveConfig.bShowFPS && SConfig::GetInstance().m_ShowFrameCount)
final_cyan += " - ";
diff --git a/Source/UnitTests/StubHost.cpp b/Source/UnitTests/StubHost.cpp
index 6348b1c776..860992bffd 100644
--- a/Source/UnitTests/StubHost.cpp
+++ b/Source/UnitTests/StubHost.cpp
@@ -57,6 +57,9 @@ void Host_ShowVideoConfig(void*, const std::string&)
void Host_YieldToUI()
{
}
+void Host_UpdateProgressDialog(const char* caption, int position, int total)
+{
+}
std::unique_ptr<cInterfaceBase> HostGL_CreateGLInterface()
{
return nullptr;