Compare commits
	
		
			40 Commits
		
	
	
		
			i-hate-cli
			...
			vulkan-but
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | dcc51d5526 | ||
|  | 164d85709c | ||
|  | eb6ef052d5 | ||
|  | 868dc0c11f | ||
|  | f17f127f46 | ||
|  | 5c3f6e3b72 | ||
|  | da891af113 | ||
|  | ba89673d77 | ||
|  | cc9768963a | ||
|  | 37f5d66c10 | ||
|  | 6bfe1daac7 | ||
|  | bcbce50120 | ||
|  | a608e33593 | ||
|  | ff10decc2c | ||
|  | 78050813c4 | ||
|  | b758dd214a | ||
|  | 8267da3874 | ||
|  | 9e7ae03b6c | ||
|  | 631c777983 | ||
|  | 622c20761c | ||
|  | d22d556d30 | ||
|  | c281c1a5cc | ||
|  | fd38e33fb2 | ||
|  | 66c5f59627 | ||
|  | faf6b36f3b | ||
|  | dd71859818 | ||
|  | fe724600ab | ||
|  | a12b01105d | ||
|  | 8c05a52a65 | ||
|  | e5ba2abd1c | ||
|  | 8421be7ebf | ||
|  | ba9f1f8ae9 | ||
|  | 6c391971c6 | ||
|  | 85dd604a7e | ||
|  | edd8de29ae | ||
|  | 89226eea2a | ||
|  | 59549a2eb6 | ||
|  | a8d590ae80 | ||
|  | 6b51afaf1f | ||
|  | a67bfe544d | 
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -82,3 +82,6 @@ | ||||
| [submodule "library-headers"] | ||||
|     path = externals/library-headers/library-headers | ||||
|     url = https://github.com/citra-emu/ext-library-headers.git | ||||
| [submodule "libadrenotools"] | ||||
|     path = externals/libadrenotools | ||||
|     url = https://github.com/bylaws/libadrenotools | ||||
|   | ||||
							
								
								
									
										8
									
								
								externals/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								externals/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							| @@ -242,3 +242,11 @@ target_include_directories(vma SYSTEM INTERFACE ./vma/include) | ||||
| # vulkan-headers | ||||
| add_library(vulkan-headers INTERFACE) | ||||
| target_include_directories(vulkan-headers SYSTEM INTERFACE ./vulkan-headers/include) | ||||
| if (APPLE) | ||||
|     target_include_directories(vulkan-headers SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK) | ||||
| endif() | ||||
|  | ||||
| # adrenotools | ||||
| if (ANDROID) | ||||
|     add_subdirectory(libadrenotools) | ||||
| endif() | ||||
|   | ||||
							
								
								
									
										1
									
								
								externals/libadrenotools
									
									
									
									
										vendored
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								externals/libadrenotools
									
									
									
									
										vendored
									
									
										Submodule
									
								
							 Submodule externals/libadrenotools added at deec5f75ee
									
								
							
							
								
								
									
										1071
									
								
								externals/moltenvk/mvk_config.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1071
									
								
								externals/moltenvk/mvk_config.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										2
									
								
								externals/vulkan-headers
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								externals/vulkan-headers
									
									
									
									
										vendored
									
									
								
							 Submodule externals/vulkan-headers updated: bae9700cd9...85c2334e92
									
								
							| @@ -359,6 +359,8 @@ public final class SettingsFragmentPresenter { | ||||
|  | ||||
|         SettingSection rendererSection = mSettings.getSection(Settings.SECTION_RENDERER); | ||||
|         Setting graphicsApi = rendererSection.getSetting(SettingsFile.KEY_GRAPHICS_API); | ||||
|         Setting spirvShaderGen = rendererSection.getSetting(SettingsFile.KEY_SPIRV_SHADER_GEN); | ||||
|         Setting asyncShaders = rendererSection.getSetting(SettingsFile.KEY_ASYNC_SHADERS); | ||||
|         Setting resolutionFactor = rendererSection.getSetting(SettingsFile.KEY_RESOLUTION_FACTOR); | ||||
|         Setting filterMode = rendererSection.getSetting(SettingsFile.KEY_FILTER_MODE); | ||||
|         Setting shadersAccurateMul = rendererSection.getSetting(SettingsFile.KEY_SHADERS_ACCURATE_MUL); | ||||
| @@ -377,6 +379,8 @@ public final class SettingsFragmentPresenter { | ||||
|  | ||||
|         sl.add(new HeaderSetting(null, null, R.string.renderer, 0)); | ||||
|         sl.add(new SingleChoiceSetting(SettingsFile.KEY_GRAPHICS_API, Settings.SECTION_RENDERER, R.string.graphics_api, 0, R.array.graphicsApiNames, R.array.graphicsApiValues, 0, graphicsApi)); | ||||
|         sl.add(new CheckBoxSetting(SettingsFile.KEY_SPIRV_SHADER_GEN, Settings.SECTION_RENDERER, R.string.spirv_shader_gen, R.string.spirv_shader_gen_description, true, spirvShaderGen)); | ||||
|         sl.add(new CheckBoxSetting(SettingsFile.KEY_ASYNC_SHADERS, Settings.SECTION_RENDERER, R.string.async_shaders, R.string.async_shaders_description, false, asyncShaders)); | ||||
|         sl.add(new SliderSetting(SettingsFile.KEY_RESOLUTION_FACTOR, Settings.SECTION_RENDERER, R.string.internal_resolution, R.string.internal_resolution_description, 1, 4, "x", 1, resolutionFactor)); | ||||
|         sl.add(new CheckBoxSetting(SettingsFile.KEY_FILTER_MODE, Settings.SECTION_RENDERER, R.string.linear_filtering, R.string.linear_filtering_description, true, filterMode)); | ||||
|         sl.add(new CheckBoxSetting(SettingsFile.KEY_SHADERS_ACCURATE_MUL, Settings.SECTION_RENDERER, R.string.shaders_accurate_mul, R.string.shaders_accurate_mul_description, false, shadersAccurateMul)); | ||||
| @@ -424,6 +428,6 @@ public final class SettingsFragmentPresenter { | ||||
|         sl.add(new CheckBoxSetting(SettingsFile.KEY_CPU_JIT, Settings.SECTION_CORE, R.string.cpu_jit, R.string.cpu_jit_description, true, useCpuJit, true, mView)); | ||||
|         sl.add(new CheckBoxSetting(SettingsFile.KEY_HW_SHADER, Settings.SECTION_RENDERER, R.string.hw_shaders, R.string.hw_shaders_description, true, hardwareShader, true, mView)); | ||||
|         sl.add(new CheckBoxSetting(SettingsFile.KEY_USE_VSYNC, Settings.SECTION_RENDERER, R.string.vsync, R.string.vsync_description, true, vsyncEnable)); | ||||
|         sl.add(new CheckBoxSetting(SettingsFile.KEY_RENDERER_DEBUG, Settings.SECTION_RENDERER, R.string.renderer_debug, R.string.renderer_debug_description, false, rendererDebug)); | ||||
|         sl.add(new CheckBoxSetting(SettingsFile.KEY_RENDERER_DEBUG, Settings.SECTION_DEBUG, R.string.renderer_debug, R.string.renderer_debug_description, false, rendererDebug)); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -45,6 +45,8 @@ public final class SettingsFile { | ||||
|     public static final String KEY_PREMIUM = "premium"; | ||||
|  | ||||
|     public static final String KEY_GRAPHICS_API = "graphics_api"; | ||||
|     public static final String KEY_SPIRV_SHADER_GEN = "spirv_shader_gen"; | ||||
|     public static final String KEY_ASYNC_SHADERS = "async_shader_compilation"; | ||||
|     public static final String KEY_RENDERER_DEBUG = "renderer_debug"; | ||||
|     public static final String KEY_HW_SHADER = "use_hw_shader"; | ||||
|     public static final String KEY_SHADERS_ACCURATE_MUL = "shaders_accurate_mul"; | ||||
|   | ||||
| @@ -19,6 +19,10 @@ add_library(citra-android SHARED | ||||
|     default_ini.h | ||||
|     emu_window/emu_window.cpp | ||||
|     emu_window/emu_window.h | ||||
|     emu_window/emu_window_gl.cpp | ||||
|     emu_window/emu_window_gl.h | ||||
|     emu_window/emu_window_vk.cpp | ||||
|     emu_window/emu_window_vk.h | ||||
|     game_info.cpp | ||||
|     game_settings.cpp | ||||
|     game_settings.h | ||||
| @@ -30,7 +34,7 @@ add_library(citra-android SHARED | ||||
|     ndk_motion.h | ||||
| ) | ||||
|  | ||||
| target_link_libraries(citra-android PRIVATE audio_core citra_common citra_core input_common network) | ||||
| target_link_libraries(citra-android PRIVATE audio_core citra_common citra_core input_common network adrenotools) | ||||
| target_link_libraries(citra-android PRIVATE android camera2ndk EGL glad inih jnigraphics log mediandk yuv) | ||||
|  | ||||
| set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} citra-android) | ||||
|   | ||||
| @@ -147,6 +147,9 @@ void Config::ReadValues() { | ||||
|     Settings::values.shaders_accurate_mul = | ||||
|         sdl2_config->GetBoolean("Renderer", "shaders_accurate_mul", false); | ||||
|     ReadSetting("Renderer", Settings::values.graphics_api); | ||||
|     ReadSetting("Renderer", Settings::values.async_presentation); | ||||
|     ReadSetting("Renderer", Settings::values.async_shader_compilation); | ||||
|     ReadSetting("Renderer", Settings::values.spirv_shader_gen); | ||||
|     ReadSetting("Renderer", Settings::values.use_hw_shader); | ||||
|     ReadSetting("Renderer", Settings::values.use_shader_jit); | ||||
|     ReadSetting("Renderer", Settings::values.resolution_factor); | ||||
|   | ||||
| @@ -99,9 +99,17 @@ cpu_clock_percentage = | ||||
|  | ||||
| [Renderer] | ||||
| # Whether to render using OpenGL | ||||
| # 1: OpenGLES (default) | ||||
| # 1: OpenGL ES (default), 2: Vulkan | ||||
| graphics_api = | ||||
|  | ||||
| # Whether to compile shaders on multiple worker threads (Vulkan only) | ||||
| # 0: Off, 1: On (default) | ||||
| async_shader_compilation = | ||||
|  | ||||
| # Whether to emit PICA fragment shader using SPIRV or GLSL (Vulkan only) | ||||
| # 0: GLSL, 1: SPIR-V (default) | ||||
| spirv_shader_gen = | ||||
|  | ||||
| # Whether to use hardware shaders to emulate 3DS shaders | ||||
| # 0: Software, 1 (default): Hardware | ||||
| use_hw_shader = | ||||
|   | ||||
| @@ -6,10 +6,7 @@ | ||||
| #include <array> | ||||
| #include <cstdlib> | ||||
| #include <string> | ||||
|  | ||||
| #include <android/native_window_jni.h> | ||||
| #include <glad/glad.h> | ||||
|  | ||||
| #include "common/logging/log.h" | ||||
| #include "common/settings.h" | ||||
| #include "input_common/main.h" | ||||
| @@ -20,52 +17,6 @@ | ||||
| #include "video_core/renderer_base.h" | ||||
| #include "video_core/video_core.h" | ||||
|  | ||||
| static constexpr std::array<EGLint, 15> egl_attribs{EGL_SURFACE_TYPE, | ||||
|                                                     EGL_WINDOW_BIT, | ||||
|                                                     EGL_RENDERABLE_TYPE, | ||||
|                                                     EGL_OPENGL_ES3_BIT_KHR, | ||||
|                                                     EGL_BLUE_SIZE, | ||||
|                                                     8, | ||||
|                                                     EGL_GREEN_SIZE, | ||||
|                                                     8, | ||||
|                                                     EGL_RED_SIZE, | ||||
|                                                     8, | ||||
|                                                     EGL_DEPTH_SIZE, | ||||
|                                                     0, | ||||
|                                                     EGL_STENCIL_SIZE, | ||||
|                                                     0, | ||||
|                                                     EGL_NONE}; | ||||
| static constexpr std::array<EGLint, 5> egl_empty_attribs{EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE}; | ||||
| static constexpr std::array<EGLint, 4> egl_context_attribs{EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; | ||||
|  | ||||
| SharedContext_Android::SharedContext_Android(EGLDisplay egl_display, EGLConfig egl_config, | ||||
|                                              EGLContext egl_share_context) | ||||
|     : egl_display{egl_display}, egl_surface{eglCreatePbufferSurface(egl_display, egl_config, | ||||
|                                                                     egl_empty_attribs.data())}, | ||||
|       egl_context{eglCreateContext(egl_display, egl_config, egl_share_context, | ||||
|                                    egl_context_attribs.data())} { | ||||
|     ASSERT_MSG(egl_surface, "eglCreatePbufferSurface() failed!"); | ||||
|     ASSERT_MSG(egl_context, "eglCreateContext() failed!"); | ||||
| } | ||||
|  | ||||
| SharedContext_Android::~SharedContext_Android() { | ||||
|     if (!eglDestroySurface(egl_display, egl_surface)) { | ||||
|         LOG_CRITICAL(Frontend, "eglDestroySurface() failed"); | ||||
|     } | ||||
|  | ||||
|     if (!eglDestroyContext(egl_display, egl_context)) { | ||||
|         LOG_CRITICAL(Frontend, "eglDestroySurface() failed"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void SharedContext_Android::MakeCurrent() { | ||||
|     eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context); | ||||
| } | ||||
|  | ||||
| void SharedContext_Android::DoneCurrent() { | ||||
|     eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | ||||
| } | ||||
|  | ||||
| static bool IsPortraitMode() { | ||||
|     return JNI_FALSE != IDCache::GetEnvForThread()->CallStaticBooleanMethod( | ||||
|                             IDCache::GetNativeLibraryClass(), IDCache::GetIsPortraitMode()); | ||||
| @@ -79,7 +30,12 @@ static void UpdateLandscapeScreenLayout() { | ||||
|  | ||||
| void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { | ||||
|     render_window = surface; | ||||
|  | ||||
|     window_info.type = Frontend::WindowSystemType::Android; | ||||
|     window_info.render_surface = surface; | ||||
|  | ||||
|     StopPresenting(); | ||||
|     OnFramebufferSizeChanged(); | ||||
| } | ||||
|  | ||||
| bool EmuWindow_Android::OnTouchEvent(int x, int y, bool pressed) { | ||||
| @@ -98,6 +54,7 @@ void EmuWindow_Android::OnTouchMoved(int x, int y) { | ||||
| void EmuWindow_Android::OnFramebufferSizeChanged() { | ||||
|     UpdateLandscapeScreenLayout(); | ||||
|     const bool is_portrait_mode{IsPortraitMode()}; | ||||
|  | ||||
|     const int bigger{window_width > window_height ? window_width : window_height}; | ||||
|     const int smaller{window_width < window_height ? window_width : window_height}; | ||||
|     if (is_portrait_mode) { | ||||
| @@ -107,7 +64,7 @@ void EmuWindow_Android::OnFramebufferSizeChanged() { | ||||
|     } | ||||
| } | ||||
|  | ||||
| EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface) { | ||||
| EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface) : host_window{surface} { | ||||
|     LOG_DEBUG(Frontend, "Initializing EmuWindow_Android"); | ||||
|  | ||||
|     if (!surface) { | ||||
| @@ -115,108 +72,10 @@ EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     window_width = ANativeWindow_getWidth(surface); | ||||
|     window_height = ANativeWindow_getHeight(surface); | ||||
|  | ||||
|     Network::Init(); | ||||
|  | ||||
|     host_window = surface; | ||||
|  | ||||
|     if (egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); egl_display == EGL_NO_DISPLAY) { | ||||
|         LOG_CRITICAL(Frontend, "eglGetDisplay() failed"); | ||||
|         return; | ||||
|     } | ||||
|     if (eglInitialize(egl_display, 0, 0) != EGL_TRUE) { | ||||
|         LOG_CRITICAL(Frontend, "eglInitialize() failed"); | ||||
|         return; | ||||
|     } | ||||
|     if (EGLint egl_num_configs{}; eglChooseConfig(egl_display, egl_attribs.data(), &egl_config, 1, | ||||
|                                                   &egl_num_configs) != EGL_TRUE) { | ||||
|         LOG_CRITICAL(Frontend, "eglChooseConfig() failed"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     CreateWindowSurface(); | ||||
|  | ||||
|     if (eglQuerySurface(egl_display, egl_surface, EGL_WIDTH, &window_width) != EGL_TRUE) { | ||||
|         return; | ||||
|     } | ||||
|     if (eglQuerySurface(egl_display, egl_surface, EGL_HEIGHT, &window_height) != EGL_TRUE) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (egl_context = eglCreateContext(egl_display, egl_config, 0, egl_context_attribs.data()); | ||||
|         egl_context == EGL_NO_CONTEXT) { | ||||
|         LOG_CRITICAL(Frontend, "eglCreateContext() failed"); | ||||
|         return; | ||||
|     } | ||||
|     if (eglSurfaceAttrib(egl_display, egl_surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED) != | ||||
|         EGL_TRUE) { | ||||
|         LOG_CRITICAL(Frontend, "eglSurfaceAttrib() failed"); | ||||
|         return; | ||||
|     } | ||||
|     if (core_context = CreateSharedContext(); !core_context) { | ||||
|         LOG_CRITICAL(Frontend, "CreateSharedContext() failed"); | ||||
|         return; | ||||
|     } | ||||
|     if (eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context) != EGL_TRUE) { | ||||
|         LOG_CRITICAL(Frontend, "eglMakeCurrent() failed"); | ||||
|         return; | ||||
|     } | ||||
|     if (!gladLoadGLES2Loader((GLADloadproc)eglGetProcAddress)) { | ||||
|         LOG_CRITICAL(Frontend, "gladLoadGLES2Loader() failed"); | ||||
|         return; | ||||
|     } | ||||
|     if (!eglSwapInterval(egl_display, Settings::values.use_vsync_new ? 1 : 0)) { | ||||
|         LOG_CRITICAL(Frontend, "eglSwapInterval() failed"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     OnFramebufferSizeChanged(); | ||||
| } | ||||
|  | ||||
| bool EmuWindow_Android::CreateWindowSurface() { | ||||
|     if (!host_window) { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     EGLint format{}; | ||||
|     eglGetConfigAttrib(egl_display, egl_config, EGL_NATIVE_VISUAL_ID, &format); | ||||
|     ANativeWindow_setBuffersGeometry(host_window, 0, 0, format); | ||||
|  | ||||
|     if (egl_surface = eglCreateWindowSurface(egl_display, egl_config, host_window, 0); | ||||
|         egl_surface == EGL_NO_SURFACE) { | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     return !!egl_surface; | ||||
| } | ||||
|  | ||||
| void EmuWindow_Android::DestroyWindowSurface() { | ||||
|     if (!egl_surface) { | ||||
|         return; | ||||
|     } | ||||
|     if (eglGetCurrentSurface(EGL_DRAW) == egl_surface) { | ||||
|         eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | ||||
|     } | ||||
|     if (!eglDestroySurface(egl_display, egl_surface)) { | ||||
|         LOG_CRITICAL(Frontend, "eglDestroySurface() failed"); | ||||
|     } | ||||
|     egl_surface = EGL_NO_SURFACE; | ||||
| } | ||||
|  | ||||
| void EmuWindow_Android::DestroyContext() { | ||||
|     if (!egl_context) { | ||||
|         return; | ||||
|     } | ||||
|     if (eglGetCurrentContext() == egl_context) { | ||||
|         eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | ||||
|     } | ||||
|     if (!eglDestroyContext(egl_display, egl_context)) { | ||||
|         LOG_CRITICAL(Frontend, "eglDestroySurface() failed"); | ||||
|     } | ||||
|     if (!eglTerminate(egl_display)) { | ||||
|         LOG_CRITICAL(Frontend, "eglTerminate() failed"); | ||||
|     } | ||||
|     egl_context = EGL_NO_CONTEXT; | ||||
|     egl_display = EGL_NO_DISPLAY; | ||||
| } | ||||
|  | ||||
| EmuWindow_Android::~EmuWindow_Android() { | ||||
| @@ -224,48 +83,6 @@ EmuWindow_Android::~EmuWindow_Android() { | ||||
|     DestroyContext(); | ||||
| } | ||||
|  | ||||
| std::unique_ptr<Frontend::GraphicsContext> EmuWindow_Android::CreateSharedContext() const { | ||||
|     return std::make_unique<SharedContext_Android>(egl_display, egl_config, egl_context); | ||||
| } | ||||
|  | ||||
| void EmuWindow_Android::StopPresenting() { | ||||
|     if (presenting_state == PresentingState::Running) { | ||||
|         eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | ||||
|     } | ||||
|     presenting_state = PresentingState::Stopped; | ||||
| } | ||||
|  | ||||
| void EmuWindow_Android::TryPresenting() { | ||||
|     if (presenting_state != PresentingState::Running) { | ||||
|         if (presenting_state == PresentingState::Initial) { | ||||
|             eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context); | ||||
|             glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); | ||||
|             presenting_state = PresentingState::Running; | ||||
|         } else { | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|     eglSwapInterval(egl_display, Settings::values.use_vsync_new ? 1 : 0); | ||||
|     if (VideoCore::g_renderer) { | ||||
|         VideoCore::g_renderer->TryPresent(0); | ||||
|         eglSwapBuffers(egl_display, egl_surface); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void EmuWindow_Android::PollEvents() { | ||||
|     if (!render_window) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     host_window = render_window; | ||||
|     render_window = nullptr; | ||||
|  | ||||
|     DestroyWindowSurface(); | ||||
|     CreateWindowSurface(); | ||||
|     OnFramebufferSizeChanged(); | ||||
|     presenting_state = PresentingState::Initial; | ||||
| } | ||||
|  | ||||
| void EmuWindow_Android::MakeCurrent() { | ||||
|     core_context->MakeCurrent(); | ||||
| } | ||||
|   | ||||
| @@ -5,38 +5,13 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <vector> | ||||
|  | ||||
| #include <EGL/egl.h> | ||||
| #include <EGL/eglext.h> | ||||
|  | ||||
| #include "core/frontend/emu_window.h" | ||||
|  | ||||
| struct ANativeWindow; | ||||
|  | ||||
| class SharedContext_Android : public Frontend::GraphicsContext { | ||||
| public: | ||||
|     SharedContext_Android(EGLDisplay egl_display, EGLConfig egl_config, | ||||
|                           EGLContext egl_share_context); | ||||
|  | ||||
|     ~SharedContext_Android() override; | ||||
|  | ||||
|     void MakeCurrent() override; | ||||
|  | ||||
|     void DoneCurrent() override; | ||||
|  | ||||
| private: | ||||
|     EGLDisplay egl_display{}; | ||||
|     EGLSurface egl_surface{}; | ||||
|     EGLContext egl_context{}; | ||||
| }; | ||||
|  | ||||
| class EmuWindow_Android : public Frontend::EmuWindow { | ||||
| public: | ||||
|     EmuWindow_Android(ANativeWindow* surface); | ||||
|     ~EmuWindow_Android(); | ||||
|  | ||||
|     void Present(); | ||||
|  | ||||
|     /// Called by the onSurfaceChanges() method to change the surface | ||||
|     void OnSurfaceChanged(ANativeWindow* surface); | ||||
|  | ||||
| @@ -46,38 +21,34 @@ public: | ||||
|     /// Handles movement of touch pointer | ||||
|     void OnTouchMoved(int x, int y); | ||||
|  | ||||
|     void PollEvents() override; | ||||
|     void MakeCurrent() override; | ||||
|  | ||||
|     void DoneCurrent() override; | ||||
|  | ||||
|     void TryPresenting(); | ||||
|     void StopPresenting(); | ||||
|     virtual void TryPresenting() {} | ||||
|  | ||||
|     std::unique_ptr<GraphicsContext> CreateSharedContext() const override; | ||||
|     virtual void StopPresenting() {} | ||||
|  | ||||
| private: | ||||
| protected: | ||||
|     void OnFramebufferSizeChanged(); | ||||
|     bool CreateWindowSurface(); | ||||
|     void DestroyWindowSurface(); | ||||
|     void DestroyContext(); | ||||
|  | ||||
|     /// Creates the API specific window surface | ||||
|     virtual bool CreateWindowSurface() { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     /// Destroys the API specific window surface | ||||
|     virtual void DestroyWindowSurface() {} | ||||
|  | ||||
|     /// Destroys the graphics context | ||||
|     virtual void DestroyContext() {} | ||||
|  | ||||
| protected: | ||||
|     ANativeWindow* render_window{}; | ||||
|     ANativeWindow* host_window{}; | ||||
|  | ||||
|     int window_width{}; | ||||
|     int window_height{}; | ||||
|  | ||||
|     EGLConfig egl_config; | ||||
|     EGLSurface egl_surface{}; | ||||
|     EGLContext egl_context{}; | ||||
|     EGLDisplay egl_display{}; | ||||
|  | ||||
|     std::unique_ptr<Frontend::GraphicsContext> core_context; | ||||
|  | ||||
|     enum class PresentingState { | ||||
|         Initial, | ||||
|         Running, | ||||
|         Stopped, | ||||
|     }; | ||||
|     PresentingState presenting_state{}; | ||||
| }; | ||||
|   | ||||
							
								
								
									
										215
									
								
								src/android/app/src/main/jni/emu_window/emu_window_gl.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								src/android/app/src/main/jni/emu_window/emu_window_gl.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,215 @@ | ||||
| // Copyright 2019 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <array> | ||||
| #include <cstdlib> | ||||
| #include <string> | ||||
|  | ||||
| #include <android/native_window_jni.h> | ||||
| #include <glad/glad.h> | ||||
|  | ||||
| #include "common/logging/log.h" | ||||
| #include "common/settings.h" | ||||
| #include "input_common/main.h" | ||||
| #include "jni/emu_window/emu_window_gl.h" | ||||
| #include "video_core/renderer_base.h" | ||||
| #include "video_core/video_core.h" | ||||
|  | ||||
| static constexpr std::array<EGLint, 15> egl_attribs{EGL_SURFACE_TYPE, | ||||
|                                                     EGL_WINDOW_BIT, | ||||
|                                                     EGL_RENDERABLE_TYPE, | ||||
|                                                     EGL_OPENGL_ES3_BIT_KHR, | ||||
|                                                     EGL_BLUE_SIZE, | ||||
|                                                     8, | ||||
|                                                     EGL_GREEN_SIZE, | ||||
|                                                     8, | ||||
|                                                     EGL_RED_SIZE, | ||||
|                                                     8, | ||||
|                                                     EGL_DEPTH_SIZE, | ||||
|                                                     0, | ||||
|                                                     EGL_STENCIL_SIZE, | ||||
|                                                     0, | ||||
|                                                     EGL_NONE}; | ||||
| static constexpr std::array<EGLint, 5> egl_empty_attribs{EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE}; | ||||
| static constexpr std::array<EGLint, 4> egl_context_attribs{EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; | ||||
|  | ||||
| class SharedContext_Android : public Frontend::GraphicsContext { | ||||
| public: | ||||
|     SharedContext_Android(EGLDisplay egl_display, EGLConfig egl_config, | ||||
|                           EGLContext egl_share_context) | ||||
|         : egl_display{egl_display}, egl_surface{eglCreatePbufferSurface(egl_display, egl_config, | ||||
|                                                                         egl_empty_attribs.data())}, | ||||
|           egl_context{eglCreateContext(egl_display, egl_config, egl_share_context, | ||||
|                                        egl_context_attribs.data())} { | ||||
|         ASSERT_MSG(egl_surface, "eglCreatePbufferSurface() failed!"); | ||||
|         ASSERT_MSG(egl_context, "eglCreateContext() failed!"); | ||||
|     } | ||||
|  | ||||
|     ~SharedContext_Android() override { | ||||
|         if (!eglDestroySurface(egl_display, egl_surface)) { | ||||
|             LOG_CRITICAL(Frontend, "eglDestroySurface() failed"); | ||||
|         } | ||||
|  | ||||
|         if (!eglDestroyContext(egl_display, egl_context)) { | ||||
|             LOG_CRITICAL(Frontend, "eglDestroySurface() failed"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void MakeCurrent() override { | ||||
|         eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context); | ||||
|     } | ||||
|  | ||||
|     void DoneCurrent() override { | ||||
|         eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     EGLDisplay egl_display{}; | ||||
|     EGLSurface egl_surface{}; | ||||
|     EGLContext egl_context{}; | ||||
| }; | ||||
|  | ||||
| EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(ANativeWindow* surface) | ||||
|     : EmuWindow_Android{surface} { | ||||
|     if (egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); egl_display == EGL_NO_DISPLAY) { | ||||
|         LOG_CRITICAL(Frontend, "eglGetDisplay() failed"); | ||||
|         return; | ||||
|     } | ||||
|     if (eglInitialize(egl_display, 0, 0) != EGL_TRUE) { | ||||
|         LOG_CRITICAL(Frontend, "eglInitialize() failed"); | ||||
|         return; | ||||
|     } | ||||
|     if (EGLint egl_num_configs{}; eglChooseConfig(egl_display, egl_attribs.data(), &egl_config, 1, | ||||
|                                                   &egl_num_configs) != EGL_TRUE) { | ||||
|         LOG_CRITICAL(Frontend, "eglChooseConfig() failed"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     CreateWindowSurface(); | ||||
|  | ||||
|     if (eglQuerySurface(egl_display, egl_surface, EGL_WIDTH, &window_width) != EGL_TRUE) { | ||||
|         return; | ||||
|     } | ||||
|     if (eglQuerySurface(egl_display, egl_surface, EGL_HEIGHT, &window_height) != EGL_TRUE) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (egl_context = eglCreateContext(egl_display, egl_config, 0, egl_context_attribs.data()); | ||||
|         egl_context == EGL_NO_CONTEXT) { | ||||
|         LOG_CRITICAL(Frontend, "eglCreateContext() failed"); | ||||
|         return; | ||||
|     } | ||||
|     if (eglSurfaceAttrib(egl_display, egl_surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED) != | ||||
|         EGL_TRUE) { | ||||
|         LOG_CRITICAL(Frontend, "eglSurfaceAttrib() failed"); | ||||
|         return; | ||||
|     } | ||||
|     if (core_context = CreateSharedContext(); !core_context) { | ||||
|         LOG_CRITICAL(Frontend, "CreateSharedContext() failed"); | ||||
|         return; | ||||
|     } | ||||
|     if (eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context) != EGL_TRUE) { | ||||
|         LOG_CRITICAL(Frontend, "eglMakeCurrent() failed"); | ||||
|         return; | ||||
|     } | ||||
|     if (!gladLoadGLES2Loader((GLADloadproc)eglGetProcAddress)) { | ||||
|         LOG_CRITICAL(Frontend, "gladLoadGLES2Loader() failed"); | ||||
|         return; | ||||
|     } | ||||
|     if (!eglSwapInterval(egl_display, Settings::values.use_vsync_new ? 1 : 0)) { | ||||
|         LOG_CRITICAL(Frontend, "eglSwapInterval() failed"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     OnFramebufferSizeChanged(); | ||||
| } | ||||
|  | ||||
| bool EmuWindow_Android_OpenGL::CreateWindowSurface() { | ||||
|     if (!host_window) { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     EGLint format{}; | ||||
|     eglGetConfigAttrib(egl_display, egl_config, EGL_NATIVE_VISUAL_ID, &format); | ||||
|     ANativeWindow_setBuffersGeometry(host_window, 0, 0, format); | ||||
|  | ||||
|     if (egl_surface = eglCreateWindowSurface(egl_display, egl_config, host_window, 0); | ||||
|         egl_surface == EGL_NO_SURFACE) { | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     return egl_surface; | ||||
| } | ||||
|  | ||||
| void EmuWindow_Android_OpenGL::DestroyWindowSurface() { | ||||
|     if (!egl_surface) { | ||||
|         return; | ||||
|     } | ||||
|     if (eglGetCurrentSurface(EGL_DRAW) == egl_surface) { | ||||
|         eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | ||||
|     } | ||||
|     if (!eglDestroySurface(egl_display, egl_surface)) { | ||||
|         LOG_CRITICAL(Frontend, "eglDestroySurface() failed"); | ||||
|     } | ||||
|     egl_surface = EGL_NO_SURFACE; | ||||
| } | ||||
|  | ||||
| void EmuWindow_Android_OpenGL::DestroyContext() { | ||||
|     if (!egl_context) { | ||||
|         return; | ||||
|     } | ||||
|     if (eglGetCurrentContext() == egl_context) { | ||||
|         eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | ||||
|     } | ||||
|     if (!eglDestroyContext(egl_display, egl_context)) { | ||||
|         LOG_CRITICAL(Frontend, "eglDestroySurface() failed"); | ||||
|     } | ||||
|     if (!eglTerminate(egl_display)) { | ||||
|         LOG_CRITICAL(Frontend, "eglTerminate() failed"); | ||||
|     } | ||||
|     egl_context = EGL_NO_CONTEXT; | ||||
|     egl_display = EGL_NO_DISPLAY; | ||||
| } | ||||
|  | ||||
| std::unique_ptr<Frontend::GraphicsContext> EmuWindow_Android_OpenGL::CreateSharedContext() const { | ||||
|     return std::make_unique<SharedContext_Android>(egl_display, egl_config, egl_context); | ||||
| } | ||||
|  | ||||
| void EmuWindow_Android_OpenGL::PollEvents() { | ||||
|     if (!render_window) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     host_window = render_window; | ||||
|     render_window = nullptr; | ||||
|  | ||||
|     DestroyWindowSurface(); | ||||
|     CreateWindowSurface(); | ||||
|     OnFramebufferSizeChanged(); | ||||
|     presenting_state = PresentingState::Initial; | ||||
| } | ||||
|  | ||||
| void EmuWindow_Android_OpenGL::StopPresenting() { | ||||
|     if (presenting_state == PresentingState::Running) { | ||||
|         eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | ||||
|     } | ||||
|     presenting_state = PresentingState::Stopped; | ||||
| } | ||||
|  | ||||
| void EmuWindow_Android_OpenGL::TryPresenting() { | ||||
|     if (presenting_state == PresentingState::Initial) [[unlikely]] { | ||||
|         eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context); | ||||
|         glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); | ||||
|         presenting_state = PresentingState::Running; | ||||
|     } | ||||
|     if (presenting_state != PresentingState::Running) [[unlikely]] { | ||||
|         return; | ||||
|     } | ||||
|     eglSwapInterval(egl_display, Settings::values.use_vsync_new ? 1 : 0); | ||||
|     if (VideoCore::g_renderer) { | ||||
|         VideoCore::g_renderer->TryPresent(0); | ||||
|         eglSwapBuffers(egl_display, egl_surface); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										44
									
								
								src/android/app/src/main/jni/emu_window/emu_window_gl.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/android/app/src/main/jni/emu_window/emu_window_gl.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| // Copyright 2019 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <vector> | ||||
|  | ||||
| #include <EGL/egl.h> | ||||
| #include <EGL/eglext.h> | ||||
|  | ||||
| #include "jni/emu_window/emu_window.h" | ||||
|  | ||||
| struct ANativeWindow; | ||||
|  | ||||
| class EmuWindow_Android_OpenGL : public EmuWindow_Android { | ||||
| public: | ||||
|     EmuWindow_Android_OpenGL(ANativeWindow* surface); | ||||
|     ~EmuWindow_Android_OpenGL() override = default; | ||||
|  | ||||
|     void TryPresenting() override; | ||||
|     void StopPresenting() override; | ||||
|     void PollEvents() override; | ||||
|  | ||||
|     std::unique_ptr<GraphicsContext> CreateSharedContext() const override; | ||||
|  | ||||
| private: | ||||
|     bool CreateWindowSurface() override; | ||||
|     void DestroyWindowSurface() override; | ||||
|     void DestroyContext() override; | ||||
|  | ||||
| private: | ||||
|     EGLConfig egl_config; | ||||
|     EGLSurface egl_surface{}; | ||||
|     EGLContext egl_context{}; | ||||
|     EGLDisplay egl_display{}; | ||||
|  | ||||
|     enum class PresentingState { | ||||
|         Initial, | ||||
|         Running, | ||||
|         Stopped, | ||||
|     }; | ||||
|     PresentingState presenting_state{}; | ||||
| }; | ||||
							
								
								
									
										53
									
								
								src/android/app/src/main/jni/emu_window/emu_window_vk.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/android/app/src/main/jni/emu_window/emu_window_vk.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| // Copyright 2019 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <cstdlib> | ||||
| #include <android/native_window_jni.h> | ||||
| #include "common/logging/log.h" | ||||
| #include "common/settings.h" | ||||
| #include "jni/emu_window/emu_window_vk.h" | ||||
| #include "video_core/video_core.h" | ||||
|  | ||||
| class GraphicsContext_Android final : public Frontend::GraphicsContext { | ||||
| public: | ||||
|     explicit GraphicsContext_Android(std::shared_ptr<Common::DynamicLibrary> driver_library_) | ||||
|         : driver_library{driver_library_} {} | ||||
|  | ||||
|     ~GraphicsContext_Android() = default; | ||||
|  | ||||
|     std::shared_ptr<Common::DynamicLibrary> GetDriverLibrary() override { | ||||
|         return driver_library; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     std::shared_ptr<Common::DynamicLibrary> driver_library; | ||||
| }; | ||||
|  | ||||
| EmuWindow_Android_Vulkan::EmuWindow_Android_Vulkan( | ||||
|     ANativeWindow* surface, std::shared_ptr<Common::DynamicLibrary> driver_library_) | ||||
|     : EmuWindow_Android{surface}, driver_library{driver_library_} { | ||||
|     CreateWindowSurface(); | ||||
|  | ||||
|     if (core_context = CreateSharedContext(); !core_context) { | ||||
|         LOG_CRITICAL(Frontend, "CreateSharedContext() failed"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     OnFramebufferSizeChanged(); | ||||
| } | ||||
|  | ||||
| bool EmuWindow_Android_Vulkan::CreateWindowSurface() { | ||||
|     if (!host_window) { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     window_info.type = Frontend::WindowSystemType::Android; | ||||
|     window_info.render_surface = host_window; | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| std::unique_ptr<Frontend::GraphicsContext> EmuWindow_Android_Vulkan::CreateSharedContext() const { | ||||
|     return std::make_unique<GraphicsContext_Android>(driver_library); | ||||
| } | ||||
							
								
								
									
										26
									
								
								src/android/app/src/main/jni/emu_window/emu_window_vk.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/android/app/src/main/jni/emu_window/emu_window_vk.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| // Copyright 2022 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "jni/emu_window/emu_window.h" | ||||
|  | ||||
| struct ANativeWindow; | ||||
|  | ||||
| class EmuWindow_Android_Vulkan : public EmuWindow_Android { | ||||
| public: | ||||
|     EmuWindow_Android_Vulkan(ANativeWindow* surface, | ||||
|                              std::shared_ptr<Common::DynamicLibrary> driver_library); | ||||
|     ~EmuWindow_Android_Vulkan() override = default; | ||||
|  | ||||
|     void PollEvents() override {} | ||||
|  | ||||
|     std::unique_ptr<GraphicsContext> CreateSharedContext() const override; | ||||
|  | ||||
| private: | ||||
|     bool CreateWindowSurface() override; | ||||
|  | ||||
| private: | ||||
|     std::shared_ptr<Common::DynamicLibrary> driver_library; | ||||
| }; | ||||
| @@ -4,13 +4,16 @@ | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <thread> | ||||
| #include <dlfcn.h> | ||||
|  | ||||
| #include <android/api-level.h> | ||||
| #include <android/native_window_jni.h> | ||||
|  | ||||
| #include "audio_core/dsp_interface.h" | ||||
| #include "common/aarch64/cpu_detect.h" | ||||
| #include "common/arch.h" | ||||
| #include "common/common_paths.h" | ||||
| #include "common/dynamic_library/dynamic_library.h" | ||||
| #include "common/file_util.h" | ||||
| #include "common/logging/backend.h" | ||||
| #include "common/logging/log.h" | ||||
| @@ -33,7 +36,8 @@ | ||||
| #include "jni/camera/ndk_camera.h" | ||||
| #include "jni/camera/still_image_camera.h" | ||||
| #include "jni/config.h" | ||||
| #include "jni/emu_window/emu_window.h" | ||||
| #include "jni/emu_window/emu_window_gl.h" | ||||
| #include "jni/emu_window/emu_window_vk.h" | ||||
| #include "jni/game_settings.h" | ||||
| #include "jni/id_cache.h" | ||||
| #include "jni/input_manager.h" | ||||
| @@ -42,10 +46,15 @@ | ||||
| #include "video_core/renderer_base.h" | ||||
| #include "video_core/video_core.h" | ||||
|  | ||||
| #if CITRA_ARCH(arm64) | ||||
| #include <adrenotools/driver.h> | ||||
| #endif | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| ANativeWindow* s_surf; | ||||
|  | ||||
| std::shared_ptr<Common::DynamicLibrary> vulkan_library{}; | ||||
| std::unique_ptr<EmuWindow_Android> window; | ||||
|  | ||||
| std::atomic<bool> stop_run{true}; | ||||
| @@ -123,11 +132,14 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { | ||||
|     const auto graphics_api = Settings::values.graphics_api.GetValue(); | ||||
|     switch (graphics_api) { | ||||
|     case Settings::GraphicsAPI::OpenGL: | ||||
|         window = std::make_unique<EmuWindow_Android>(s_surf); | ||||
|         window = std::make_unique<EmuWindow_Android_OpenGL>(s_surf); | ||||
|         break; | ||||
|     case Settings::GraphicsAPI::Vulkan: | ||||
|         window = std::make_unique<EmuWindow_Android_Vulkan>(s_surf, vulkan_library); | ||||
|         break; | ||||
|     default: | ||||
|         LOG_CRITICAL(Frontend, "Unknown graphics API {}, using OpenGL", graphics_api); | ||||
|         window = std::make_unique<EmuWindow_Android>(s_surf); | ||||
|         LOG_CRITICAL(Frontend, "Unknown graphics API {}, using Vulkan", graphics_api); | ||||
|         window = std::make_unique<EmuWindow_Android_Vulkan>(s_surf, vulkan_library); | ||||
|     } | ||||
|  | ||||
|     Core::System& system{Core::System::GetInstance()}; | ||||
| @@ -228,6 +240,37 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { | ||||
|     return Core::System::ResultStatus::Success; | ||||
| } | ||||
|  | ||||
| void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir, | ||||
|                          const std::string& custom_driver_name, | ||||
|                          const std::string& file_redirect_dir) { | ||||
| #if CITRA_ARCH(arm64) | ||||
|     void* handle{}; | ||||
|     const char* file_redirect_dir_{}; | ||||
|     int featureFlags{}; | ||||
|  | ||||
|     // Enable driver file redirection when renderer debugging is enabled. | ||||
|     if (Settings::values.renderer_debug && file_redirect_dir.size()) { | ||||
|         featureFlags |= ADRENOTOOLS_DRIVER_FILE_REDIRECT; | ||||
|         file_redirect_dir_ = file_redirect_dir.c_str(); | ||||
|     } | ||||
|  | ||||
|     // Try to load a custom driver. | ||||
|     if (custom_driver_name.size()) { | ||||
|         handle = adrenotools_open_libvulkan( | ||||
|             RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(), | ||||
|             custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr); | ||||
|     } | ||||
|  | ||||
|     // Try to load the system driver. | ||||
|     if (!handle) { | ||||
|         handle = adrenotools_open_libvulkan(RTLD_NOW, featureFlags, nullptr, hook_lib_dir.c_str(), | ||||
|                                             nullptr, nullptr, file_redirect_dir_, nullptr); | ||||
|     } | ||||
|  | ||||
|     vulkan_library = std::make_shared<Common::DynamicLibrary>(handle); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| extern "C" { | ||||
|  | ||||
| void Java_org_citra_citra_1emu_NativeLibrary_SurfaceChanged(JNIEnv* env, | ||||
| @@ -238,6 +281,9 @@ void Java_org_citra_citra_1emu_NativeLibrary_SurfaceChanged(JNIEnv* env, | ||||
|     if (window) { | ||||
|         window->OnSurfaceChanged(s_surf); | ||||
|     } | ||||
|     if (VideoCore::g_renderer) { | ||||
|         VideoCore::g_renderer->NotifySurfaceChanged(); | ||||
|     } | ||||
|  | ||||
|     LOG_INFO(Frontend, "Surface changed"); | ||||
| } | ||||
| @@ -258,6 +304,15 @@ void Java_org_citra_citra_1emu_NativeLibrary_DoFrame(JNIEnv* env, [[maybe_unused | ||||
|     window->TryPresenting(); | ||||
| } | ||||
|  | ||||
| void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz, | ||||
|                                                                        jstring hook_lib_dir, | ||||
|                                                                        jstring custom_driver_dir, | ||||
|                                                                        jstring custom_driver_name, | ||||
|                                                                        jstring file_redirect_dir) { | ||||
|     InitializeGpuDriver(GetJString(env, hook_lib_dir), GetJString(env, custom_driver_dir), | ||||
|                         GetJString(env, custom_driver_name), GetJString(env, file_redirect_dir)); | ||||
| } | ||||
|  | ||||
| void Java_org_citra_citra_1emu_NativeLibrary_NotifyOrientationChange(JNIEnv* env, | ||||
|                                                                      [[maybe_unused]] jclass clazz, | ||||
|                                                                      jint layout_option, | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -177,11 +177,13 @@ | ||||
|     </integer-array> | ||||
|  | ||||
|     <string-array name="graphicsApiNames"> | ||||
|         <item>OpenGLES</item> | ||||
|         <item>OpenGL ES</item> | ||||
|         <item>Vulkan</item> | ||||
|     </string-array> | ||||
|  | ||||
|     <integer-array name="graphicsApiValues"> | ||||
|         <item>1</item> | ||||
|         <item>2</item> | ||||
|     </integer-array> | ||||
|  | ||||
|     <string-array name="textureFilterNames"> | ||||
|   | ||||
| @@ -74,6 +74,10 @@ | ||||
|     <!-- Graphics settings strings --> | ||||
|     <string name="renderer">Renderer</string> | ||||
|     <string name="graphics_api">Graphics API</string> | ||||
|     <string name="spirv_shader_gen">Enable SPIR-V shader generation</string> | ||||
|     <string name="spirv_shader_gen_description">Emits the fragment shader used to emulate PICA using SPIR-V instead of GLSL</string> | ||||
|     <string name="async_shaders">Enable asynchronous shader compilation</string> | ||||
|     <string name="async_shaders_description">Compiles shaders in the background to reduce stuttering during gameplay. When enabled expect temporary graphical glitches</string> | ||||
|     <string name="renderer_debug">Debug Renderer</string> | ||||
|     <string name="renderer_debug_description">Log additional graphics related debug information. When enabled, game performance will be significantly reduced.</string> | ||||
|     <string name="vsync">Enable V-Sync</string> | ||||
|   | ||||
| @@ -12,6 +12,8 @@ add_executable(citra | ||||
|     emu_window/emu_window_sdl2_gl.h | ||||
|     emu_window/emu_window_sdl2_sw.cpp | ||||
|     emu_window/emu_window_sdl2_sw.h | ||||
|     emu_window/emu_window_sdl2_vk.cpp | ||||
|     emu_window/emu_window_sdl2_vk.h | ||||
|     precompiled_headers.h | ||||
|     resource.h | ||||
| ) | ||||
|   | ||||
| @@ -15,6 +15,7 @@ | ||||
| #include "citra/emu_window/emu_window_sdl2.h" | ||||
| #include "citra/emu_window/emu_window_sdl2_gl.h" | ||||
| #include "citra/emu_window/emu_window_sdl2_sw.h" | ||||
| #include "citra/emu_window/emu_window_sdl2_vk.h" | ||||
| #include "common/common_paths.h" | ||||
| #include "common/detached_tasks.h" | ||||
| #include "common/file_util.h" | ||||
| @@ -351,6 +352,8 @@ int main(int argc, char** argv) { | ||||
|         switch (Settings::values.graphics_api.GetValue()) { | ||||
|         case Settings::GraphicsAPI::OpenGL: | ||||
|             return std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen, is_secondary); | ||||
|         case Settings::GraphicsAPI::Vulkan: | ||||
|             return std::make_unique<EmuWindow_SDL2_VK>(system, fullscreen, is_secondary); | ||||
|         case Settings::GraphicsAPI::Software: | ||||
|             return std::make_unique<EmuWindow_SDL2_SW>(system, fullscreen, is_secondary); | ||||
|         } | ||||
|   | ||||
| @@ -133,6 +133,10 @@ void Config::ReadValues() { | ||||
|  | ||||
|     // Renderer | ||||
|     ReadSetting("Renderer", Settings::values.graphics_api); | ||||
|     ReadSetting("Renderer", Settings::values.physical_device); | ||||
|     ReadSetting("Renderer", Settings::values.spirv_shader_gen); | ||||
|     ReadSetting("Renderer", Settings::values.async_shader_compilation); | ||||
|     ReadSetting("Renderer", Settings::values.async_presentation); | ||||
|     ReadSetting("Renderer", Settings::values.use_gles); | ||||
|     ReadSetting("Renderer", Settings::values.use_hw_shader); | ||||
|     ReadSetting("Renderer", Settings::values.shaders_accurate_mul); | ||||
|   | ||||
| @@ -99,7 +99,7 @@ cpu_clock_percentage = | ||||
|  | ||||
| [Renderer] | ||||
| # Whether to render using OpenGL or Software | ||||
| # 0: Software, 1: OpenGL (default) | ||||
| # 0: Software, 1: OpenGL (default), 2: Vulkan | ||||
| graphics_api = | ||||
|  | ||||
| # Whether to render using GLES or OpenGL | ||||
|   | ||||
							
								
								
									
										90
									
								
								src/citra/emu_window/emu_window_sdl2_vk.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/citra/emu_window/emu_window_sdl2_vk.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <cstdlib> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <SDL.h> | ||||
| #include <SDL_syswm.h> | ||||
| #include <fmt/format.h> | ||||
| #include "citra/emu_window/emu_window_sdl2_vk.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/scm_rev.h" | ||||
| #include "core/frontend/emu_window.h" | ||||
|  | ||||
| class DummyContext : public Frontend::GraphicsContext {}; | ||||
|  | ||||
| EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen, bool is_secondary) | ||||
|     : EmuWindow_SDL2{system, is_secondary} { | ||||
|     const std::string window_title = fmt::format("Citra {} | {}-{}", Common::g_build_fullname, | ||||
|                                                  Common::g_scm_branch, Common::g_scm_desc); | ||||
|     render_window = | ||||
|         SDL_CreateWindow(window_title.c_str(), | ||||
|                          SDL_WINDOWPOS_UNDEFINED, // x position | ||||
|                          SDL_WINDOWPOS_UNDEFINED, // y position | ||||
|                          Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight, | ||||
|                          SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); | ||||
|     SDL_SysWMinfo wm; | ||||
|     SDL_VERSION(&wm.version); | ||||
|     if (SDL_GetWindowWMInfo(render_window, &wm) == SDL_FALSE) { | ||||
|         LOG_CRITICAL(Frontend, "Failed to get information from the window manager"); | ||||
|         std::exit(EXIT_FAILURE); | ||||
|     } | ||||
|  | ||||
|     if (fullscreen) { | ||||
|         Fullscreen(); | ||||
|         SDL_ShowCursor(false); | ||||
|     } | ||||
|  | ||||
|     switch (wm.subsystem) { | ||||
| #ifdef SDL_VIDEO_DRIVER_WINDOWS | ||||
|     case SDL_SYSWM_TYPE::SDL_SYSWM_WINDOWS: | ||||
|         window_info.type = Frontend::WindowSystemType::Windows; | ||||
|         window_info.render_surface = reinterpret_cast<void*>(wm.info.win.window); | ||||
|         break; | ||||
| #endif | ||||
| #ifdef SDL_VIDEO_DRIVER_X11 | ||||
|     case SDL_SYSWM_TYPE::SDL_SYSWM_X11: | ||||
|         window_info.type = Frontend::WindowSystemType::X11; | ||||
|         window_info.display_connection = wm.info.x11.display; | ||||
|         window_info.render_surface = reinterpret_cast<void*>(wm.info.x11.window); | ||||
|         break; | ||||
| #endif | ||||
| #ifdef SDL_VIDEO_DRIVER_WAYLAND | ||||
|     case SDL_SYSWM_TYPE::SDL_SYSWM_WAYLAND: | ||||
|         window_info.type = Frontend::WindowSystemType::Wayland; | ||||
|         window_info.display_connection = wm.info.wl.display; | ||||
|         window_info.render_surface = wm.info.wl.surface; | ||||
|         break; | ||||
| #endif | ||||
| #ifdef SDL_VIDEO_DRIVER_COCOA | ||||
|     case SDL_SYSWM_TYPE::SDL_SYSWM_COCOA: | ||||
|         window_info.type = Frontend::WindowSystemType::MacOS; | ||||
|         window_info.render_surface = SDL_Metal_GetLayer(SDL_Metal_CreateView(render_window)); | ||||
|         break; | ||||
| #endif | ||||
| #ifdef SDL_VIDEO_DRIVER_ANDROID | ||||
|     case SDL_SYSWM_TYPE::SDL_SYSWM_ANDROID: | ||||
|         window_info.type = Frontend::WindowSystemType::Android; | ||||
|         window_info.render_surface = reinterpret_cast<void*>(wm.info.android.window); | ||||
|         break; | ||||
| #endif | ||||
|     default: | ||||
|         LOG_CRITICAL(Frontend, "Window manager subsystem {} not implemented", wm.subsystem); | ||||
|         std::exit(EXIT_FAILURE); | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     render_window_id = SDL_GetWindowID(render_window); | ||||
|  | ||||
|     OnResize(); | ||||
|     OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); | ||||
|     SDL_PumpEvents(); | ||||
| } | ||||
|  | ||||
| EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() = default; | ||||
|  | ||||
| std::unique_ptr<Frontend::GraphicsContext> EmuWindow_SDL2_VK::CreateSharedContext() const { | ||||
|     return std::make_unique<DummyContext>(); | ||||
| } | ||||
							
								
								
									
										24
									
								
								src/citra/emu_window/emu_window_sdl2_vk.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/citra/emu_window/emu_window_sdl2_vk.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
| #include "citra/emu_window/emu_window_sdl2.h" | ||||
|  | ||||
| namespace Frontend { | ||||
| class GraphicsContext; | ||||
| } | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| } | ||||
|  | ||||
| class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 { | ||||
| public: | ||||
|     explicit EmuWindow_SDL2_VK(Core::System& system_, bool fullscreen, bool is_secondary); | ||||
|     ~EmuWindow_SDL2_VK() override; | ||||
|  | ||||
|     std::unique_ptr<Frontend::GraphicsContext> CreateSharedContext() const override; | ||||
| }; | ||||
| @@ -185,6 +185,8 @@ add_executable(citra-qt | ||||
|     util/spinbox.h | ||||
|     util/util.cpp | ||||
|     util/util.h | ||||
|     util/vk_device_info.cpp | ||||
|     util/vk_device_info.h | ||||
| ) | ||||
|  | ||||
| file(GLOB COMPAT_LIST | ||||
|   | ||||
| @@ -229,20 +229,10 @@ class RenderWidget : public QWidget { | ||||
| public: | ||||
|     RenderWidget(GRenderWindow* parent) : QWidget(parent) { | ||||
|         setMouseTracking(true); | ||||
|     } | ||||
|  | ||||
|     virtual ~RenderWidget() = default; | ||||
|  | ||||
|     virtual void Present() {} | ||||
|  | ||||
|     void paintEvent(QPaintEvent* event) override { | ||||
|         Present(); | ||||
|         update(); | ||||
|     } | ||||
|  | ||||
|     std::pair<unsigned, unsigned> GetSize() const { | ||||
|         return std::make_pair(width(), height()); | ||||
|     } | ||||
|     virtual ~RenderWidget() = default; | ||||
| }; | ||||
|  | ||||
| #ifdef HAS_OPENGL | ||||
| @@ -262,7 +252,7 @@ public: | ||||
|         context = std::move(context_); | ||||
|     } | ||||
|  | ||||
|     void Present() override { | ||||
|     void Present() { | ||||
|         if (!isVisible()) { | ||||
|             return; | ||||
|         } | ||||
| @@ -278,6 +268,11 @@ public: | ||||
|         glFinish(); | ||||
|     } | ||||
|  | ||||
|     void paintEvent(QPaintEvent* event) override { | ||||
|         Present(); | ||||
|         update(); | ||||
|     } | ||||
|  | ||||
|     QPaintEngine* paintEngine() const override { | ||||
|         return nullptr; | ||||
|     } | ||||
| @@ -289,11 +284,27 @@ private: | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| class VulkanRenderWidget : public RenderWidget { | ||||
| public: | ||||
|     explicit VulkanRenderWidget(GRenderWindow* parent) : RenderWidget(parent) { | ||||
|         setAttribute(Qt::WA_NativeWindow); | ||||
|         setAttribute(Qt::WA_PaintOnScreen); | ||||
|         if (GetWindowSystemType() == Frontend::WindowSystemType::Wayland) { | ||||
|             setAttribute(Qt::WA_DontCreateNativeAncestors); | ||||
|         } | ||||
|         windowHandle()->setSurfaceType(QWindow::VulkanSurface); | ||||
|     } | ||||
|  | ||||
|     QPaintEngine* paintEngine() const override { | ||||
|         return nullptr; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| struct SoftwareRenderWidget : public RenderWidget { | ||||
|     explicit SoftwareRenderWidget(GRenderWindow* parent, Core::System& system_) | ||||
|         : RenderWidget(parent), system(system_) {} | ||||
|  | ||||
|     void Present() override { | ||||
|     void Present() { | ||||
|         if (!isVisible()) { | ||||
|             return; | ||||
|         } | ||||
| @@ -323,6 +334,11 @@ struct SoftwareRenderWidget : public RenderWidget { | ||||
|         painter.end(); | ||||
|     } | ||||
|  | ||||
|     void paintEvent(QPaintEvent* event) override { | ||||
|         Present(); | ||||
|         update(); | ||||
|     } | ||||
|  | ||||
|     QImage LoadFramebuffer(VideoCore::ScreenId screen_id) { | ||||
|         const auto& renderer = static_cast<SwRenderer::RendererSoftware&>(system.Renderer()); | ||||
|         const auto& info = renderer.Screen(screen_id); | ||||
| @@ -601,6 +617,9 @@ bool GRenderWindow::InitRenderTarget() { | ||||
|             return false; | ||||
|         } | ||||
|         break; | ||||
|     case Settings::GraphicsAPI::Vulkan: | ||||
|         InitializeVulkan(); | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     // Update the Window System information with the new render target | ||||
| @@ -686,6 +705,13 @@ bool GRenderWindow::InitializeOpenGL() { | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void GRenderWindow::InitializeVulkan() { | ||||
|     auto child = new VulkanRenderWidget(this); | ||||
|     child_widget = child; | ||||
|     child_widget->windowHandle()->create(); | ||||
|     main_context = std::make_unique<DummyContext>(); | ||||
| } | ||||
|  | ||||
| void GRenderWindow::InitializeSoftware() { | ||||
|     child_widget = new SoftwareRenderWidget(this, system); | ||||
|     main_context = std::make_unique<DummyContext>(); | ||||
|   | ||||
| @@ -187,6 +187,7 @@ private: | ||||
|     void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override; | ||||
|  | ||||
|     bool InitializeOpenGL(); | ||||
|     void InitializeVulkan(); | ||||
|     void InitializeSoftware(); | ||||
|     bool LoadOpenGL(); | ||||
|  | ||||
|   | ||||
| @@ -483,6 +483,7 @@ void Config::ReadDebuggingValues() { | ||||
|     ReadBasicSetting(Settings::values.use_gdbstub); | ||||
|     ReadBasicSetting(Settings::values.gdbstub_port); | ||||
|     ReadBasicSetting(Settings::values.renderer_debug); | ||||
|     ReadBasicSetting(Settings::values.dump_command_buffers); | ||||
|  | ||||
|     qt_config->beginGroup(QStringLiteral("LLE")); | ||||
|     for (const auto& service_module : Service::service_module_map) { | ||||
| @@ -627,6 +628,10 @@ void Config::ReadRendererValues() { | ||||
|     qt_config->beginGroup(QStringLiteral("Renderer")); | ||||
|  | ||||
|     ReadGlobalSetting(Settings::values.graphics_api); | ||||
|     ReadGlobalSetting(Settings::values.physical_device); | ||||
|     ReadGlobalSetting(Settings::values.spirv_shader_gen); | ||||
|     ReadGlobalSetting(Settings::values.async_shader_compilation); | ||||
|     ReadGlobalSetting(Settings::values.async_presentation); | ||||
|     ReadGlobalSetting(Settings::values.use_hw_shader); | ||||
|     ReadGlobalSetting(Settings::values.shaders_accurate_mul); | ||||
|     ReadGlobalSetting(Settings::values.use_disk_shader_cache); | ||||
| @@ -1107,6 +1112,10 @@ void Config::SaveRendererValues() { | ||||
|     qt_config->beginGroup(QStringLiteral("Renderer")); | ||||
|  | ||||
|     WriteGlobalSetting(Settings::values.graphics_api); | ||||
|     WriteGlobalSetting(Settings::values.physical_device); | ||||
|     WriteGlobalSetting(Settings::values.spirv_shader_gen); | ||||
|     WriteGlobalSetting(Settings::values.async_shader_compilation); | ||||
|     WriteGlobalSetting(Settings::values.async_presentation); | ||||
|     WriteGlobalSetting(Settings::values.use_hw_shader); | ||||
|     WriteGlobalSetting(Settings::values.shaders_accurate_mul); | ||||
|     WriteGlobalSetting(Settings::values.use_disk_shader_cache); | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <QDesktopServices> | ||||
| #include <QMessageBox> | ||||
| #include <QUrl> | ||||
| #include "citra_qt/configuration/configuration_shared.h" | ||||
| #include "citra_qt/configuration/configure_debug.h" | ||||
| @@ -12,6 +13,7 @@ | ||||
| #include "common/logging/backend.h" | ||||
| #include "common/settings.h" | ||||
| #include "ui_configure_debug.h" | ||||
| #include "video_core/renderer_vulkan/vk_instance.h" | ||||
|  | ||||
| // The QSlider doesn't have an easy way to set a custom step amount, | ||||
| // so we can just convert from the sliders range (0 - 79) to the expected | ||||
| @@ -34,8 +36,39 @@ ConfigureDebug::ConfigureDebug(bool is_powered_on_, QWidget* parent) | ||||
|         QDesktopServices::openUrl(QUrl::fromLocalFile(path)); | ||||
|     }); | ||||
|  | ||||
|     connect(ui->toggle_renderer_debug, &QCheckBox::clicked, this, [this](bool checked) { | ||||
|         if (checked && Settings::values.graphics_api.GetValue() == Settings::GraphicsAPI::Vulkan) { | ||||
|             try { | ||||
|                 Vulkan::Instance debug_inst{true}; | ||||
|             } catch (vk::LayerNotPresentError&) { | ||||
|                 ui->toggle_renderer_debug->toggle(); | ||||
|                 QMessageBox::warning(this, tr("Validation layer not available"), | ||||
|                                      tr("Unable to enable debug renderer because the layer " | ||||
|                                         "<strong>VK_LAYER_KHRONOS_validation</strong> is missing. " | ||||
|                                         "Please install the Vulkan SDK or the appropriate package " | ||||
|                                         "of your distribution")); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     connect(ui->toggle_dump_command_buffers, &QCheckBox::clicked, this, [this](bool checked) { | ||||
|         if (checked && Settings::values.graphics_api.GetValue() == Settings::GraphicsAPI::Vulkan) { | ||||
|             try { | ||||
|                 Vulkan::Instance debug_inst{false, true}; | ||||
|             } catch (vk::LayerNotPresentError&) { | ||||
|                 ui->toggle_dump_command_buffers->toggle(); | ||||
|                 QMessageBox::warning(this, tr("Command buffer dumping not available"), | ||||
|                                      tr("Unable to enable command buffer dumping because the layer " | ||||
|                                         "<strong>VK_LAYER_LUNARG_api_dump</strong> is missing. " | ||||
|                                         "Please install the Vulkan SDK or the appropriate package " | ||||
|                                         "of your distribution")); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     ui->toggle_cpu_jit->setEnabled(!is_powered_on); | ||||
|     ui->toggle_renderer_debug->setEnabled(!is_powered_on); | ||||
|     ui->toggle_dump_command_buffers->setEnabled(!is_powered_on); | ||||
|  | ||||
|     // Set a minimum width for the label to prevent the slider from changing size. | ||||
|     // This scales across DPIs. (This value should be enough for "xxx%") | ||||
| @@ -62,6 +95,7 @@ void ConfigureDebug::SetConfiguration() { | ||||
|     ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter.GetValue())); | ||||
|     ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit.GetValue()); | ||||
|     ui->toggle_renderer_debug->setChecked(Settings::values.renderer_debug.GetValue()); | ||||
|     ui->toggle_dump_command_buffers->setChecked(Settings::values.dump_command_buffers.GetValue()); | ||||
|  | ||||
|     if (!Settings::IsConfiguringGlobal()) { | ||||
|         if (Settings::values.cpu_clock_percentage.UsingGlobal()) { | ||||
| @@ -92,6 +126,7 @@ void ConfigureDebug::ApplyConfiguration() { | ||||
|     Common::Log::SetGlobalFilter(filter); | ||||
|     Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked(); | ||||
|     Settings::values.renderer_debug = ui->toggle_renderer_debug->isChecked(); | ||||
|     Settings::values.dump_command_buffers = ui->toggle_dump_command_buffers->isChecked(); | ||||
|  | ||||
|     ConfigurationShared::ApplyPerGameSetting( | ||||
|         &Settings::values.cpu_clock_percentage, ui->clock_speed_combo, | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>523</width> | ||||
|     <height>447</height> | ||||
|     <height>458</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
| @@ -112,16 +112,6 @@ | ||||
|       <string>CPU</string> | ||||
|      </property> | ||||
|      <layout class="QGridLayout" name="gridLayout_2"> | ||||
|       <item row="2" column="0"> | ||||
|        <widget class="QCheckBox" name="toggle_cpu_jit"> | ||||
|         <property name="toolTip"> | ||||
|          <string><html><head/><body><p>Enables the use of the ARM JIT compiler for emulating the 3DS CPUs. Don't disable unless for debugging purposes</p></body></html></string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>Enable CPU JIT</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="1" column="0"> | ||||
|        <widget class="QWidget" name="clock_speed_widget" native="true"> | ||||
|         <layout class="QHBoxLayout" name="clock_speed_layout"> | ||||
| @@ -202,6 +192,16 @@ | ||||
|         </layout> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="0"> | ||||
|        <widget class="QCheckBox" name="toggle_cpu_jit"> | ||||
|         <property name="toolTip"> | ||||
|          <string><html><head/><body><p>Enables the use of the ARM JIT compiler for emulating the 3DS CPUs. Don't disable unless for debugging purposes</p></body></html></string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>Enable CPU JIT</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="3" column="0"> | ||||
|        <widget class="QCheckBox" name="toggle_renderer_debug"> | ||||
|         <property name="text"> | ||||
| @@ -209,6 +209,13 @@ | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="4" column="0"> | ||||
|        <widget class="QCheckBox" name="toggle_dump_command_buffers"> | ||||
|         <property name="text"> | ||||
|          <string>Dump command buffers</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
|   | ||||
| @@ -23,14 +23,14 @@ | ||||
| #include "ui_configure.h" | ||||
|  | ||||
| ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Core::System& system_, | ||||
|                                  bool enable_web_config) | ||||
|                                  std::span<const QString> physical_devices, bool enable_web_config) | ||||
|     : QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()}, registry{registry_}, | ||||
|       system{system_}, is_powered_on{system.IsPoweredOn()}, | ||||
|       general_tab{std::make_unique<ConfigureGeneral>(this)}, | ||||
|       system_tab{std::make_unique<ConfigureSystem>(system, this)}, | ||||
|       input_tab{std::make_unique<ConfigureInput>(this)}, | ||||
|       hotkeys_tab{std::make_unique<ConfigureHotkeys>(this)}, | ||||
|       graphics_tab{std::make_unique<ConfigureGraphics>(is_powered_on, this)}, | ||||
|       graphics_tab{std::make_unique<ConfigureGraphics>(physical_devices, is_powered_on, this)}, | ||||
|       enhancements_tab{std::make_unique<ConfigureEnhancements>(this)}, | ||||
|       audio_tab{std::make_unique<ConfigureAudio>(is_powered_on, this)}, | ||||
|       camera_tab{std::make_unique<ConfigureCamera>(this)}, | ||||
|   | ||||
| @@ -5,7 +5,9 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
| #include <span> | ||||
| #include <QDialog> | ||||
| #include <QString> | ||||
|  | ||||
| class HotkeyRegistry; | ||||
|  | ||||
| @@ -35,6 +37,7 @@ class ConfigureDialog : public QDialog { | ||||
|  | ||||
| public: | ||||
|     explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, Core::System& system, | ||||
|                              std::span<const QString> physical_devices, | ||||
|                              bool enable_web_config = true); | ||||
|     ~ConfigureDialog() override; | ||||
|  | ||||
|   | ||||
| @@ -7,16 +7,34 @@ | ||||
| #include "citra_qt/configuration/configure_graphics.h" | ||||
| #include "common/settings.h" | ||||
| #include "ui_configure_graphics.h" | ||||
| #include "video_core/renderer_vulkan/vk_instance.h" | ||||
|  | ||||
| ConfigureGraphics::ConfigureGraphics(bool is_powered_on, QWidget* parent) | ||||
| ConfigureGraphics::ConfigureGraphics(std::span<const QString> physical_devices, bool is_powered_on, | ||||
|                                      QWidget* parent) | ||||
|     : QWidget(parent), ui(std::make_unique<Ui::ConfigureGraphics>()) { | ||||
|     ui->setupUi(this); | ||||
|  | ||||
|     SetupPerGameUI(); | ||||
|  | ||||
|     for (const QString& name : physical_devices) { | ||||
|         ui->physical_device_combo->addItem(name); | ||||
|     } | ||||
|  | ||||
|     ui->toggle_vsync_new->setEnabled(!is_powered_on); | ||||
|     ui->graphics_api_combo->setEnabled(!is_powered_on); | ||||
|     ui->physical_device_combo->setEnabled(!is_powered_on); | ||||
|     ui->toggle_async_shaders->setEnabled(!is_powered_on); | ||||
|     ui->toggle_async_present->setEnabled(!is_powered_on); | ||||
|     // Set the index to -1 to ensure the below lambda is called with setCurrentIndex | ||||
|     ui->graphics_api_combo->setCurrentIndex(-1); | ||||
|  | ||||
|     if (physical_devices.empty()) { | ||||
|         const u32 index = static_cast<u32>(Settings::GraphicsAPI::Vulkan); | ||||
|         ui->graphics_api_combo->removeItem(index); | ||||
|         ui->physical_device_combo->setVisible(false); | ||||
|         ui->spirv_shader_gen->setVisible(false); | ||||
|     } | ||||
|  | ||||
|     connect(ui->graphics_api_combo, qOverload<int>(&QComboBox::currentIndexChanged), this, | ||||
|             [this](int index) { | ||||
|                 const auto graphics_api = | ||||
| @@ -35,7 +53,9 @@ ConfigureGraphics::ConfigureGraphics(bool is_powered_on, QWidget* parent) | ||||
|         ui->toggle_disk_shader_cache->setEnabled(checked && enabled); | ||||
|     }); | ||||
|  | ||||
|     SetupPerGameUI(); | ||||
|     connect(ui->graphics_api_combo, qOverload<int>(&QComboBox::currentIndexChanged), this, | ||||
|             &ConfigureGraphics::SetPhysicalDeviceComboVisibility); | ||||
|  | ||||
|     SetConfiguration(); | ||||
| } | ||||
|  | ||||
| @@ -47,15 +67,24 @@ void ConfigureGraphics::SetConfiguration() { | ||||
|                                           !Settings::values.graphics_api.UsingGlobal()); | ||||
|         ConfigurationShared::SetPerGameSetting(ui->graphics_api_combo, | ||||
|                                                &Settings::values.graphics_api); | ||||
|         ConfigurationShared::SetHighlight(ui->physical_device_group, | ||||
|                                           !Settings::values.physical_device.UsingGlobal()); | ||||
|         ConfigurationShared::SetPerGameSetting(ui->physical_device_combo, | ||||
|                                                &Settings::values.physical_device); | ||||
|     } else { | ||||
|         ui->graphics_api_combo->setCurrentIndex( | ||||
|             static_cast<int>(Settings::values.graphics_api.GetValue())); | ||||
|         ui->physical_device_combo->setCurrentIndex( | ||||
|             static_cast<int>(Settings::values.physical_device.GetValue())); | ||||
|     } | ||||
|  | ||||
|     ui->toggle_hw_shader->setChecked(Settings::values.use_hw_shader.GetValue()); | ||||
|     ui->toggle_accurate_mul->setChecked(Settings::values.shaders_accurate_mul.GetValue()); | ||||
|     ui->toggle_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue()); | ||||
|     ui->toggle_vsync_new->setChecked(Settings::values.use_vsync_new.GetValue()); | ||||
|     ui->spirv_shader_gen->setChecked(Settings::values.spirv_shader_gen.GetValue()); | ||||
|     ui->toggle_async_shaders->setChecked(Settings::values.async_shader_compilation.GetValue()); | ||||
|     ui->toggle_async_present->setChecked(Settings::values.async_presentation.GetValue()); | ||||
|  | ||||
|     if (Settings::IsConfiguringGlobal()) { | ||||
|         ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit.GetValue()); | ||||
| @@ -65,6 +94,14 @@ void ConfigureGraphics::SetConfiguration() { | ||||
| void ConfigureGraphics::ApplyConfiguration() { | ||||
|     ConfigurationShared::ApplyPerGameSetting(&Settings::values.graphics_api, | ||||
|                                              ui->graphics_api_combo); | ||||
|     ConfigurationShared::ApplyPerGameSetting(&Settings::values.physical_device, | ||||
|                                              ui->physical_device_combo); | ||||
|     ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_shader_compilation, | ||||
|                                              ui->toggle_async_shaders, async_shader_compilation); | ||||
|     ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_presentation, | ||||
|                                              ui->toggle_async_present, async_presentation); | ||||
|     ConfigurationShared::ApplyPerGameSetting(&Settings::values.spirv_shader_gen, | ||||
|                                              ui->spirv_shader_gen, spirv_shader_gen); | ||||
|     ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_hw_shader, ui->toggle_hw_shader, | ||||
|                                              use_hw_shader); | ||||
|     ConfigurationShared::ApplyPerGameSetting(&Settings::values.shaders_accurate_mul, | ||||
| @@ -93,6 +130,11 @@ void ConfigureGraphics::SetupPerGameUI() { | ||||
|             Settings::values.use_disk_shader_cache.UsingGlobal()); | ||||
|         ui->toggle_vsync_new->setEnabled(ui->toggle_vsync_new->isEnabled() && | ||||
|                                          Settings::values.use_vsync_new.UsingGlobal()); | ||||
|         ui->toggle_async_shaders->setEnabled( | ||||
|             Settings::values.async_shader_compilation.UsingGlobal()); | ||||
|         ui->toggle_async_present->setEnabled(Settings::values.async_presentation.UsingGlobal()); | ||||
|         ui->graphics_api_combo->setEnabled(Settings::values.graphics_api.UsingGlobal()); | ||||
|         ui->physical_device_combo->setEnabled(Settings::values.physical_device.UsingGlobal()); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
| @@ -102,6 +144,10 @@ void ConfigureGraphics::SetupPerGameUI() { | ||||
|         ui->graphics_api_combo, ui->graphics_api_group, | ||||
|         static_cast<u32>(Settings::values.graphics_api.GetValue(true))); | ||||
|  | ||||
|     ConfigurationShared::SetColoredComboBox( | ||||
|         ui->physical_device_combo, ui->physical_device_group, | ||||
|         static_cast<u32>(Settings::values.physical_device.GetValue(true))); | ||||
|  | ||||
|     ConfigurationShared::SetColoredTristate(ui->toggle_hw_shader, Settings::values.use_hw_shader, | ||||
|                                             use_hw_shader); | ||||
|     ConfigurationShared::SetColoredTristate( | ||||
| @@ -111,4 +157,34 @@ void ConfigureGraphics::SetupPerGameUI() { | ||||
|                                             use_disk_shader_cache); | ||||
|     ConfigurationShared::SetColoredTristate(ui->toggle_vsync_new, Settings::values.use_vsync_new, | ||||
|                                             use_vsync_new); | ||||
|     ConfigurationShared::SetColoredTristate(ui->toggle_async_shaders, | ||||
|                                             Settings::values.async_shader_compilation, | ||||
|                                             async_shader_compilation); | ||||
|     ConfigurationShared::SetColoredTristate( | ||||
|         ui->toggle_async_present, Settings::values.async_presentation, async_presentation); | ||||
|     ConfigurationShared::SetColoredTristate(ui->spirv_shader_gen, Settings::values.spirv_shader_gen, | ||||
|                                             spirv_shader_gen); | ||||
| } | ||||
|  | ||||
| void ConfigureGraphics::SetPhysicalDeviceComboVisibility(int index) { | ||||
|     bool is_visible{}; | ||||
|  | ||||
|     // When configuring per-game the physical device combo should be | ||||
|     // shown either when the global api is used and that is Vulkan or | ||||
|     // Vulkan is set as the per-game api. | ||||
|     if (!Settings::IsConfiguringGlobal()) { | ||||
|         const auto global_graphics_api = Settings::values.graphics_api.GetValue(true); | ||||
|         const bool using_global = index == 0; | ||||
|         if (!using_global) { | ||||
|             index -= ConfigurationShared::USE_GLOBAL_OFFSET; | ||||
|         } | ||||
|         const auto graphics_api = static_cast<Settings::GraphicsAPI>(index); | ||||
|         is_visible = (using_global && global_graphics_api == Settings::GraphicsAPI::Vulkan) || | ||||
|                      graphics_api == Settings::GraphicsAPI::Vulkan; | ||||
|     } else { | ||||
|         const auto graphics_api = static_cast<Settings::GraphicsAPI>(index); | ||||
|         is_visible = graphics_api == Settings::GraphicsAPI::Vulkan; | ||||
|     } | ||||
|     ui->physical_device_group->setVisible(is_visible); | ||||
|     ui->spirv_shader_gen->setVisible(is_visible); | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,8 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
| #include <span> | ||||
| #include <QString> | ||||
| #include <QWidget> | ||||
|  | ||||
| namespace Ui { | ||||
| @@ -19,7 +21,8 @@ class ConfigureGraphics : public QWidget { | ||||
|     Q_OBJECT | ||||
|  | ||||
| public: | ||||
|     explicit ConfigureGraphics(bool is_powered_on, QWidget* parent = nullptr); | ||||
|     explicit ConfigureGraphics(std::span<const QString> physical_devices, bool is_powered_on, | ||||
|                                QWidget* parent = nullptr); | ||||
|     ~ConfigureGraphics() override; | ||||
|  | ||||
|     void ApplyConfiguration(); | ||||
| @@ -30,11 +33,15 @@ public: | ||||
|  | ||||
| private: | ||||
|     void SetupPerGameUI(); | ||||
|     void SetPhysicalDeviceComboVisibility(int index); | ||||
|  | ||||
|     ConfigurationShared::CheckState use_hw_shader; | ||||
|     ConfigurationShared::CheckState shaders_accurate_mul; | ||||
|     ConfigurationShared::CheckState use_disk_shader_cache; | ||||
|     ConfigurationShared::CheckState use_vsync_new; | ||||
|     ConfigurationShared::CheckState async_shader_compilation; | ||||
|     ConfigurationShared::CheckState async_presentation; | ||||
|     ConfigurationShared::CheckState spirv_shader_gen; | ||||
|     std::unique_ptr<Ui::ConfigureGraphics> ui; | ||||
|     QColor bg_color; | ||||
| }; | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>400</width> | ||||
|     <height>443</height> | ||||
|     <height>509</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="minimumSize"> | ||||
| @@ -63,11 +63,51 @@ | ||||
|              <string>OpenGL</string> | ||||
|             </property> | ||||
|            </item> | ||||
|            <item> | ||||
|             <property name="text"> | ||||
|              <string>Vulkan</string> | ||||
|             </property> | ||||
|            </item> | ||||
|           </widget> | ||||
|          </item> | ||||
|         </layout> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QWidget" name="physical_device_group" native="true"> | ||||
|         <layout class="QHBoxLayout" name="physical_device_group_2"> | ||||
|          <property name="leftMargin"> | ||||
|           <number>0</number> | ||||
|          </property> | ||||
|          <property name="topMargin"> | ||||
|           <number>0</number> | ||||
|          </property> | ||||
|          <property name="rightMargin"> | ||||
|           <number>0</number> | ||||
|          </property> | ||||
|          <property name="bottomMargin"> | ||||
|           <number>0</number> | ||||
|          </property> | ||||
|          <item> | ||||
|           <widget class="QLabel" name="physical_device_label"> | ||||
|            <property name="text"> | ||||
|             <string>Physical Device</string> | ||||
|            </property> | ||||
|           </widget> | ||||
|          </item> | ||||
|          <item> | ||||
|           <widget class="QComboBox" name="physical_device_combo"/> | ||||
|          </item> | ||||
|         </layout> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QCheckBox" name="spirv_shader_gen"> | ||||
|         <property name="text"> | ||||
|          <string>SPIR-V Shader Generation</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
| @@ -95,7 +135,7 @@ | ||||
|          <item> | ||||
|           <widget class="QCheckBox" name="toggle_hw_shader"> | ||||
|            <property name="toolTip"> | ||||
|             <string><html><head/><body><p>Use OpenGL to accelerate shader emulation.</p><p>Requires a relatively powerful GPU for better performance.</p></body></html></string> | ||||
|             <string><html><head/><body><p>Use the selected graphics API to accelerate shader emulation.</p><p>Requires a relatively powerful GPU for better performance.</p></body></html></string> | ||||
|            </property> | ||||
|            <property name="text"> | ||||
|             <string>Enable Hardware Shader</string> | ||||
| @@ -143,6 +183,26 @@ | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QCheckBox" name="toggle_async_shaders"> | ||||
|         <property name="toolTip"> | ||||
|          <string><html><head/><body><p>Compile shaders using background threads to avoid shader compilation stutter. Expect temporary graphical glitches</p></body></html></string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>Enable Async Shader Compilation</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QCheckBox" name="toggle_async_present"> | ||||
|         <property name="toolTip"> | ||||
|          <string><html><head/><body><p>Perform presentation on separate threads. Improves performance when using Vulkan in most games.</p></body></html></string> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>Enable Async Presentation</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
|   | ||||
| @@ -24,7 +24,7 @@ | ||||
| #include "ui_configure_per_game.h" | ||||
|  | ||||
| ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const QString& file_name, | ||||
|                                    Core::System& system_) | ||||
|                                    std::span<const QString> physical_devices, Core::System& system_) | ||||
|     : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), | ||||
|       filename{file_name.toStdString()}, title_id{title_id_}, system{system_} { | ||||
|     const auto config_file_name = title_id == 0 ? std::string(FileUtil::GetFilename(filename)) | ||||
| @@ -35,7 +35,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const QString | ||||
|     audio_tab = std::make_unique<ConfigureAudio>(is_powered_on, this); | ||||
|     general_tab = std::make_unique<ConfigureGeneral>(this); | ||||
|     enhancements_tab = std::make_unique<ConfigureEnhancements>(this); | ||||
|     graphics_tab = std::make_unique<ConfigureGraphics>(is_powered_on, this); | ||||
|     graphics_tab = std::make_unique<ConfigureGraphics>(physical_devices, is_powered_on, this); | ||||
|     system_tab = std::make_unique<ConfigureSystem>(system, this); | ||||
|     debug_tab = std::make_unique<ConfigureDebug>(is_powered_on, this); | ||||
|     cheat_tab = std::make_unique<ConfigureCheats>(system, title_id, this); | ||||
|   | ||||
| @@ -4,9 +4,11 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
| #include <span> | ||||
| #include <string> | ||||
| #include <QDialog> | ||||
| #include <QList> | ||||
| #include <QString> | ||||
| #include "citra_qt/configuration/config.h" | ||||
|  | ||||
| namespace Core { | ||||
| @@ -35,9 +37,8 @@ class ConfigurePerGame : public QDialog { | ||||
|     Q_OBJECT | ||||
|  | ||||
| public: | ||||
|     // Cannot use std::filesystem::path due to https://bugreports.qt.io/browse/QTBUG-73263 | ||||
|     explicit ConfigurePerGame(QWidget* parent, u64 title_id_, const QString& file_name, | ||||
|                               Core::System& system_); | ||||
|                               std::span<const QString> physical_devices, Core::System& system_); | ||||
|     ~ConfigurePerGame() override; | ||||
|  | ||||
|     /// Loads all button configurations to settings file | ||||
|   | ||||
| @@ -62,6 +62,7 @@ | ||||
| #include "citra_qt/uisettings.h" | ||||
| #include "citra_qt/updater/updater.h" | ||||
| #include "citra_qt/util/clickable_label.h" | ||||
| #include "citra_qt/util/vk_device_info.h" | ||||
| #include "common/arch.h" | ||||
| #include "common/common_paths.h" | ||||
| #include "common/detached_tasks.h" | ||||
| @@ -263,6 +264,14 @@ GMainWindow::GMainWindow(Core::System& system_) | ||||
|     connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor); | ||||
|     connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::OnMouseActivity); | ||||
|  | ||||
|     physical_devices = GetVulkanPhysicalDevices(); | ||||
|     if (physical_devices.empty()) { | ||||
|         QMessageBox::warning(this, tr("No Suitable Vulkan Devices Detected"), | ||||
|                              tr("Vulkan initialization failed during boot.<br/>" | ||||
|                                 "Your GPU may not support Vulkan 1.1, or you do not " | ||||
|                                 "have the latest graphics driver.")); | ||||
|     } | ||||
|  | ||||
| #if ENABLE_QT_UPDATER | ||||
|     if (UISettings::values.check_for_update_on_start) { | ||||
|         CheckForUpdates(); | ||||
| @@ -2010,7 +2019,7 @@ void GMainWindow::OnLoadState() { | ||||
| void GMainWindow::OnConfigure() { | ||||
|     game_list->SetDirectoryWatcherEnabled(false); | ||||
|     Settings::SetConfiguringGlobal(true); | ||||
|     ConfigureDialog configureDialog(this, hotkey_registry, system, | ||||
|     ConfigureDialog configureDialog(this, hotkey_registry, system, physical_devices, | ||||
|                                     !multiplayer_state->IsHostingPublicRoom()); | ||||
|     connect(&configureDialog, &ConfigureDialog::LanguageChanged, this, | ||||
|             &GMainWindow::OnLanguageChanged); | ||||
| @@ -2470,9 +2479,11 @@ void GMainWindow::ShowMouseCursor() { | ||||
| } | ||||
|  | ||||
| void GMainWindow::UpdateAPIIndicator(bool update) { | ||||
|     static std::array graphics_apis = {QStringLiteral("SOFTWARE"), QStringLiteral("OPENGL")}; | ||||
|     static std::array graphics_apis = {QStringLiteral("SOFTWARE"), QStringLiteral("OPENGL"), | ||||
|                                        QStringLiteral("VULKAN")}; | ||||
|  | ||||
|     static std::array graphics_api_colors = {QStringLiteral("#3ae400"), QStringLiteral("#00ccdd")}; | ||||
|     static std::array graphics_api_colors = {QStringLiteral("#3ae400"), QStringLiteral("#00ccdd"), | ||||
|                                              QStringLiteral("#91242a")}; | ||||
|  | ||||
|     u32 api_index = static_cast<u32>(Settings::values.graphics_api.GetValue()); | ||||
|     if (update) { | ||||
| @@ -2764,7 +2775,7 @@ void GMainWindow::OnConfigurePerGame() { | ||||
|  | ||||
| void GMainWindow::OpenPerGameConfiguration(u64 title_id, const QString& file_name) { | ||||
|     Settings::SetConfiguringGlobal(false); | ||||
|     ConfigurePerGame dialog(this, title_id, file_name, system); | ||||
|     ConfigurePerGame dialog(this, title_id, file_name, physical_devices, system); | ||||
|     const auto result = dialog.exec(); | ||||
|  | ||||
|     if (result != QDialog::Accepted) { | ||||
|   | ||||
| @@ -6,8 +6,10 @@ | ||||
|  | ||||
| #include <array> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| #include <QMainWindow> | ||||
| #include <QPushButton> | ||||
| #include <QString> | ||||
| #include <QTimer> | ||||
| #include <QTranslator> | ||||
| #include "citra_qt/compatibility_list.h" | ||||
| @@ -326,6 +328,8 @@ private: | ||||
|     // Whether game was paused due to stopping video dumping | ||||
|     bool game_paused_for_dumping = false; | ||||
|  | ||||
|     std::vector<QString> physical_devices; | ||||
|  | ||||
|     // Debugger panes | ||||
|     ProfilerWidget* profilerWidget; | ||||
|     MicroProfileDialog* microProfileDialog; | ||||
|   | ||||
							
								
								
									
										23
									
								
								src/citra_qt/util/vk_device_info.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/citra_qt/util/vk_device_info.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include "citra_qt/util/vk_device_info.h" | ||||
| #include "video_core/renderer_vulkan/vk_instance.h" | ||||
|  | ||||
| std::vector<QString> GetVulkanPhysicalDevices() { | ||||
|     std::vector<QString> result; | ||||
|     try { | ||||
|         Vulkan::Instance instance{}; | ||||
|         const auto physical_devices = instance.GetPhysicalDevices(); | ||||
|  | ||||
|         for (const vk::PhysicalDevice physical_device : physical_devices) { | ||||
|             const QString name = QString::fromUtf8(physical_device.getProperties().deviceName, -1); | ||||
|             result.push_back(name); | ||||
|         } | ||||
|     } catch (const std::runtime_error& err) { | ||||
|         LOG_ERROR(Frontend, "Error occured while querying for physical devices: {}", err.what()); | ||||
|     } | ||||
|  | ||||
|     return result; | ||||
| } | ||||
							
								
								
									
										11
									
								
								src/citra_qt/util/vk_device_info.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/citra_qt/util/vk_device_info.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <vector> | ||||
| #include <QString> | ||||
|  | ||||
| /// Returns a list of all available vulkan GPUs. | ||||
| std::vector<QString> GetVulkanPhysicalDevices(); | ||||
| @@ -12,6 +12,10 @@ | ||||
|  | ||||
| namespace Common { | ||||
|  | ||||
| DynamicLibrary::DynamicLibrary() = default; | ||||
|  | ||||
| DynamicLibrary::DynamicLibrary(void* handle_) : handle{handle_} {} | ||||
|  | ||||
| DynamicLibrary::DynamicLibrary(std::string_view name, int major, int minor) { | ||||
|     auto full_name = GetLibraryName(name, major, minor); | ||||
|     void(Load(full_name)); | ||||
|   | ||||
| @@ -11,6 +11,7 @@ namespace Common { | ||||
| class DynamicLibrary { | ||||
| public: | ||||
|     explicit DynamicLibrary(); | ||||
|     explicit DynamicLibrary(void* handle); | ||||
|     explicit DynamicLibrary(std::string_view name, int major = -1, int minor = -1); | ||||
|     ~DynamicLibrary(); | ||||
|  | ||||
|   | ||||
| @@ -37,8 +37,8 @@ static inline u64 ComputeStructHash64(const T& data) noexcept { | ||||
|  * Combines the seed parameter with the provided hash, producing a new unique hash | ||||
|  * Implementation from: http://boost.sourceforge.net/doc/html/boost/hash_combine.html | ||||
|  */ | ||||
| inline u64 HashCombine(std::size_t& seed, const u64 hash) { | ||||
|     return seed ^= hash + 0x9e3779b9 + (seed << 6) + (seed >> 2); | ||||
| inline u64 HashCombine(std::size_t seed, const u64 hash) { | ||||
|     return seed ^ (hash + 0x9e3779b9 + (seed << 6) + (seed >> 2)); | ||||
| } | ||||
|  | ||||
| template <typename T> | ||||
|   | ||||
| @@ -31,6 +31,8 @@ std::string_view GetGraphicsAPIName(GraphicsAPI api) { | ||||
|         return "Software"; | ||||
|     case GraphicsAPI::OpenGL: | ||||
|         return "OpenGL"; | ||||
|     case GraphicsAPI::Vulkan: | ||||
|         return "Vulkan"; | ||||
|     default: | ||||
|         return "Invalid"; | ||||
|     } | ||||
| @@ -72,6 +74,9 @@ void LogSettings() { | ||||
|     log_setting("Core_CPUClockPercentage", values.cpu_clock_percentage.GetValue()); | ||||
|     log_setting("Renderer_UseGLES", values.use_gles.GetValue()); | ||||
|     log_setting("Renderer_GraphicsAPI", GetGraphicsAPIName(values.graphics_api.GetValue())); | ||||
|     log_setting("Renderer_AsyncShaders", values.async_shader_compilation.GetValue()); | ||||
|     log_setting("Renderer_AsyncPresentation", values.async_presentation.GetValue()); | ||||
|     log_setting("Renderer_SpirvShaderGen", values.spirv_shader_gen.GetValue()); | ||||
|     log_setting("Renderer_Debug", values.renderer_debug.GetValue()); | ||||
|     log_setting("Renderer_UseHwShader", values.use_hw_shader.GetValue()); | ||||
|     log_setting("Renderer_ShadersAccurateMul", values.shaders_accurate_mul.GetValue()); | ||||
| @@ -159,6 +164,10 @@ void RestoreGlobalState(bool is_powered_on) { | ||||
|  | ||||
|     // Renderer | ||||
|     values.graphics_api.SetGlobal(true); | ||||
|     values.physical_device.SetGlobal(true); | ||||
|     values.spirv_shader_gen.SetGlobal(true); | ||||
|     values.async_shader_compilation.SetGlobal(true); | ||||
|     values.async_presentation.SetGlobal(true); | ||||
|     values.use_hw_shader.SetGlobal(true); | ||||
|     values.use_disk_shader_cache.SetGlobal(true); | ||||
|     values.shaders_accurate_mul.SetGlobal(true); | ||||
|   | ||||
| @@ -17,10 +17,10 @@ | ||||
|  | ||||
| namespace Settings { | ||||
|  | ||||
| constexpr u32 GraphicsAPICount = 2; | ||||
| enum class GraphicsAPI { | ||||
|     Software = 0, | ||||
|     OpenGL = 1, | ||||
|     Vulkan = 2, | ||||
| }; | ||||
|  | ||||
| enum class InitClock : u32 { | ||||
| @@ -430,12 +430,15 @@ struct Values { | ||||
|     Setting<bool> allow_plugin_loader{true, "allow_plugin_loader"}; | ||||
|  | ||||
|     // Renderer | ||||
|     SwitchableSetting<GraphicsAPI, true> graphics_api{ | ||||
|         GraphicsAPI::OpenGL, GraphicsAPI::Software, static_cast<GraphicsAPI>(GraphicsAPICount - 1), | ||||
|         "graphics_api"}; | ||||
|     SwitchableSetting<GraphicsAPI, true> graphics_api{GraphicsAPI::OpenGL, GraphicsAPI::Software, | ||||
|                                                       GraphicsAPI::Vulkan, "graphics_api"}; | ||||
|     SwitchableSetting<u32> physical_device{0, "physical_device"}; | ||||
|     Setting<bool> use_gles{false, "use_gles"}; | ||||
|     Setting<bool> renderer_debug{false, "renderer_debug"}; | ||||
|     Setting<bool> dump_command_buffers{false, "dump_command_buffers"}; | ||||
|     SwitchableSetting<bool> spirv_shader_gen{true, "spirv_shader_gen"}; | ||||
|     SwitchableSetting<bool> async_shader_compilation{false, "async_shader_compilation"}; | ||||
|     SwitchableSetting<bool> async_presentation{true, "async_presentation"}; | ||||
|     SwitchableSetting<bool> use_hw_shader{true, "use_hw_shader"}; | ||||
|     SwitchableSetting<bool> use_disk_shader_cache{true, "use_disk_shader_cache"}; | ||||
|     SwitchableSetting<bool> shaders_accurate_mul{true, "shaders_accurate_mul"}; | ||||
|   | ||||
| @@ -12,6 +12,10 @@ | ||||
| #include "core/3ds.h" | ||||
| #include "core/frontend/framebuffer_layout.h" | ||||
|  | ||||
| namespace Common { | ||||
| class DynamicLibrary; | ||||
| } | ||||
|  | ||||
| namespace Frontend { | ||||
|  | ||||
| /// Information for the Graphics Backends signifying what type of screen pointer is in | ||||
| @@ -82,6 +86,11 @@ public: | ||||
|     /// Releases (dunno if this is the "right" word) the context from the caller thread | ||||
|     virtual void DoneCurrent(){}; | ||||
|  | ||||
|     /// Gets the GPU driver library (used by Android only) | ||||
|     virtual std::shared_ptr<Common::DynamicLibrary> GetDriverLibrary() { | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     class Scoped { | ||||
|     public: | ||||
|         explicit Scoped(GraphicsContext& context_) : context(context_) { | ||||
|   | ||||
| @@ -101,18 +101,47 @@ add_library(video_core STATIC | ||||
|     renderer_software/sw_texturing.cpp | ||||
|     renderer_software/sw_texturing.h | ||||
|     renderer_vulkan/pica_to_vk.h | ||||
|     renderer_vulkan/renderer_vulkan.cpp | ||||
|     renderer_vulkan/renderer_vulkan.h | ||||
|     renderer_vulkan/vk_blit_helper.cpp | ||||
|     renderer_vulkan/vk_blit_helper.h | ||||
|     renderer_vulkan/vk_common.cpp | ||||
|     renderer_vulkan/vk_common.h | ||||
|     renderer_vulkan/vk_descriptor_pool.cpp | ||||
|     renderer_vulkan/vk_descriptor_pool.h | ||||
|     renderer_vulkan/vk_graphics_pipeline.cpp | ||||
|     renderer_vulkan/vk_graphics_pipeline.h | ||||
|     renderer_vulkan/vk_master_semaphore.cpp | ||||
|     renderer_vulkan/vk_master_semaphore.h | ||||
|     renderer_vulkan/vk_rasterizer.cpp | ||||
|     renderer_vulkan/vk_rasterizer.h | ||||
|     renderer_vulkan/vk_rasterizer_cache.cpp | ||||
|     renderer_vulkan/vk_scheduler.cpp | ||||
|     renderer_vulkan/vk_scheduler.h | ||||
|     renderer_vulkan/vk_resource_pool.cpp | ||||
|     renderer_vulkan/vk_resource_pool.h | ||||
|     renderer_vulkan/vk_instance.cpp | ||||
|     renderer_vulkan/vk_instance.h | ||||
|     renderer_vulkan/vk_pipeline_cache.cpp | ||||
|     renderer_vulkan/vk_pipeline_cache.h | ||||
|     renderer_vulkan/vk_platform.cpp | ||||
|     renderer_vulkan/vk_platform.h | ||||
|     renderer_vulkan/vk_present_window.cpp | ||||
|     renderer_vulkan/vk_present_window.h | ||||
|     renderer_vulkan/vk_renderpass_cache.cpp | ||||
|     renderer_vulkan/vk_renderpass_cache.h | ||||
|     renderer_vulkan/vk_shader_gen.cpp | ||||
|     renderer_vulkan/vk_shader_gen.h | ||||
|     renderer_vulkan/vk_shader_gen_spv.cpp | ||||
|     renderer_vulkan/vk_shader_gen_spv.h | ||||
|     renderer_vulkan/vk_shader_util.cpp | ||||
|     renderer_vulkan/vk_shader_util.h | ||||
|     renderer_vulkan/vk_stream_buffer.cpp | ||||
|     renderer_vulkan/vk_stream_buffer.h | ||||
|     renderer_vulkan/vk_swapchain.cpp | ||||
|     renderer_vulkan/vk_swapchain.h | ||||
|     renderer_vulkan/vk_texture_runtime.cpp | ||||
|     renderer_vulkan/vk_texture_runtime.h | ||||
|     shader/debug_data.h | ||||
|     shader/shader.cpp | ||||
|     shader/shader.h | ||||
|   | ||||
| @@ -12,15 +12,17 @@ layout(set = 0, binding = 2, rgba8) uniform highp writeonly image2D color; | ||||
|  | ||||
| layout(push_constant, std140) uniform ComputeInfo { | ||||
|     mediump ivec2 src_offset; | ||||
|     mediump ivec2 dst_offset; | ||||
|     mediump ivec2 extent; | ||||
| }; | ||||
|  | ||||
| void main() { | ||||
|     ivec2 tex_coord = src_offset + ivec2(gl_GlobalInvocationID.xy); | ||||
|     ivec2 src_coord = src_offset + ivec2(gl_GlobalInvocationID.xy); | ||||
|     ivec2 dst_coord = dst_offset + ivec2(gl_GlobalInvocationID.xy); | ||||
|     highp uint depth_val = | ||||
|         uint(texelFetch(depth, tex_coord, 0).x * (exp2(32.0) - 1.0)); | ||||
|     lowp uint stencil_val = texelFetch(stencil, tex_coord, 0).x; | ||||
|         uint(texelFetch(depth, src_coord, 0).x * (exp2(32.0) - 1.0)); | ||||
|     lowp uint stencil_val = texelFetch(stencil, src_coord, 0).x; | ||||
|     highp uvec4 components = | ||||
|         uvec4(stencil_val, (uvec3(depth_val) >> uvec3(24u, 16u, 8u)) & 0x000000FFu); | ||||
|     imageStore(color, tex_coord, vec4(components) / (exp2(8.0) - 1.0)); | ||||
|     imageStore(color, dst_coord, vec4(components) / (exp2(8.0) - 1.0)); | ||||
| } | ||||
|   | ||||
| @@ -14,6 +14,7 @@ layout(binding = 2) writeonly buffer OutputBuffer{ | ||||
|  | ||||
| layout(push_constant, std140) uniform ComputeInfo { | ||||
|     mediump ivec2 src_offset; | ||||
|     mediump ivec2 dst_offset; | ||||
|     mediump ivec2 extent; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -244,6 +244,11 @@ void RasterizerAccelerated::NotifyPicaRegisterChanged(u32 id) { | ||||
|         } | ||||
|         break; | ||||
|  | ||||
|     // Fragment operation mode | ||||
|     case PICA_REG_INDEX(framebuffer.output_merger.fragment_operation_mode): | ||||
|         shader_dirty = true; | ||||
|         break; | ||||
|  | ||||
|     // Alpha test | ||||
|     case PICA_REG_INDEX(framebuffer.output_merger.alpha_test): | ||||
|         SyncAlphaTest(); | ||||
| @@ -617,11 +622,10 @@ void RasterizerAccelerated::NotifyPicaRegisterChanged(u32 id) { | ||||
|     case PICA_REG_INDEX(rasterizer.clip_coef[3]): | ||||
|         SyncClipCoef(); | ||||
|         break; | ||||
|  | ||||
|     default: | ||||
|         // Forward registers that map to fixed function API features to the video backend | ||||
|         NotifyFixedFunctionPicaRegisterChanged(id); | ||||
|     } | ||||
|  | ||||
|     // Forward registers that map to fixed function API features to the video backend | ||||
|     NotifyFixedFunctionPicaRegisterChanged(id); | ||||
| } | ||||
|  | ||||
| void RasterizerAccelerated::SyncDepthScale() { | ||||
|   | ||||
| @@ -708,8 +708,8 @@ FramebufferHelper<T> RasterizerCache<T>::GetFramebufferSurfaces(bool using_color | ||||
|         fb_rect = depth_rect; | ||||
|     } | ||||
|  | ||||
|     const Surface* color_surface = color_id ? &slot_surfaces[color_id] : nullptr; | ||||
|     const Surface* depth_surface = depth_id ? &slot_surfaces[depth_id] : nullptr; | ||||
|     Surface* color_surface = color_id ? &slot_surfaces[color_id] : nullptr; | ||||
|     Surface* depth_surface = depth_id ? &slot_surfaces[depth_id] : nullptr; | ||||
|  | ||||
|     if (color_id) { | ||||
|         color_level = color_surface->LevelOf(color_params.addr); | ||||
| @@ -722,7 +722,7 @@ FramebufferHelper<T> RasterizerCache<T>::GetFramebufferSurfaces(bool using_color | ||||
|                         boost::icl::length(depth_vp_interval)); | ||||
|     } | ||||
|  | ||||
|     fb_params = FramebufferParams{ | ||||
|     const FramebufferParams fb_params = { | ||||
|         .color_id = color_id, | ||||
|         .depth_id = depth_id, | ||||
|         .color_level = color_level, | ||||
| @@ -1147,11 +1147,14 @@ bool RasterizerCache<T>::ValidateByReinterpretation(Surface& surface, SurfacePar | ||||
|         } | ||||
|         const PAddr addr = boost::icl::lower(interval); | ||||
|         const SurfaceParams copy_params = surface.FromInterval(copy_interval); | ||||
|         const TextureBlit reinterpret = { | ||||
|         const auto src_rect = src_surface.GetScaledSubRect(copy_params); | ||||
|         const auto dst_rect = surface.GetScaledSubRect(copy_params); | ||||
|         const TextureCopy reinterpret = { | ||||
|             .src_level = src_surface.LevelOf(addr), | ||||
|             .dst_level = surface.LevelOf(addr), | ||||
|             .src_rect = src_surface.GetScaledSubRect(copy_params), | ||||
|             .dst_rect = surface.GetScaledSubRect(copy_params), | ||||
|             .src_offset = {src_rect.left, src_rect.bottom}, | ||||
|             .dst_offset = {dst_rect.left, dst_rect.bottom}, | ||||
|             .extent = {src_rect.GetWidth(), src_rect.GetHeight()}, | ||||
|         }; | ||||
|         return runtime.Reinterpret(src_surface, surface, reinterpret); | ||||
|     } | ||||
| @@ -1300,11 +1303,6 @@ void RasterizerCache<T>::InvalidateRegion(PAddr addr, u32 size, SurfaceId region | ||||
|  | ||||
|     for (const SurfaceId surface_id : remove_surfaces) { | ||||
|         UnregisterSurface(surface_id); | ||||
|         if (slot_surfaces[surface_id].type != SurfaceType::Fill) { | ||||
|             sentenced.emplace_back(surface_id, frame_tick); | ||||
|         } else { | ||||
|             slot_surfaces.erase(surface_id); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -1365,7 +1363,13 @@ void RasterizerCache<T>::UnregisterSurface(SurfaceId surface_id) { | ||||
|         surfaces.erase(vector_it); | ||||
|     }); | ||||
|  | ||||
|     RemoveTextureCubeFace(surface_id); | ||||
|     if (surface.type != SurfaceType::Fill) { | ||||
|         RemoveTextureCubeFace(surface_id); | ||||
|         sentenced.emplace_back(surface_id, frame_tick); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     slot_surfaces.erase(surface_id); | ||||
| } | ||||
|  | ||||
| template <class T> | ||||
| @@ -1376,7 +1380,9 @@ void RasterizerCache<T>::UnregisterAll() { | ||||
|             UnregisterSurface(surfaces.back()); | ||||
|         } | ||||
|     } | ||||
|     texture_cube_cache.clear(); | ||||
|     runtime.Finish(); | ||||
|     frame_tick += runtime.RemoveThreshold(); | ||||
|     RunGarbageCollector(); | ||||
| } | ||||
|  | ||||
| template <class T> | ||||
|   | ||||
| @@ -26,7 +26,7 @@ using DiskResourceLoadCallback = std::function<void(LoadCallbackStage, std::size | ||||
|  | ||||
| class RasterizerInterface { | ||||
| public: | ||||
|     virtual ~RasterizerInterface() {} | ||||
|     virtual ~RasterizerInterface() = default; | ||||
|  | ||||
|     /// Queues the primitive formed by the given vertices for rendering | ||||
|     virtual void AddTriangle(const Pica::Shader::OutputVertex& v0, | ||||
|   | ||||
| @@ -159,6 +159,7 @@ struct FramebufferRegs { | ||||
|         } stencil_test; | ||||
|  | ||||
|         union { | ||||
|             u32 depth_color_mask; | ||||
|             BitField<0, 1, u32> depth_test_enable; | ||||
|             BitField<4, 3, CompareFunc> depth_test_func; | ||||
|             BitField<8, 1, u32> red_enable; | ||||
|   | ||||
| @@ -63,6 +63,9 @@ public: | ||||
|     /// Synchronizes fixed function renderer state | ||||
|     virtual void Sync() {} | ||||
|  | ||||
|     /// This is called to notify the rendering backend of a surface change | ||||
|     virtual void NotifySurfaceChanged() {} | ||||
|  | ||||
|     /// Returns the resolution scale factor relative to the native 3DS screen resolution | ||||
|     u32 GetResolutionScaleFactor(); | ||||
|  | ||||
|   | ||||
| @@ -84,7 +84,7 @@ BlitHelper::BlitHelper(const Driver& driver_) | ||||
| BlitHelper::~BlitHelper() = default; | ||||
|  | ||||
| bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest, | ||||
|                                       const VideoCore::TextureBlit& blit) { | ||||
|                                       const VideoCore::TextureCopy& copy) { | ||||
|     OpenGLState prev_state = OpenGLState::GetCurState(); | ||||
|     SCOPE_EXIT({ prev_state.Apply(); }); | ||||
|  | ||||
| @@ -99,32 +99,35 @@ bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest, | ||||
|                       1); | ||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | ||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | ||||
|     } else if (blit.src_rect.top > temp_rect.top || blit.src_rect.right > temp_rect.right) { | ||||
|     } else if (copy.extent.width > temp_extent.width || copy.extent.height > temp_extent.height) { | ||||
|         temp_extent = copy.extent; | ||||
|         temp_tex.Release(); | ||||
|         temp_tex.Create(); | ||||
|         state.texture_units[1].texture_2d = temp_tex.handle; | ||||
|         state.Apply(); | ||||
|         glActiveTexture(GL_TEXTURE1); | ||||
|         glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH24_STENCIL8, blit.src_rect.right, | ||||
|                        blit.src_rect.top); | ||||
|         glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH24_STENCIL8, temp_extent.width, | ||||
|                        temp_extent.height); | ||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | ||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | ||||
|         temp_rect = blit.src_rect; | ||||
|     } | ||||
|     state.texture_units[1].texture_2d = temp_tex.handle; | ||||
|     state.Apply(); | ||||
|  | ||||
|     glActiveTexture(GL_TEXTURE1); | ||||
|     if (!use_texture_view) { | ||||
|         glCopyImageSubData(source.Handle(), GL_TEXTURE_2D, 0, blit.src_rect.left, | ||||
|                            blit.src_rect.bottom, 0, temp_tex.handle, GL_TEXTURE_2D, 0, | ||||
|                            blit.src_rect.left, blit.src_rect.bottom, 0, blit.src_rect.GetWidth(), | ||||
|                            blit.src_rect.GetHeight(), 1); | ||||
|         glCopyImageSubData(source.Handle(), GL_TEXTURE_2D, 0, copy.src_offset.x, copy.src_offset.y, | ||||
|                            0, temp_tex.handle, GL_TEXTURE_2D, 0, copy.src_offset.x, | ||||
|                            copy.src_offset.y, 0, copy.extent.width, copy.extent.height, 1); | ||||
|     } | ||||
|     glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX); | ||||
|  | ||||
|     SetParams(d24s8_to_rgba8, source.RealExtent(), blit.src_rect); | ||||
|     Draw(d24s8_to_rgba8, dest.Handle(), draw_fbo.handle, 0, blit.dst_rect); | ||||
|     const Common::Rectangle src_rect{copy.src_offset.x, copy.src_offset.y + copy.extent.height, | ||||
|                                      copy.src_offset.x + copy.extent.width, copy.src_offset.x}; | ||||
|     const Common::Rectangle dst_rect{copy.dst_offset.x, copy.dst_offset.y + copy.extent.height, | ||||
|                                      copy.dst_offset.x + copy.extent.width, copy.dst_offset.x}; | ||||
|     SetParams(d24s8_to_rgba8, source.RealExtent(), src_rect); | ||||
|     Draw(d24s8_to_rgba8, dest.Handle(), draw_fbo.handle, 0, dst_rect); | ||||
|  | ||||
|     if (use_texture_view) { | ||||
|         temp_tex.Release(); | ||||
| @@ -138,14 +141,18 @@ bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest, | ||||
| } | ||||
|  | ||||
| bool BlitHelper::ConvertRGBA4ToRGB5A1(Surface& source, Surface& dest, | ||||
|                                       const VideoCore::TextureBlit& blit) { | ||||
|                                       const VideoCore::TextureCopy& copy) { | ||||
|     OpenGLState prev_state = OpenGLState::GetCurState(); | ||||
|     SCOPE_EXIT({ prev_state.Apply(); }); | ||||
|  | ||||
|     state.texture_units[0].texture_2d = source.Handle(); | ||||
|  | ||||
|     SetParams(rgba4_to_rgb5a1, source.RealExtent(), blit.src_rect); | ||||
|     Draw(rgba4_to_rgb5a1, dest.Handle(), draw_fbo.handle, 0, blit.dst_rect); | ||||
|     const Common::Rectangle src_rect{copy.src_offset.x, copy.src_offset.y + copy.extent.height, | ||||
|                                      copy.src_offset.x + copy.extent.width, copy.src_offset.x}; | ||||
|     const Common::Rectangle dst_rect{copy.dst_offset.x, copy.dst_offset.y + copy.extent.height, | ||||
|                                      copy.dst_offset.x + copy.extent.width, copy.dst_offset.x}; | ||||
|     SetParams(rgba4_to_rgb5a1, source.RealExtent(), src_rect); | ||||
|     Draw(rgba4_to_rgb5a1, dest.Handle(), draw_fbo.handle, 0, dst_rect); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|   | ||||
| @@ -5,12 +5,14 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "common/math_util.h" | ||||
| #include "video_core/rasterizer_cache/utils.h" | ||||
| #include "video_core/renderer_opengl/gl_resource_manager.h" | ||||
| #include "video_core/renderer_opengl/gl_state.h" | ||||
|  | ||||
| namespace VideoCore { | ||||
| struct Extent; | ||||
| struct TextureBlit; | ||||
| struct TextureCopy; | ||||
| } // namespace VideoCore | ||||
|  | ||||
| namespace OpenGL { | ||||
| @@ -25,9 +27,9 @@ public: | ||||
|  | ||||
|     bool Filter(Surface& surface, const VideoCore::TextureBlit& blit); | ||||
|  | ||||
|     bool ConvertDS24S8ToRGBA8(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit); | ||||
|     bool ConvertDS24S8ToRGBA8(Surface& source, Surface& dest, const VideoCore::TextureCopy& copy); | ||||
|  | ||||
|     bool ConvertRGBA4ToRGB5A1(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit); | ||||
|     bool ConvertRGBA4ToRGB5A1(Surface& source, Surface& dest, const VideoCore::TextureCopy& copy); | ||||
|  | ||||
| private: | ||||
|     void FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& blit); | ||||
| @@ -68,7 +70,7 @@ private: | ||||
|     OGLProgram rgba4_to_rgb5a1; | ||||
|  | ||||
|     OGLTexture temp_tex; | ||||
|     Common::Rectangle<u32> temp_rect{}; | ||||
|     VideoCore::Extent temp_extent{}; | ||||
|     bool use_texture_view{true}; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include "common/logging/log.h" | ||||
| #include "video_core/renderer_opengl/gl_state.h" | ||||
| #include "video_core/renderer_opengl/gl_texture_mailbox.h" | ||||
|  | ||||
|   | ||||
| @@ -170,14 +170,14 @@ const FormatTuple& TextureRuntime::GetFormatTuple(VideoCore::CustomPixelFormat p | ||||
| } | ||||
|  | ||||
| bool TextureRuntime::Reinterpret(Surface& source, Surface& dest, | ||||
|                                  const VideoCore::TextureBlit& blit) { | ||||
|                                  const VideoCore::TextureCopy& copy) { | ||||
|     const PixelFormat src_format = source.pixel_format; | ||||
|     const PixelFormat dst_format = dest.pixel_format; | ||||
|     ASSERT_MSG(src_format != dst_format, "Reinterpretation with the same format is invalid"); | ||||
|     if (src_format == PixelFormat::D24S8 && dst_format == PixelFormat::RGBA8) { | ||||
|         blit_helper.ConvertDS24S8ToRGBA8(source, dest, blit); | ||||
|         blit_helper.ConvertDS24S8ToRGBA8(source, dest, copy); | ||||
|     } else if (src_format == PixelFormat::RGBA4 && dst_format == PixelFormat::RGB5A1) { | ||||
|         blit_helper.ConvertRGBA4ToRGB5A1(source, dest, blit); | ||||
|         blit_helper.ConvertRGBA4ToRGB5A1(source, dest, copy); | ||||
|     } else { | ||||
|         LOG_WARNING(Render_OpenGL, "Unimplemented reinterpretation {} -> {}", | ||||
|                     VideoCore::PixelFormatAsString(src_format), | ||||
|   | ||||
| @@ -45,6 +45,9 @@ public: | ||||
|     /// Returns the removal threshold ticks for the garbage collector | ||||
|     u32 RemoveThreshold(); | ||||
|  | ||||
|     /// Submits and waits for current GPU work. | ||||
|     void Finish() {} | ||||
|  | ||||
|     /// Returns true if the provided pixel format cannot be used natively by the runtime. | ||||
|     bool NeedsConversion(VideoCore::PixelFormat pixel_format) const; | ||||
|  | ||||
| @@ -56,7 +59,7 @@ public: | ||||
|     const FormatTuple& GetFormatTuple(VideoCore::CustomPixelFormat pixel_format); | ||||
|  | ||||
|     /// Attempts to reinterpret a rectangle of source to another rectangle of dest | ||||
|     bool Reinterpret(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit); | ||||
|     bool Reinterpret(Surface& source, Surface& dest, const VideoCore::TextureCopy& copy); | ||||
|  | ||||
|     /// Fills the rectangle of the texture with the clear value provided | ||||
|     void ClearTexture(Surface& surface, const VideoCore::TextureClear& clear); | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
|  | ||||
| #include "common/logging/log.h" | ||||
| #include "core/core.h" | ||||
| #include "core/telemetry_session.h" | ||||
| #include "video_core/regs.h" | ||||
| #include "video_core/renderer_vulkan/vk_common.h" | ||||
|  | ||||
| @@ -172,7 +173,10 @@ inline vk::PrimitiveTopology PrimitiveTopology(Pica::PipelineRegs::TriangleTopol | ||||
|         return vk::PrimitiveTopology::eTriangleList; | ||||
|     case Pica::PipelineRegs::TriangleTopology::Strip: | ||||
|         return vk::PrimitiveTopology::eTriangleStrip; | ||||
|     default: | ||||
|         UNREACHABLE_MSG("Unknown triangle topology {}", topology); | ||||
|     } | ||||
|     return vk::PrimitiveTopology::eTriangleList; | ||||
| } | ||||
|  | ||||
| inline vk::CullModeFlags CullMode(Pica::RasterizerRegs::CullMode mode) { | ||||
| @@ -182,7 +186,10 @@ inline vk::CullModeFlags CullMode(Pica::RasterizerRegs::CullMode mode) { | ||||
|     case Pica::RasterizerRegs::CullMode::KeepClockWise: | ||||
|     case Pica::RasterizerRegs::CullMode::KeepCounterClockWise: | ||||
|         return vk::CullModeFlagBits::eBack; | ||||
|     default: | ||||
|         UNREACHABLE_MSG("Unknown cull mode {}", mode); | ||||
|     } | ||||
|     return vk::CullModeFlagBits::eNone; | ||||
| } | ||||
|  | ||||
| inline vk::FrontFace FrontFace(Pica::RasterizerRegs::CullMode mode) { | ||||
| @@ -192,7 +199,16 @@ inline vk::FrontFace FrontFace(Pica::RasterizerRegs::CullMode mode) { | ||||
|         return vk::FrontFace::eCounterClockwise; | ||||
|     case Pica::RasterizerRegs::CullMode::KeepCounterClockWise: | ||||
|         return vk::FrontFace::eClockwise; | ||||
|     default: | ||||
|         UNREACHABLE_MSG("Unknown cull mode {}", mode); | ||||
|     } | ||||
|     return vk::FrontFace::eClockwise; | ||||
| } | ||||
|  | ||||
| inline Common::Vec4f ColorRGBA8(const u32 color) { | ||||
|     const auto rgba = | ||||
|         Common::Vec4u{color >> 0 & 0xFF, color >> 8 & 0xFF, color >> 16 & 0xFF, color >> 24 & 0xFF}; | ||||
|     return rgba / 255.0f; | ||||
| } | ||||
|  | ||||
| } // namespace PicaToVK | ||||
|   | ||||
							
								
								
									
										1112
									
								
								src/video_core/renderer_vulkan/renderer_vulkan.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1112
									
								
								src/video_core/renderer_vulkan/renderer_vulkan.cpp
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										139
									
								
								src/video_core/renderer_vulkan/renderer_vulkan.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								src/video_core/renderer_vulkan/renderer_vulkan.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <condition_variable> | ||||
| #include <mutex> | ||||
| #include "common/common_types.h" | ||||
| #include "common/math_util.h" | ||||
| #include "core/hw/gpu.h" | ||||
| #include "video_core/renderer_base.h" | ||||
| #include "video_core/renderer_vulkan/vk_descriptor_pool.h" | ||||
| #include "video_core/renderer_vulkan/vk_instance.h" | ||||
| #include "video_core/renderer_vulkan/vk_present_window.h" | ||||
| #include "video_core/renderer_vulkan/vk_rasterizer.h" | ||||
| #include "video_core/renderer_vulkan/vk_renderpass_cache.h" | ||||
| #include "video_core/renderer_vulkan/vk_scheduler.h" | ||||
| #include "video_core/renderer_vulkan/vk_swapchain.h" | ||||
|  | ||||
| namespace Core { | ||||
| class System; | ||||
| class TelemetrySession; | ||||
| } // namespace Core | ||||
|  | ||||
| namespace Memory { | ||||
| class MemorySystem; | ||||
| } | ||||
|  | ||||
| namespace Layout { | ||||
| struct FramebufferLayout; | ||||
| } | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| struct TextureInfo { | ||||
|     u32 width; | ||||
|     u32 height; | ||||
|     GPU::Regs::PixelFormat format; | ||||
|     vk::Image image; | ||||
|     vk::ImageView image_view; | ||||
|     VmaAllocation allocation; | ||||
| }; | ||||
|  | ||||
| struct ScreenInfo { | ||||
|     TextureInfo texture; | ||||
|     Common::Rectangle<f32> texcoords; | ||||
|     vk::ImageView image_view; | ||||
| }; | ||||
|  | ||||
| struct PresentUniformData { | ||||
|     std::array<f32, 4 * 4> modelview; | ||||
|     Common::Vec4f i_resolution; | ||||
|     Common::Vec4f o_resolution; | ||||
|     int screen_id_l = 0; | ||||
|     int screen_id_r = 0; | ||||
|     int layer = 0; | ||||
|     int reverse_interlaced = 0; | ||||
| }; | ||||
| static_assert(sizeof(PresentUniformData) == 112, | ||||
|               "PresentUniformData does not structure in shader!"); | ||||
|  | ||||
| class RendererVulkan : public VideoCore::RendererBase { | ||||
|     static constexpr std::size_t PRESENT_PIPELINES = 3; | ||||
|  | ||||
| public: | ||||
|     explicit RendererVulkan(Core::System& system, Frontend::EmuWindow& window, | ||||
|                             Frontend::EmuWindow* secondary_window); | ||||
|     ~RendererVulkan() override; | ||||
|  | ||||
|     [[nodiscard]] VideoCore::RasterizerInterface* Rasterizer() override { | ||||
|         return &rasterizer; | ||||
|     } | ||||
|  | ||||
|     void NotifySurfaceChanged() override { | ||||
|         main_window.NotifySurfaceChanged(); | ||||
|     } | ||||
|  | ||||
|     void SwapBuffers() override; | ||||
|     void TryPresent(int timeout_ms, bool is_secondary) override {} | ||||
|     void Sync() override; | ||||
|  | ||||
| private: | ||||
|     void ReportDriver() const; | ||||
|     void ReloadPipeline(); | ||||
|     void CompileShaders(); | ||||
|     void BuildLayouts(); | ||||
|     void BuildPipelines(); | ||||
|     void ConfigureFramebufferTexture(TextureInfo& texture, | ||||
|                                      const GPU::Regs::FramebufferConfig& framebuffer); | ||||
|     void ConfigureRenderPipeline(); | ||||
|     void PrepareRendertarget(); | ||||
|     void RenderScreenshot(); | ||||
|     void PrepareDraw(Frame* frame, const Layout::FramebufferLayout& layout); | ||||
|     void RenderToWindow(PresentWindow& window, const Layout::FramebufferLayout& layout, | ||||
|                         bool flipped); | ||||
|  | ||||
|     void DrawScreens(Frame* frame, const Layout::FramebufferLayout& layout, bool flipped); | ||||
|     void DrawBottomScreen(const Layout::FramebufferLayout& layout, | ||||
|                           const Common::Rectangle<u32>& bottom_screen); | ||||
|     void DrawTopScreen(const Layout::FramebufferLayout& layout, | ||||
|                        const Common::Rectangle<u32>& top_screen); | ||||
|     void DrawSingleScreen(u32 screen_id, float x, float y, float w, float h, | ||||
|                           Layout::DisplayOrientation orientation); | ||||
|     void DrawSingleScreenStereo(u32 screen_id_l, u32 screen_id_r, float x, float y, float w, | ||||
|                                 float h, Layout::DisplayOrientation orientation); | ||||
|     void LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer, | ||||
|                             ScreenInfo& screen_info, bool right_eye); | ||||
|     void LoadColorToActiveVkTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture); | ||||
|  | ||||
| private: | ||||
|     Memory::MemorySystem& memory; | ||||
|     Core::TelemetrySession& telemetry_session; | ||||
|  | ||||
|     Instance instance; | ||||
|     Scheduler scheduler; | ||||
|     RenderpassCache renderpass_cache; | ||||
|     DescriptorPool pool; | ||||
|     PresentWindow main_window; | ||||
|     StreamBuffer vertex_buffer; | ||||
|     RasterizerVulkan rasterizer; | ||||
|     std::unique_ptr<PresentWindow> second_window; | ||||
|  | ||||
|     vk::UniquePipelineLayout present_pipeline_layout; | ||||
|     DescriptorSetProvider present_set_provider; | ||||
|     std::array<vk::Pipeline, PRESENT_PIPELINES> present_pipelines; | ||||
|     std::array<vk::ShaderModule, PRESENT_PIPELINES> present_shaders; | ||||
|     std::array<vk::Sampler, 2> present_samplers; | ||||
|     vk::ShaderModule present_vertex_shader; | ||||
|     u32 current_pipeline = 0; | ||||
|  | ||||
|     std::array<ScreenInfo, 3> screen_infos{}; | ||||
|     std::array<DescriptorData, 3> present_textures{}; | ||||
|     PresentUniformData draw_info{}; | ||||
|     vk::ClearColorValue clear_color{}; | ||||
| }; | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										554
									
								
								src/video_core/renderer_vulkan/vk_blit_helper.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										554
									
								
								src/video_core/renderer_vulkan/vk_blit_helper.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,554 @@ | ||||
| // Copyright 2022 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include "common/vector_math.h" | ||||
| #include "video_core/renderer_vulkan/vk_blit_helper.h" | ||||
| #include "video_core/renderer_vulkan/vk_instance.h" | ||||
| #include "video_core/renderer_vulkan/vk_renderpass_cache.h" | ||||
| #include "video_core/renderer_vulkan/vk_scheduler.h" | ||||
| #include "video_core/renderer_vulkan/vk_shader_util.h" | ||||
| #include "video_core/renderer_vulkan/vk_texture_runtime.h" | ||||
|  | ||||
| #include "video_core/host_shaders/format_reinterpreter/vulkan_d24s8_to_rgba8_comp_spv.h" | ||||
| #include "video_core/host_shaders/full_screen_triangle_vert_spv.h" | ||||
| #include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h" | ||||
| #include "video_core/host_shaders/vulkan_depth_to_buffer_comp_spv.h" | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| using VideoCore::PixelFormat; | ||||
|  | ||||
| namespace { | ||||
| struct PushConstants { | ||||
|     std::array<float, 2> tex_scale; | ||||
|     std::array<float, 2> tex_offset; | ||||
| }; | ||||
|  | ||||
| struct ComputeInfo { | ||||
|     Common::Vec2i src_offset; | ||||
|     Common::Vec2i dst_offset; | ||||
|     Common::Vec2i src_extent; | ||||
| }; | ||||
|  | ||||
| inline constexpr vk::PushConstantRange COMPUTE_PUSH_CONSTANT_RANGE{ | ||||
|     .stageFlags = vk::ShaderStageFlagBits::eCompute, | ||||
|     .offset = 0, | ||||
|     .size = sizeof(ComputeInfo), | ||||
| }; | ||||
|  | ||||
| constexpr std::array<vk::DescriptorSetLayoutBinding, 3> COMPUTE_BINDINGS = {{ | ||||
|     {0, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eCompute}, | ||||
|     {1, vk::DescriptorType::eSampledImage, 1, vk::ShaderStageFlagBits::eCompute}, | ||||
|     {2, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eCompute}, | ||||
| }}; | ||||
|  | ||||
| constexpr std::array<vk::DescriptorSetLayoutBinding, 3> COMPUTE_BUFFER_BINDINGS = {{ | ||||
|     {0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eCompute}, | ||||
|     {1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eCompute}, | ||||
|     {2, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute}, | ||||
| }}; | ||||
|  | ||||
| constexpr std::array<vk::DescriptorSetLayoutBinding, 2> TWO_TEXTURES_BINDINGS = {{ | ||||
|     {0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, | ||||
|     {1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, | ||||
| }}; | ||||
|  | ||||
| inline constexpr vk::PushConstantRange PUSH_CONSTANT_RANGE{ | ||||
|     .stageFlags = vk::ShaderStageFlagBits::eVertex, | ||||
|     .offset = 0, | ||||
|     .size = sizeof(PushConstants), | ||||
| }; | ||||
| constexpr vk::PipelineVertexInputStateCreateInfo PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO{ | ||||
|     .vertexBindingDescriptionCount = 0, | ||||
|     .pVertexBindingDescriptions = nullptr, | ||||
|     .vertexAttributeDescriptionCount = 0, | ||||
|     .pVertexAttributeDescriptions = nullptr, | ||||
| }; | ||||
| constexpr vk::PipelineInputAssemblyStateCreateInfo PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO{ | ||||
|     .topology = vk::PrimitiveTopology::eTriangleList, | ||||
|     .primitiveRestartEnable = VK_FALSE, | ||||
| }; | ||||
| constexpr vk::PipelineViewportStateCreateInfo PIPELINE_VIEWPORT_STATE_CREATE_INFO{ | ||||
|     .viewportCount = 1, | ||||
|     .pViewports = nullptr, | ||||
|     .scissorCount = 1, | ||||
|     .pScissors = nullptr, | ||||
| }; | ||||
| constexpr vk::PipelineRasterizationStateCreateInfo PIPELINE_RASTERIZATION_STATE_CREATE_INFO{ | ||||
|     .depthClampEnable = VK_FALSE, | ||||
|     .rasterizerDiscardEnable = VK_FALSE, | ||||
|     .polygonMode = vk::PolygonMode::eFill, | ||||
|     .cullMode = vk::CullModeFlagBits::eBack, | ||||
|     .frontFace = vk::FrontFace::eClockwise, | ||||
|     .depthBiasEnable = VK_FALSE, | ||||
|     .depthBiasConstantFactor = 0.0f, | ||||
|     .depthBiasClamp = 0.0f, | ||||
|     .depthBiasSlopeFactor = 0.0f, | ||||
|     .lineWidth = 1.0f, | ||||
| }; | ||||
| constexpr vk::PipelineMultisampleStateCreateInfo PIPELINE_MULTISAMPLE_STATE_CREATE_INFO{ | ||||
|     .rasterizationSamples = vk::SampleCountFlagBits::e1, | ||||
|     .sampleShadingEnable = VK_FALSE, | ||||
|     .minSampleShading = 0.0f, | ||||
|     .pSampleMask = nullptr, | ||||
|     .alphaToCoverageEnable = VK_FALSE, | ||||
|     .alphaToOneEnable = VK_FALSE, | ||||
| }; | ||||
| constexpr std::array DYNAMIC_STATES{ | ||||
|     vk::DynamicState::eViewport, | ||||
|     vk::DynamicState::eScissor, | ||||
| }; | ||||
| constexpr vk::PipelineDynamicStateCreateInfo PIPELINE_DYNAMIC_STATE_CREATE_INFO{ | ||||
|     .dynamicStateCount = static_cast<u32>(DYNAMIC_STATES.size()), | ||||
|     .pDynamicStates = DYNAMIC_STATES.data(), | ||||
| }; | ||||
| constexpr vk::PipelineColorBlendStateCreateInfo PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO{ | ||||
|     .logicOpEnable = VK_FALSE, | ||||
|     .logicOp = vk::LogicOp::eClear, | ||||
|     .attachmentCount = 0, | ||||
|     .pAttachments = nullptr, | ||||
|     .blendConstants = std::array{0.0f, 0.0f, 0.0f, 0.0f}, | ||||
| }; | ||||
| constexpr vk::PipelineDepthStencilStateCreateInfo PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO{ | ||||
|     .depthTestEnable = VK_TRUE, | ||||
|     .depthWriteEnable = VK_TRUE, | ||||
|     .depthCompareOp = vk::CompareOp::eAlways, | ||||
|     .depthBoundsTestEnable = VK_FALSE, | ||||
|     .stencilTestEnable = VK_FALSE, | ||||
|     .front = vk::StencilOpState{}, | ||||
|     .back = vk::StencilOpState{}, | ||||
|     .minDepthBounds = 0.0f, | ||||
|     .maxDepthBounds = 0.0f, | ||||
| }; | ||||
|  | ||||
| template <vk::Filter filter> | ||||
| inline constexpr vk::SamplerCreateInfo SAMPLER_CREATE_INFO{ | ||||
|     .magFilter = filter, | ||||
|     .minFilter = filter, | ||||
|     .mipmapMode = vk::SamplerMipmapMode::eNearest, | ||||
|     .addressModeU = vk::SamplerAddressMode::eClampToBorder, | ||||
|     .addressModeV = vk::SamplerAddressMode::eClampToBorder, | ||||
|     .addressModeW = vk::SamplerAddressMode::eClampToBorder, | ||||
|     .mipLodBias = 0.0f, | ||||
|     .anisotropyEnable = VK_FALSE, | ||||
|     .maxAnisotropy = 0.0f, | ||||
|     .compareEnable = VK_FALSE, | ||||
|     .compareOp = vk::CompareOp::eNever, | ||||
|     .minLod = 0.0f, | ||||
|     .maxLod = 0.0f, | ||||
|     .borderColor = vk::BorderColor::eFloatOpaqueWhite, | ||||
|     .unnormalizedCoordinates = VK_FALSE, | ||||
| }; | ||||
|  | ||||
| constexpr vk::PipelineLayoutCreateInfo PipelineLayoutCreateInfo( | ||||
|     const vk::DescriptorSetLayout* set_layout, bool compute = false) { | ||||
|     return vk::PipelineLayoutCreateInfo{ | ||||
|         .setLayoutCount = 1, | ||||
|         .pSetLayouts = set_layout, | ||||
|         .pushConstantRangeCount = 1, | ||||
|         .pPushConstantRanges = (compute ? &COMPUTE_PUSH_CONSTANT_RANGE : &PUSH_CONSTANT_RANGE), | ||||
|     }; | ||||
| } | ||||
|  | ||||
| constexpr std::array<vk::PipelineShaderStageCreateInfo, 2> MakeStages( | ||||
|     vk::ShaderModule vertex_shader, vk::ShaderModule fragment_shader) { | ||||
|     return std::array{ | ||||
|         vk::PipelineShaderStageCreateInfo{ | ||||
|             .stage = vk::ShaderStageFlagBits::eVertex, | ||||
|             .module = vertex_shader, | ||||
|             .pName = "main", | ||||
|         }, | ||||
|         vk::PipelineShaderStageCreateInfo{ | ||||
|             .stage = vk::ShaderStageFlagBits::eFragment, | ||||
|             .module = fragment_shader, | ||||
|             .pName = "main", | ||||
|         }, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| constexpr vk::PipelineShaderStageCreateInfo MakeStages(vk::ShaderModule compute_shader) { | ||||
|     return vk::PipelineShaderStageCreateInfo{ | ||||
|         .stage = vk::ShaderStageFlagBits::eCompute, | ||||
|         .module = compute_shader, | ||||
|         .pName = "main", | ||||
|     }; | ||||
| } | ||||
|  | ||||
| } // Anonymous namespace | ||||
|  | ||||
| BlitHelper::BlitHelper(const Instance& instance_, Scheduler& scheduler_, DescriptorPool& pool, | ||||
|                        RenderpassCache& renderpass_cache_) | ||||
|     : instance{instance_}, scheduler{scheduler_}, renderpass_cache{renderpass_cache_}, | ||||
|       device{instance.GetDevice()}, compute_provider{instance, pool, COMPUTE_BINDINGS}, | ||||
|       compute_buffer_provider{instance, pool, COMPUTE_BUFFER_BINDINGS}, | ||||
|       two_textures_provider{instance, pool, TWO_TEXTURES_BINDINGS}, | ||||
|       compute_pipeline_layout{ | ||||
|           device.createPipelineLayout(PipelineLayoutCreateInfo(&compute_provider.Layout(), true))}, | ||||
|       compute_buffer_pipeline_layout{device.createPipelineLayout( | ||||
|           PipelineLayoutCreateInfo(&compute_buffer_provider.Layout(), true))}, | ||||
|       two_textures_pipeline_layout{ | ||||
|           device.createPipelineLayout(PipelineLayoutCreateInfo(&two_textures_provider.Layout()))}, | ||||
|       full_screen_vert{CompileSPV(FULL_SCREEN_TRIANGLE_VERT_SPV, device)}, | ||||
|       d24s8_to_rgba8_comp{CompileSPV(VULKAN_D24S8_TO_RGBA8_COMP_SPV, device)}, | ||||
|       depth_to_buffer_comp{CompileSPV(VULKAN_DEPTH_TO_BUFFER_COMP_SPV, device)}, | ||||
|       blit_depth_stencil_frag{CompileSPV(VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV, device)}, | ||||
|       d24s8_to_rgba8_pipeline{MakeComputePipeline(d24s8_to_rgba8_comp, compute_pipeline_layout)}, | ||||
|       depth_to_buffer_pipeline{ | ||||
|           MakeComputePipeline(depth_to_buffer_comp, compute_buffer_pipeline_layout)}, | ||||
|       depth_blit_pipeline{MakeDepthStencilBlitPipeline()}, | ||||
|       linear_sampler{device.createSampler(SAMPLER_CREATE_INFO<vk::Filter::eLinear>)}, | ||||
|       nearest_sampler{device.createSampler(SAMPLER_CREATE_INFO<vk::Filter::eNearest>)} {} | ||||
|  | ||||
| BlitHelper::~BlitHelper() { | ||||
|     device.destroyPipelineLayout(compute_pipeline_layout); | ||||
|     device.destroyPipelineLayout(compute_buffer_pipeline_layout); | ||||
|     device.destroyPipelineLayout(two_textures_pipeline_layout); | ||||
|     device.destroyShaderModule(full_screen_vert); | ||||
|     device.destroyShaderModule(d24s8_to_rgba8_comp); | ||||
|     device.destroyShaderModule(depth_to_buffer_comp); | ||||
|     device.destroyShaderModule(blit_depth_stencil_frag); | ||||
|     device.destroyPipeline(depth_to_buffer_pipeline); | ||||
|     device.destroyPipeline(d24s8_to_rgba8_pipeline); | ||||
|     device.destroyPipeline(depth_blit_pipeline); | ||||
|     device.destroySampler(linear_sampler); | ||||
|     device.destroySampler(nearest_sampler); | ||||
| } | ||||
|  | ||||
| void BindBlitState(vk::CommandBuffer cmdbuf, vk::PipelineLayout layout, | ||||
|                    const VideoCore::TextureBlit& blit) { | ||||
|     const vk::Offset2D offset{ | ||||
|         .x = std::min<s32>(blit.dst_rect.left, blit.dst_rect.right), | ||||
|         .y = std::min<s32>(blit.dst_rect.bottom, blit.dst_rect.top), | ||||
|     }; | ||||
|     const vk::Extent2D extent{ | ||||
|         .width = blit.dst_rect.GetWidth(), | ||||
|         .height = blit.dst_rect.GetHeight(), | ||||
|     }; | ||||
|     const vk::Viewport viewport{ | ||||
|         .x = static_cast<float>(offset.x), | ||||
|         .y = static_cast<float>(offset.y), | ||||
|         .width = static_cast<float>(extent.width), | ||||
|         .height = static_cast<float>(extent.height), | ||||
|         .minDepth = 0.0f, | ||||
|         .maxDepth = 1.0f, | ||||
|     }; | ||||
|     const vk::Rect2D scissor{ | ||||
|         .offset = offset, | ||||
|         .extent = extent, | ||||
|     }; | ||||
|     const float scale_x = static_cast<float>(blit.src_rect.GetWidth()); | ||||
|     const float scale_y = static_cast<float>(blit.src_rect.GetHeight()); | ||||
|     const PushConstants push_constants{ | ||||
|         .tex_scale = {scale_x, scale_y}, | ||||
|         .tex_offset = {static_cast<float>(blit.src_rect.left), | ||||
|                        static_cast<float>(blit.src_rect.bottom)}, | ||||
|     }; | ||||
|     cmdbuf.setViewport(0, viewport); | ||||
|     cmdbuf.setScissor(0, scissor); | ||||
|     cmdbuf.pushConstants(layout, vk::ShaderStageFlagBits::eVertex, 0, sizeof(push_constants), | ||||
|                          &push_constants); | ||||
| } | ||||
|  | ||||
| bool BlitHelper::BlitDepthStencil(Surface& source, Surface& dest, | ||||
|                                   const VideoCore::TextureBlit& blit) { | ||||
|     if (!instance.IsShaderStencilExportSupported()) { | ||||
|         LOG_ERROR(Render_Vulkan, "Unable to emulate depth stencil images"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     const vk::Rect2D dst_render_area = { | ||||
|         .offset = {0, 0}, | ||||
|         .extent = {dest.GetScaledWidth(), dest.GetScaledHeight()}, | ||||
|     }; | ||||
|  | ||||
|     std::array<DescriptorData, 2> textures{}; | ||||
|     textures[0].image_info = vk::DescriptorImageInfo{ | ||||
|         .sampler = nearest_sampler, | ||||
|         .imageView = source.DepthView(), | ||||
|         .imageLayout = vk::ImageLayout::eGeneral, | ||||
|     }; | ||||
|     textures[1].image_info = vk::DescriptorImageInfo{ | ||||
|         .sampler = nearest_sampler, | ||||
|         .imageView = source.StencilView(), | ||||
|         .imageLayout = vk::ImageLayout::eGeneral, | ||||
|     }; | ||||
|  | ||||
|     const auto descriptor_set = two_textures_provider.Acquire(textures); | ||||
|  | ||||
|     const RenderPass depth_pass = { | ||||
|         .framebuffer = dest.Framebuffer(), | ||||
|         .render_pass = | ||||
|             renderpass_cache.GetRenderpass(PixelFormat::Invalid, dest.pixel_format, false), | ||||
|         .render_area = dst_render_area, | ||||
|     }; | ||||
|     renderpass_cache.BeginRendering(depth_pass); | ||||
|  | ||||
|     scheduler.Record([blit, descriptor_set, this](vk::CommandBuffer cmdbuf) { | ||||
|         const vk::PipelineLayout layout = two_textures_pipeline_layout; | ||||
|  | ||||
|         cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, depth_blit_pipeline); | ||||
|         cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, descriptor_set, {}); | ||||
|         BindBlitState(cmdbuf, layout, blit); | ||||
|         cmdbuf.draw(3, 1, 0, 0); | ||||
|     }); | ||||
|     scheduler.MakeDirty(StateFlags::Pipeline); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest, | ||||
|                                       const VideoCore::TextureCopy& copy) { | ||||
|     std::array<DescriptorData, 3> textures{}; | ||||
|     textures[0].image_info = vk::DescriptorImageInfo{ | ||||
|         .imageView = source.DepthView(), | ||||
|         .imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal, | ||||
|     }; | ||||
|     textures[1].image_info = vk::DescriptorImageInfo{ | ||||
|         .imageView = source.StencilView(), | ||||
|         .imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal, | ||||
|     }; | ||||
|     textures[2].image_info = vk::DescriptorImageInfo{ | ||||
|         .imageView = dest.ImageView(), | ||||
|         .imageLayout = vk::ImageLayout::eGeneral, | ||||
|     }; | ||||
|  | ||||
|     const auto descriptor_set = compute_provider.Acquire(textures); | ||||
|  | ||||
|     renderpass_cache.EndRendering(); | ||||
|     scheduler.Record([this, descriptor_set, copy, src_image = source.Image(), | ||||
|                       dst_image = dest.Image()](vk::CommandBuffer cmdbuf) { | ||||
|         const std::array pre_barriers = { | ||||
|             vk::ImageMemoryBarrier{ | ||||
|                 .srcAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite, | ||||
|                 .dstAccessMask = vk::AccessFlagBits::eShaderRead, | ||||
|                 .oldLayout = vk::ImageLayout::eGeneral, | ||||
|                 .newLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal, | ||||
|                 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|                 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|                 .image = src_image, | ||||
|                 .subresourceRange{ | ||||
|                     .aspectMask = | ||||
|                         vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, | ||||
|                     .baseMipLevel = 0, | ||||
|                     .levelCount = VK_REMAINING_MIP_LEVELS, | ||||
|                     .baseArrayLayer = 0, | ||||
|                     .layerCount = VK_REMAINING_ARRAY_LAYERS, | ||||
|                 }, | ||||
|             }, | ||||
|             vk::ImageMemoryBarrier{ | ||||
|                 .srcAccessMask = vk::AccessFlagBits::eNone, | ||||
|                 .dstAccessMask = vk::AccessFlagBits::eShaderWrite, | ||||
|                 .oldLayout = vk::ImageLayout::eUndefined, | ||||
|                 .newLayout = vk::ImageLayout::eGeneral, | ||||
|                 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|                 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|                 .image = dst_image, | ||||
|                 .subresourceRange{ | ||||
|                     .aspectMask = vk::ImageAspectFlagBits::eColor, | ||||
|                     .baseMipLevel = 0, | ||||
|                     .levelCount = VK_REMAINING_MIP_LEVELS, | ||||
|                     .baseArrayLayer = 0, | ||||
|                     .layerCount = VK_REMAINING_ARRAY_LAYERS, | ||||
|                 }, | ||||
|             }, | ||||
|         }; | ||||
|         const std::array post_barriers = { | ||||
|             vk::ImageMemoryBarrier{ | ||||
|                 .srcAccessMask = vk::AccessFlagBits::eShaderRead, | ||||
|                 .dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite | | ||||
|                                  vk::AccessFlagBits::eDepthStencilAttachmentRead, | ||||
|                 .oldLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal, | ||||
|                 .newLayout = vk::ImageLayout::eGeneral, | ||||
|                 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|                 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|                 .image = src_image, | ||||
|                 .subresourceRange{ | ||||
|                     .aspectMask = | ||||
|                         vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, | ||||
|                     .baseMipLevel = 0, | ||||
|                     .levelCount = VK_REMAINING_MIP_LEVELS, | ||||
|                     .baseArrayLayer = 0, | ||||
|                     .layerCount = VK_REMAINING_ARRAY_LAYERS, | ||||
|                 }, | ||||
|             }, | ||||
|             vk::ImageMemoryBarrier{ | ||||
|                 .srcAccessMask = vk::AccessFlagBits::eShaderWrite, | ||||
|                 .dstAccessMask = vk::AccessFlagBits::eTransferRead, | ||||
|                 .oldLayout = vk::ImageLayout::eGeneral, | ||||
|                 .newLayout = vk::ImageLayout::eGeneral, | ||||
|                 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|                 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|                 .image = dst_image, | ||||
|                 .subresourceRange{ | ||||
|                     .aspectMask = vk::ImageAspectFlagBits::eColor, | ||||
|                     .baseMipLevel = 0, | ||||
|                     .levelCount = VK_REMAINING_MIP_LEVELS, | ||||
|                     .baseArrayLayer = 0, | ||||
|                     .layerCount = VK_REMAINING_ARRAY_LAYERS, | ||||
|                 }, | ||||
|             }}; | ||||
|         cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eEarlyFragmentTests | | ||||
|                                    vk::PipelineStageFlagBits::eLateFragmentTests, | ||||
|                                vk::PipelineStageFlagBits::eComputeShader, | ||||
|                                vk::DependencyFlagBits::eByRegion, {}, {}, pre_barriers); | ||||
|  | ||||
|         cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eCompute, compute_pipeline_layout, 0, | ||||
|                                   descriptor_set, {}); | ||||
|         cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, d24s8_to_rgba8_pipeline); | ||||
|  | ||||
|         const ComputeInfo info = { | ||||
|             .src_offset = Common::Vec2i{static_cast<int>(copy.src_offset.x), | ||||
|                                         static_cast<int>(copy.src_offset.y)}, | ||||
|             .dst_offset = Common::Vec2i{static_cast<int>(copy.dst_offset.x), | ||||
|                                         static_cast<int>(copy.dst_offset.y)}, | ||||
|         }; | ||||
|         cmdbuf.pushConstants(compute_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, | ||||
|                              sizeof(info), &info); | ||||
|  | ||||
|         cmdbuf.dispatch(copy.extent.width / 8, copy.extent.height / 8, 1); | ||||
|  | ||||
|         cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, | ||||
|                                vk::PipelineStageFlagBits::eEarlyFragmentTests | | ||||
|                                    vk::PipelineStageFlagBits::eLateFragmentTests | | ||||
|                                    vk::PipelineStageFlagBits::eTransfer, | ||||
|                                vk::DependencyFlagBits::eByRegion, {}, {}, post_barriers); | ||||
|     }); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool BlitHelper::DepthToBuffer(Surface& source, vk::Buffer buffer, | ||||
|                                const VideoCore::BufferTextureCopy& copy) { | ||||
|     std::array<DescriptorData, 3> textures{}; | ||||
|     textures[0].image_info = vk::DescriptorImageInfo{ | ||||
|         .sampler = nearest_sampler, | ||||
|         .imageView = source.DepthView(), | ||||
|         .imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal, | ||||
|     }; | ||||
|     textures[1].image_info = vk::DescriptorImageInfo{ | ||||
|         .sampler = nearest_sampler, | ||||
|         .imageView = source.StencilView(), | ||||
|         .imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal, | ||||
|     }; | ||||
|     textures[2].buffer_info = vk::DescriptorBufferInfo{ | ||||
|         .buffer = buffer, | ||||
|         .offset = copy.buffer_offset, | ||||
|         .range = copy.buffer_size, | ||||
|     }; | ||||
|  | ||||
|     const auto descriptor_set = compute_buffer_provider.Acquire(textures); | ||||
|  | ||||
|     renderpass_cache.EndRendering(); | ||||
|     scheduler.Record([this, descriptor_set, copy, src_image = source.Image(), | ||||
|                       extent = source.RealExtent(false)](vk::CommandBuffer cmdbuf) { | ||||
|         const vk::ImageMemoryBarrier pre_barrier = { | ||||
|             .srcAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite, | ||||
|             .dstAccessMask = vk::AccessFlagBits::eShaderRead, | ||||
|             .oldLayout = vk::ImageLayout::eGeneral, | ||||
|             .newLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal, | ||||
|             .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|             .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|             .image = src_image, | ||||
|             .subresourceRange{ | ||||
|                 .aspectMask = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, | ||||
|                 .baseMipLevel = 0, | ||||
|                 .levelCount = VK_REMAINING_MIP_LEVELS, | ||||
|                 .baseArrayLayer = 0, | ||||
|                 .layerCount = VK_REMAINING_ARRAY_LAYERS, | ||||
|             }, | ||||
|         }; | ||||
|         const vk::ImageMemoryBarrier post_barrier = { | ||||
|             .srcAccessMask = vk::AccessFlagBits::eShaderRead, | ||||
|             .dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite | | ||||
|                              vk::AccessFlagBits::eDepthStencilAttachmentRead, | ||||
|             .oldLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal, | ||||
|             .newLayout = vk::ImageLayout::eGeneral, | ||||
|             .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|             .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|             .image = src_image, | ||||
|             .subresourceRange{ | ||||
|                 .aspectMask = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, | ||||
|                 .baseMipLevel = 0, | ||||
|                 .levelCount = VK_REMAINING_MIP_LEVELS, | ||||
|                 .baseArrayLayer = 0, | ||||
|                 .layerCount = VK_REMAINING_ARRAY_LAYERS, | ||||
|             }, | ||||
|         }; | ||||
|         cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eEarlyFragmentTests | | ||||
|                                    vk::PipelineStageFlagBits::eLateFragmentTests, | ||||
|                                vk::PipelineStageFlagBits::eComputeShader, | ||||
|                                vk::DependencyFlagBits::eByRegion, {}, {}, pre_barrier); | ||||
|  | ||||
|         cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eCompute, compute_buffer_pipeline_layout, | ||||
|                                   0, descriptor_set, {}); | ||||
|         cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, depth_to_buffer_pipeline); | ||||
|  | ||||
|         const ComputeInfo info = { | ||||
|             .src_offset = Common::Vec2i{static_cast<int>(copy.texture_rect.left), | ||||
|                                         static_cast<int>(copy.texture_rect.bottom)}, | ||||
|             .src_extent = | ||||
|                 Common::Vec2i{static_cast<int>(extent.width), static_cast<int>(extent.height)}, | ||||
|         }; | ||||
|         cmdbuf.pushConstants(compute_buffer_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, | ||||
|                              sizeof(ComputeInfo), &info); | ||||
|  | ||||
|         cmdbuf.dispatch(copy.texture_rect.GetWidth() / 8, copy.texture_rect.GetHeight() / 8, 1); | ||||
|  | ||||
|         cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, | ||||
|                                vk::PipelineStageFlagBits::eEarlyFragmentTests | | ||||
|                                    vk::PipelineStageFlagBits::eLateFragmentTests | | ||||
|                                    vk::PipelineStageFlagBits::eTransfer, | ||||
|                                vk::DependencyFlagBits::eByRegion, {}, {}, post_barrier); | ||||
|     }); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| vk::Pipeline BlitHelper::MakeComputePipeline(vk::ShaderModule shader, vk::PipelineLayout layout) { | ||||
|     const vk::ComputePipelineCreateInfo compute_info = { | ||||
|         .stage = MakeStages(shader), | ||||
|         .layout = layout, | ||||
|     }; | ||||
|  | ||||
|     if (const auto result = device.createComputePipeline({}, compute_info); | ||||
|         result.result == vk::Result::eSuccess) { | ||||
|         return result.value; | ||||
|     } else { | ||||
|         LOG_CRITICAL(Render_Vulkan, "Compute pipeline creation failed!"); | ||||
|         UNREACHABLE(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| vk::Pipeline BlitHelper::MakeDepthStencilBlitPipeline() { | ||||
|     if (!instance.IsShaderStencilExportSupported()) { | ||||
|         return VK_NULL_HANDLE; | ||||
|     } | ||||
|  | ||||
|     const std::array stages = MakeStages(full_screen_vert, blit_depth_stencil_frag); | ||||
|     const auto renderpass = renderpass_cache.GetRenderpass(VideoCore::PixelFormat::Invalid, | ||||
|                                                            VideoCore::PixelFormat::D24S8, false); | ||||
|     vk::GraphicsPipelineCreateInfo depth_stencil_info = { | ||||
|         .stageCount = static_cast<u32>(stages.size()), | ||||
|         .pStages = stages.data(), | ||||
|         .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, | ||||
|         .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, | ||||
|         .pTessellationState = nullptr, | ||||
|         .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, | ||||
|         .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, | ||||
|         .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, | ||||
|         .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, | ||||
|         .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO, | ||||
|         .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, | ||||
|         .layout = two_textures_pipeline_layout, | ||||
|         .renderPass = renderpass, | ||||
|     }; | ||||
|  | ||||
|     if (const auto result = device.createGraphicsPipeline({}, depth_stencil_info); | ||||
|         result.result == vk::Result::eSuccess) { | ||||
|         return result.value; | ||||
|     } else { | ||||
|         LOG_CRITICAL(Render_Vulkan, "Depth stencil blit pipeline creation failed!"); | ||||
|         UNREACHABLE(); | ||||
|     } | ||||
|     return VK_NULL_HANDLE; | ||||
| } | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										71
									
								
								src/video_core/renderer_vulkan/vk_blit_helper.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/video_core/renderer_vulkan/vk_blit_helper.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "video_core/renderer_vulkan/vk_descriptor_pool.h" | ||||
|  | ||||
| namespace VideoCore { | ||||
| struct TextureBlit; | ||||
| struct TextureCopy; | ||||
| struct BufferTextureCopy; | ||||
| } // namespace VideoCore | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| class Instance; | ||||
| class RenderpassCache; | ||||
| class Scheduler; | ||||
| class Surface; | ||||
|  | ||||
| class BlitHelper { | ||||
|     friend class TextureRuntime; | ||||
|  | ||||
| public: | ||||
|     BlitHelper(const Instance& instance, Scheduler& scheduler, DescriptorPool& pool, | ||||
|                RenderpassCache& renderpass_cache); | ||||
|     ~BlitHelper(); | ||||
|  | ||||
|     bool BlitDepthStencil(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit); | ||||
|  | ||||
|     bool ConvertDS24S8ToRGBA8(Surface& source, Surface& dest, const VideoCore::TextureCopy& copy); | ||||
|  | ||||
|     bool DepthToBuffer(Surface& source, vk::Buffer buffer, | ||||
|                        const VideoCore::BufferTextureCopy& copy); | ||||
|  | ||||
| private: | ||||
|     /// Creates compute pipelines used for blit | ||||
|     vk::Pipeline MakeComputePipeline(vk::ShaderModule shader, vk::PipelineLayout layout); | ||||
|  | ||||
|     /// Creates graphics pipelines used for blit | ||||
|     vk::Pipeline MakeDepthStencilBlitPipeline(); | ||||
|  | ||||
| private: | ||||
|     const Instance& instance; | ||||
|     Scheduler& scheduler; | ||||
|     RenderpassCache& renderpass_cache; | ||||
|  | ||||
|     vk::Device device; | ||||
|     vk::RenderPass r32_renderpass; | ||||
|  | ||||
|     DescriptorSetProvider compute_provider; | ||||
|     DescriptorSetProvider compute_buffer_provider; | ||||
|     DescriptorSetProvider two_textures_provider; | ||||
|     vk::PipelineLayout compute_pipeline_layout; | ||||
|     vk::PipelineLayout compute_buffer_pipeline_layout; | ||||
|     vk::PipelineLayout two_textures_pipeline_layout; | ||||
|  | ||||
|     vk::ShaderModule full_screen_vert; | ||||
|     vk::ShaderModule d24s8_to_rgba8_comp; | ||||
|     vk::ShaderModule depth_to_buffer_comp; | ||||
|     vk::ShaderModule blit_depth_stencil_frag; | ||||
|  | ||||
|     vk::Pipeline d24s8_to_rgba8_pipeline; | ||||
|     vk::Pipeline depth_to_buffer_pipeline; | ||||
|     vk::Pipeline depth_blit_pipeline; | ||||
|     vk::Sampler linear_sampler; | ||||
|     vk::Sampler nearest_sampler; | ||||
| }; | ||||
|  | ||||
| } // namespace Vulkan | ||||
| @@ -9,6 +9,7 @@ | ||||
| #define VK_NO_PROTOTYPES | ||||
| #define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 | ||||
| #define VULKAN_HPP_NO_CONSTRUCTORS | ||||
| #define VULKAN_HPP_NO_UNION_CONSTRUCTORS | ||||
| #define VULKAN_HPP_NO_STRUCT_SETTERS | ||||
| #include <vulkan/vulkan.hpp> | ||||
|  | ||||
|   | ||||
							
								
								
									
										141
									
								
								src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,141 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include "common/microprofile.h" | ||||
| #include "video_core/renderer_vulkan/vk_descriptor_pool.h" | ||||
| #include "video_core/renderer_vulkan/vk_instance.h" | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| MICROPROFILE_DEFINE(Vulkan_DescriptorSetAcquire, "Vulkan", "Descriptor Set Acquire", | ||||
|                     MP_RGB(64, 128, 256)); | ||||
|  | ||||
| constexpr u32 MAX_BATCH_SIZE = 8; | ||||
|  | ||||
| DescriptorPool::DescriptorPool(const Instance& instance_) : instance{instance_} { | ||||
|     auto& pool = pools.emplace_back(); | ||||
|     pool = CreatePool(); | ||||
| } | ||||
|  | ||||
| DescriptorPool::~DescriptorPool() = default; | ||||
|  | ||||
| std::vector<vk::DescriptorSet> DescriptorPool::Allocate(vk::DescriptorSetLayout layout, | ||||
|                                                         u32 num_sets) { | ||||
|     std::array<vk::DescriptorSetLayout, MAX_BATCH_SIZE> layouts; | ||||
|     layouts.fill(layout); | ||||
|  | ||||
|     u32 current_pool = 0; | ||||
|     vk::DescriptorSetAllocateInfo alloc_info = { | ||||
|         .descriptorPool = *pools[current_pool], | ||||
|         .descriptorSetCount = num_sets, | ||||
|         .pSetLayouts = layouts.data(), | ||||
|     }; | ||||
|  | ||||
|     while (true) { | ||||
|         try { | ||||
|             return instance.GetDevice().allocateDescriptorSets(alloc_info); | ||||
|         } catch (const vk::OutOfPoolMemoryError&) { | ||||
|             current_pool++; | ||||
|             if (current_pool == pools.size()) { | ||||
|                 LOG_INFO(Render_Vulkan, "Run out of pools, creating new one!"); | ||||
|                 auto& pool = pools.emplace_back(); | ||||
|                 pool = CreatePool(); | ||||
|             } | ||||
|             alloc_info.descriptorPool = *pools[current_pool]; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| vk::DescriptorSet DescriptorPool::Allocate(vk::DescriptorSetLayout layout) { | ||||
|     const auto sets = Allocate(layout, 1); | ||||
|     return sets[0]; | ||||
| } | ||||
|  | ||||
| vk::UniqueDescriptorPool DescriptorPool::CreatePool() { | ||||
|     // Choose a sane pool size good for most games | ||||
|     static constexpr std::array<vk::DescriptorPoolSize, 6> pool_sizes = {{ | ||||
|         {vk::DescriptorType::eUniformBufferDynamic, 64}, | ||||
|         {vk::DescriptorType::eUniformTexelBuffer, 64}, | ||||
|         {vk::DescriptorType::eCombinedImageSampler, 4096}, | ||||
|         {vk::DescriptorType::eSampledImage, 256}, | ||||
|         {vk::DescriptorType::eStorageImage, 256}, | ||||
|         {vk::DescriptorType::eStorageBuffer, 32}, | ||||
|     }}; | ||||
|  | ||||
|     const vk::DescriptorPoolCreateInfo descriptor_pool_info = { | ||||
|         .maxSets = 4098, | ||||
|         .poolSizeCount = static_cast<u32>(pool_sizes.size()), | ||||
|         .pPoolSizes = pool_sizes.data(), | ||||
|     }; | ||||
|  | ||||
|     return instance.GetDevice().createDescriptorPoolUnique(descriptor_pool_info); | ||||
| } | ||||
|  | ||||
| DescriptorSetProvider::DescriptorSetProvider( | ||||
|     const Instance& instance, DescriptorPool& pool_, | ||||
|     std::span<const vk::DescriptorSetLayoutBinding> bindings) | ||||
|     : pool{pool_}, device{instance.GetDevice()} { | ||||
|     std::array<vk::DescriptorUpdateTemplateEntry, MAX_DESCRIPTORS> update_entries; | ||||
|  | ||||
|     for (u32 i = 0; i < bindings.size(); i++) { | ||||
|         update_entries[i] = vk::DescriptorUpdateTemplateEntry{ | ||||
|             .dstBinding = bindings[i].binding, | ||||
|             .dstArrayElement = 0, | ||||
|             .descriptorCount = bindings[i].descriptorCount, | ||||
|             .descriptorType = bindings[i].descriptorType, | ||||
|             .offset = i * sizeof(DescriptorData), | ||||
|             .stride = sizeof(DescriptorData), | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     const vk::DescriptorSetLayoutCreateInfo layout_info = { | ||||
|         .bindingCount = static_cast<u32>(bindings.size()), | ||||
|         .pBindings = bindings.data(), | ||||
|     }; | ||||
|     layout = device.createDescriptorSetLayoutUnique(layout_info); | ||||
|  | ||||
|     const vk::DescriptorUpdateTemplateCreateInfo template_info = { | ||||
|         .descriptorUpdateEntryCount = static_cast<u32>(bindings.size()), | ||||
|         .pDescriptorUpdateEntries = update_entries.data(), | ||||
|         .templateType = vk::DescriptorUpdateTemplateType::eDescriptorSet, | ||||
|         .descriptorSetLayout = *layout, | ||||
|     }; | ||||
|     update_template = device.createDescriptorUpdateTemplateUnique(template_info); | ||||
| } | ||||
|  | ||||
| DescriptorSetProvider::~DescriptorSetProvider() = default; | ||||
|  | ||||
| vk::DescriptorSet DescriptorSetProvider::Acquire(std::span<const DescriptorData> data) { | ||||
|     MICROPROFILE_SCOPE(Vulkan_DescriptorSetAcquire); | ||||
|     DescriptorSetData key{}; | ||||
|     std::memcpy(key.data(), data.data(), data.size_bytes()); | ||||
|     const auto [it, new_set] = descriptor_set_map.try_emplace(key); | ||||
|     if (!new_set) { | ||||
|         return it->second; | ||||
|     } | ||||
|     if (free_sets.empty()) { | ||||
|         free_sets = pool.Allocate(*layout, MAX_BATCH_SIZE); | ||||
|     } | ||||
|     it.value() = free_sets.back(); | ||||
|     free_sets.pop_back(); | ||||
|     device.updateDescriptorSetWithTemplate(it->second, *update_template, data[0]); | ||||
|     return it->second; | ||||
| } | ||||
|  | ||||
| void DescriptorSetProvider::FreeWithImage(vk::ImageView image_view) { | ||||
|     for (auto it = descriptor_set_map.begin(); it != descriptor_set_map.end();) { | ||||
|         const auto& [data, set] = *it; | ||||
|         const bool has_image = std::any_of(data.begin(), data.end(), [image_view](auto& info) { | ||||
|             return info.image_info.imageView == image_view; | ||||
|         }); | ||||
|         if (has_image) { | ||||
|             free_sets.push_back(set); | ||||
|             it = descriptor_set_map.erase(it); | ||||
|         } else { | ||||
|             it++; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										92
									
								
								src/video_core/renderer_vulkan/vk_descriptor_pool.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/video_core/renderer_vulkan/vk_descriptor_pool.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <span> | ||||
| #include <vector> | ||||
| #include <tsl/robin_map.h> | ||||
|  | ||||
| #include "common/hash.h" | ||||
| #include "video_core/renderer_vulkan/vk_common.h" | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| class Instance; | ||||
|  | ||||
| constexpr u32 MAX_DESCRIPTORS = 7; | ||||
|  | ||||
| union DescriptorData { | ||||
|     vk::DescriptorImageInfo image_info; | ||||
|     vk::DescriptorBufferInfo buffer_info; | ||||
|     vk::BufferView buffer_view; | ||||
|  | ||||
|     bool operator==(const DescriptorData& other) const noexcept { | ||||
|         return std::memcmp(this, &other, sizeof(DescriptorData)) == 0; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| using DescriptorSetData = std::array<DescriptorData, MAX_DESCRIPTORS>; | ||||
|  | ||||
| struct DataHasher { | ||||
|     u64 operator()(const DescriptorSetData& data) const noexcept { | ||||
|         return Common::ComputeHash64(data.data(), sizeof(data)); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * An interface for allocating descriptor sets that manages a collection of descriptor pools. | ||||
|  */ | ||||
| class DescriptorPool { | ||||
| public: | ||||
|     explicit DescriptorPool(const Instance& instance); | ||||
|     ~DescriptorPool(); | ||||
|  | ||||
|     std::vector<vk::DescriptorSet> Allocate(vk::DescriptorSetLayout layout, u32 num_sets); | ||||
|  | ||||
|     vk::DescriptorSet Allocate(vk::DescriptorSetLayout layout); | ||||
|  | ||||
| private: | ||||
|     vk::UniqueDescriptorPool CreatePool(); | ||||
|  | ||||
| private: | ||||
|     const Instance& instance; | ||||
|     std::vector<vk::UniqueDescriptorPool> pools; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Allocates and caches descriptor sets of a specific layout. | ||||
|  */ | ||||
| class DescriptorSetProvider { | ||||
| public: | ||||
|     explicit DescriptorSetProvider(const Instance& instance, DescriptorPool& pool, | ||||
|                                    std::span<const vk::DescriptorSetLayoutBinding> bindings); | ||||
|     ~DescriptorSetProvider(); | ||||
|  | ||||
|     vk::DescriptorSet Acquire(std::span<const DescriptorData> data); | ||||
|  | ||||
|     void FreeWithImage(vk::ImageView image_view); | ||||
|  | ||||
|     [[nodiscard]] vk::DescriptorSetLayout Layout() const noexcept { | ||||
|         return *layout; | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] vk::DescriptorSetLayout& Layout() noexcept { | ||||
|         return layout.get(); | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] vk::DescriptorUpdateTemplate UpdateTemplate() const noexcept { | ||||
|         return *update_template; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     DescriptorPool& pool; | ||||
|     vk::Device device; | ||||
|     vk::UniqueDescriptorSetLayout layout; | ||||
|     vk::UniqueDescriptorUpdateTemplate update_template; | ||||
|     std::vector<vk::DescriptorSet> free_sets; | ||||
|     tsl::robin_map<DescriptorSetData, vk::DescriptorSet, DataHasher> descriptor_set_map; | ||||
| }; | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										288
									
								
								src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										288
									
								
								src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,288 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <boost/container/static_vector.hpp> | ||||
|  | ||||
| #include "common/hash.h" | ||||
| #include "common/microprofile.h" | ||||
| #include "video_core/renderer_vulkan/pica_to_vk.h" | ||||
| #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" | ||||
| #include "video_core/renderer_vulkan/vk_instance.h" | ||||
| #include "video_core/renderer_vulkan/vk_renderpass_cache.h" | ||||
| #include "video_core/renderer_vulkan/vk_shader_util.h" | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| MICROPROFILE_DEFINE(Vulkan_Pipeline, "Vulkan", "Pipeline Building", MP_RGB(0, 192, 32)); | ||||
|  | ||||
| vk::ShaderStageFlagBits MakeShaderStage(std::size_t index) { | ||||
|     switch (index) { | ||||
|     case 0: | ||||
|         return vk::ShaderStageFlagBits::eVertex; | ||||
|     case 1: | ||||
|         return vk::ShaderStageFlagBits::eFragment; | ||||
|     case 2: | ||||
|         return vk::ShaderStageFlagBits::eGeometry; | ||||
|     default: | ||||
|         LOG_CRITICAL(Render_Vulkan, "Invalid shader stage index!"); | ||||
|         UNREACHABLE(); | ||||
|     } | ||||
|     return vk::ShaderStageFlagBits::eVertex; | ||||
| } | ||||
|  | ||||
| u64 PipelineInfo::Hash(const Instance& instance) const { | ||||
|     u64 info_hash = 0; | ||||
|     const auto append_hash = [&info_hash](const auto& data) { | ||||
|         const u64 data_hash = Common::ComputeStructHash64(data); | ||||
|         info_hash = Common::HashCombine(info_hash, data_hash); | ||||
|     }; | ||||
|  | ||||
|     append_hash(vertex_layout); | ||||
|     append_hash(attachments); | ||||
|     append_hash(blending); | ||||
|  | ||||
|     if (!instance.IsExtendedDynamicStateSupported()) { | ||||
|         append_hash(rasterization); | ||||
|         append_hash(depth_stencil); | ||||
|     } | ||||
|  | ||||
|     return info_hash; | ||||
| } | ||||
|  | ||||
| Shader::Shader(const Instance& instance) : device{instance.GetDevice()} {} | ||||
|  | ||||
| Shader::Shader(const Instance& instance, vk::ShaderStageFlagBits stage, std::string code) | ||||
|     : Shader{instance} { | ||||
|     module = Compile(code, stage, instance.GetDevice()); | ||||
|     MarkDone(); | ||||
| } | ||||
|  | ||||
| Shader::~Shader() { | ||||
|     if (device && module) { | ||||
|         device.destroyShaderModule(module); | ||||
|     } | ||||
| } | ||||
|  | ||||
| GraphicsPipeline::GraphicsPipeline(const Instance& instance_, RenderpassCache& renderpass_cache_, | ||||
|                                    const PipelineInfo& info_, vk::PipelineCache pipeline_cache_, | ||||
|                                    vk::PipelineLayout layout_, std::array<Shader*, 3> stages_, | ||||
|                                    Common::ThreadWorker* worker_) | ||||
|     : instance{instance_}, renderpass_cache{renderpass_cache_}, worker{worker_}, | ||||
|       pipeline_layout{layout_}, pipeline_cache{pipeline_cache_}, info{info_}, stages{stages_} {} | ||||
|  | ||||
| GraphicsPipeline::~GraphicsPipeline() = default; | ||||
|  | ||||
| bool GraphicsPipeline::TryBuild(bool wait_built) { | ||||
|     // The pipeline is currently being compiled. We can either wait for it | ||||
|     // or skip the draw. | ||||
|     if (is_pending) { | ||||
|         return wait_built; | ||||
|     } | ||||
|  | ||||
|     // If the shaders haven't been compiled yet, we cannot proceed. | ||||
|     const bool shaders_pending = std::any_of( | ||||
|         stages.begin(), stages.end(), [](Shader* shader) { return shader && !shader->IsDone(); }); | ||||
|     if (!wait_built && shaders_pending) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // Ask the driver if it can give us the pipeline quickly. | ||||
|     if (!shaders_pending && instance.IsPipelineCreationCacheControlSupported() && Build(true)) { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     // Fallback to (a)synchronous compilation | ||||
|     worker->QueueWork([this] { Build(); }); | ||||
|     is_pending = true; | ||||
|     return wait_built; | ||||
| } | ||||
|  | ||||
| bool GraphicsPipeline::Build(bool fail_on_compile_required) { | ||||
|     MICROPROFILE_SCOPE(Vulkan_Pipeline); | ||||
|     const vk::Device device = instance.GetDevice(); | ||||
|  | ||||
|     std::array<vk::VertexInputBindingDescription, MAX_VERTEX_BINDINGS> bindings; | ||||
|     for (u32 i = 0; i < info.vertex_layout.binding_count; i++) { | ||||
|         const auto& binding = info.vertex_layout.bindings[i]; | ||||
|         bindings[i] = vk::VertexInputBindingDescription{ | ||||
|             .binding = binding.binding, | ||||
|             .stride = binding.stride, | ||||
|             .inputRate = binding.fixed.Value() ? vk::VertexInputRate::eInstance | ||||
|                                                : vk::VertexInputRate::eVertex, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     std::array<vk::VertexInputAttributeDescription, MAX_VERTEX_ATTRIBUTES> attributes; | ||||
|     for (u32 i = 0; i < info.vertex_layout.attribute_count; i++) { | ||||
|         const auto& attr = info.vertex_layout.attributes[i]; | ||||
|         const FormatTraits& traits = instance.GetTraits(attr.type, attr.size); | ||||
|         attributes[i] = vk::VertexInputAttributeDescription{ | ||||
|             .location = attr.location, | ||||
|             .binding = attr.binding, | ||||
|             .format = traits.native, | ||||
|             .offset = attr.offset, | ||||
|         }; | ||||
|  | ||||
|         // At the end there's always the fixed binding which takes up | ||||
|         // at least 16 bytes so we should always be able to alias. | ||||
|         if (traits.needs_emulation) { | ||||
|             const FormatTraits& comp_four_traits = instance.GetTraits(attr.type, 4); | ||||
|             attributes[i].format = comp_four_traits.native; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     const vk::PipelineVertexInputStateCreateInfo vertex_input_info = { | ||||
|         .vertexBindingDescriptionCount = info.vertex_layout.binding_count, | ||||
|         .pVertexBindingDescriptions = bindings.data(), | ||||
|         .vertexAttributeDescriptionCount = info.vertex_layout.attribute_count, | ||||
|         .pVertexAttributeDescriptions = attributes.data(), | ||||
|     }; | ||||
|  | ||||
|     const vk::PipelineInputAssemblyStateCreateInfo input_assembly = { | ||||
|         .topology = PicaToVK::PrimitiveTopology(info.rasterization.topology), | ||||
|         .primitiveRestartEnable = false, | ||||
|     }; | ||||
|  | ||||
|     const vk::PipelineRasterizationStateCreateInfo raster_state = { | ||||
|         .depthClampEnable = false, | ||||
|         .rasterizerDiscardEnable = false, | ||||
|         .cullMode = PicaToVK::CullMode(info.rasterization.cull_mode), | ||||
|         .frontFace = PicaToVK::FrontFace(info.rasterization.cull_mode), | ||||
|         .depthBiasEnable = false, | ||||
|         .lineWidth = 1.0f, | ||||
|     }; | ||||
|  | ||||
|     const vk::PipelineMultisampleStateCreateInfo multisampling = { | ||||
|         .rasterizationSamples = vk::SampleCountFlagBits::e1, | ||||
|         .sampleShadingEnable = false, | ||||
|     }; | ||||
|  | ||||
|     const vk::PipelineColorBlendAttachmentState colorblend_attachment = { | ||||
|         .blendEnable = info.blending.blend_enable, | ||||
|         .srcColorBlendFactor = PicaToVK::BlendFunc(info.blending.src_color_blend_factor), | ||||
|         .dstColorBlendFactor = PicaToVK::BlendFunc(info.blending.dst_color_blend_factor), | ||||
|         .colorBlendOp = PicaToVK::BlendEquation(info.blending.color_blend_eq), | ||||
|         .srcAlphaBlendFactor = PicaToVK::BlendFunc(info.blending.src_alpha_blend_factor), | ||||
|         .dstAlphaBlendFactor = PicaToVK::BlendFunc(info.blending.dst_alpha_blend_factor), | ||||
|         .alphaBlendOp = PicaToVK::BlendEquation(info.blending.alpha_blend_eq), | ||||
|         .colorWriteMask = static_cast<vk::ColorComponentFlags>(info.blending.color_write_mask), | ||||
|     }; | ||||
|  | ||||
|     const vk::PipelineColorBlendStateCreateInfo color_blending = { | ||||
|         .logicOpEnable = !info.blending.blend_enable && !instance.NeedsLogicOpEmulation(), | ||||
|         .logicOp = PicaToVK::LogicOp(info.blending.logic_op), | ||||
|         .attachmentCount = 1, | ||||
|         .pAttachments = &colorblend_attachment, | ||||
|         .blendConstants = std::array{1.0f, 1.0f, 1.0f, 1.0f}, | ||||
|     }; | ||||
|  | ||||
|     const vk::Viewport viewport = { | ||||
|         .x = 0.0f, | ||||
|         .y = 0.0f, | ||||
|         .width = 1.0f, | ||||
|         .height = 1.0f, | ||||
|         .minDepth = 0.0f, | ||||
|         .maxDepth = 1.0f, | ||||
|     }; | ||||
|  | ||||
|     const vk::Rect2D scissor = { | ||||
|         .offset = {0, 0}, | ||||
|         .extent = {1, 1}, | ||||
|     }; | ||||
|  | ||||
|     const vk::PipelineViewportStateCreateInfo viewport_info = { | ||||
|         .viewportCount = 1, | ||||
|         .pViewports = &viewport, | ||||
|         .scissorCount = 1, | ||||
|         .pScissors = &scissor, | ||||
|     }; | ||||
|  | ||||
|     boost::container::static_vector<vk::DynamicState, 14> dynamic_states = { | ||||
|         vk::DynamicState::eViewport,           vk::DynamicState::eScissor, | ||||
|         vk::DynamicState::eStencilCompareMask, vk::DynamicState::eStencilWriteMask, | ||||
|         vk::DynamicState::eStencilReference,   vk::DynamicState::eBlendConstants, | ||||
|     }; | ||||
|  | ||||
|     if (instance.IsExtendedDynamicStateSupported()) { | ||||
|         constexpr std::array extended = { | ||||
|             vk::DynamicState::eCullModeEXT,        vk::DynamicState::eDepthCompareOpEXT, | ||||
|             vk::DynamicState::eDepthTestEnableEXT, vk::DynamicState::eDepthWriteEnableEXT, | ||||
|             vk::DynamicState::eFrontFaceEXT,       vk::DynamicState::ePrimitiveTopologyEXT, | ||||
|             vk::DynamicState::eStencilOpEXT,       vk::DynamicState::eStencilTestEnableEXT, | ||||
|         }; | ||||
|         dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end()); | ||||
|     } | ||||
|  | ||||
|     const vk::PipelineDynamicStateCreateInfo dynamic_info = { | ||||
|         .dynamicStateCount = static_cast<u32>(dynamic_states.size()), | ||||
|         .pDynamicStates = dynamic_states.data(), | ||||
|     }; | ||||
|  | ||||
|     const vk::StencilOpState stencil_op_state = { | ||||
|         .failOp = PicaToVK::StencilOp(info.depth_stencil.stencil_fail_op), | ||||
|         .passOp = PicaToVK::StencilOp(info.depth_stencil.stencil_pass_op), | ||||
|         .depthFailOp = PicaToVK::StencilOp(info.depth_stencil.stencil_depth_fail_op), | ||||
|         .compareOp = PicaToVK::CompareFunc(info.depth_stencil.stencil_compare_op), | ||||
|     }; | ||||
|  | ||||
|     const vk::PipelineDepthStencilStateCreateInfo depth_info = { | ||||
|         .depthTestEnable = static_cast<u32>(info.depth_stencil.depth_test_enable.Value()), | ||||
|         .depthWriteEnable = static_cast<u32>(info.depth_stencil.depth_write_enable.Value()), | ||||
|         .depthCompareOp = PicaToVK::CompareFunc(info.depth_stencil.depth_compare_op), | ||||
|         .depthBoundsTestEnable = false, | ||||
|         .stencilTestEnable = static_cast<u32>(info.depth_stencil.stencil_test_enable.Value()), | ||||
|         .front = stencil_op_state, | ||||
|         .back = stencil_op_state, | ||||
|     }; | ||||
|  | ||||
|     u32 shader_count = 0; | ||||
|     std::array<vk::PipelineShaderStageCreateInfo, MAX_SHADER_STAGES> shader_stages; | ||||
|     for (std::size_t i = 0; i < stages.size(); i++) { | ||||
|         Shader* shader = stages[i]; | ||||
|         if (!shader) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         shader->WaitDone(); | ||||
|         shader_stages[shader_count++] = vk::PipelineShaderStageCreateInfo{ | ||||
|             .stage = MakeShaderStage(i), | ||||
|             .module = shader->Handle(), | ||||
|             .pName = "main", | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     vk::GraphicsPipelineCreateInfo pipeline_info = { | ||||
|         .stageCount = shader_count, | ||||
|         .pStages = shader_stages.data(), | ||||
|         .pVertexInputState = &vertex_input_info, | ||||
|         .pInputAssemblyState = &input_assembly, | ||||
|         .pViewportState = &viewport_info, | ||||
|         .pRasterizationState = &raster_state, | ||||
|         .pMultisampleState = &multisampling, | ||||
|         .pDepthStencilState = &depth_info, | ||||
|         .pColorBlendState = &color_blending, | ||||
|         .pDynamicState = &dynamic_info, | ||||
|         .layout = pipeline_layout, | ||||
|         .renderPass = | ||||
|             renderpass_cache.GetRenderpass(info.attachments.color, info.attachments.depth, false), | ||||
|     }; | ||||
|  | ||||
|     if (fail_on_compile_required) { | ||||
|         pipeline_info.flags |= vk::PipelineCreateFlagBits::eFailOnPipelineCompileRequiredEXT; | ||||
|     } | ||||
|  | ||||
|     auto result = device.createGraphicsPipelineUnique(pipeline_cache, pipeline_info); | ||||
|     if (result.result == vk::Result::eSuccess) { | ||||
|         pipeline = std::move(result.value); | ||||
|     } else if (result.result == vk::Result::eErrorPipelineCompileRequiredEXT) { | ||||
|         return false; | ||||
|     } else { | ||||
|         UNREACHABLE_MSG("Graphics pipeline creation failed!"); | ||||
|     } | ||||
|  | ||||
|     MarkDone(); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										192
									
								
								src/video_core/renderer_vulkan/vk_graphics_pipeline.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								src/video_core/renderer_vulkan/vk_graphics_pipeline.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,192 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include "common/thread_worker.h" | ||||
| #include "video_core/rasterizer_cache/pixel_format.h" | ||||
| #include "video_core/renderer_vulkan/vk_common.h" | ||||
| #include "video_core/renderer_vulkan/vk_shader_gen.h" | ||||
|  | ||||
| namespace Common { | ||||
|  | ||||
| struct AsyncHandle { | ||||
| public: | ||||
|     AsyncHandle(bool is_done_ = false) : is_done{is_done_} {} | ||||
|  | ||||
|     [[nodiscard]] bool IsDone() noexcept { | ||||
|         return is_done.load(std::memory_order::relaxed); | ||||
|     } | ||||
|  | ||||
|     void WaitDone() noexcept { | ||||
|         std::unique_lock lock{mutex}; | ||||
|         condvar.wait(lock, [this] { return is_done.load(std::memory_order::relaxed); }); | ||||
|     } | ||||
|  | ||||
|     void MarkDone(bool done = true) noexcept { | ||||
|         std::scoped_lock lock{mutex}; | ||||
|         is_done = done; | ||||
|         condvar.notify_all(); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     std::condition_variable condvar; | ||||
|     std::mutex mutex; | ||||
|     std::atomic_bool is_done{false}; | ||||
| }; | ||||
|  | ||||
| } // namespace Common | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| class Instance; | ||||
| class RenderpassCache; | ||||
|  | ||||
| constexpr u32 MAX_SHADER_STAGES = 3; | ||||
| constexpr u32 MAX_VERTEX_ATTRIBUTES = 16; | ||||
| constexpr u32 MAX_VERTEX_BINDINGS = 13; | ||||
|  | ||||
| /** | ||||
|  * The pipeline state is tightly packed with bitfields to reduce | ||||
|  * the overhead of hashing as much as possible | ||||
|  */ | ||||
| union RasterizationState { | ||||
|     u8 value = 0; | ||||
|     BitField<0, 2, Pica::PipelineRegs::TriangleTopology> topology; | ||||
|     BitField<4, 2, Pica::RasterizerRegs::CullMode> cull_mode; | ||||
| }; | ||||
|  | ||||
| union DepthStencilState { | ||||
|     u32 value = 0; | ||||
|     BitField<0, 1, u32> depth_test_enable; | ||||
|     BitField<1, 1, u32> depth_write_enable; | ||||
|     BitField<2, 1, u32> stencil_test_enable; | ||||
|     BitField<3, 3, Pica::FramebufferRegs::CompareFunc> depth_compare_op; | ||||
|     BitField<6, 3, Pica::FramebufferRegs::StencilAction> stencil_fail_op; | ||||
|     BitField<9, 3, Pica::FramebufferRegs::StencilAction> stencil_pass_op; | ||||
|     BitField<12, 3, Pica::FramebufferRegs::StencilAction> stencil_depth_fail_op; | ||||
|     BitField<15, 3, Pica::FramebufferRegs::CompareFunc> stencil_compare_op; | ||||
| }; | ||||
|  | ||||
| struct BlendingState { | ||||
|     u16 blend_enable; | ||||
|     u16 color_write_mask; | ||||
|     Pica::FramebufferRegs::LogicOp logic_op; | ||||
|     union { | ||||
|         u32 value = 0; | ||||
|         BitField<0, 4, Pica::FramebufferRegs::BlendFactor> src_color_blend_factor; | ||||
|         BitField<4, 4, Pica::FramebufferRegs::BlendFactor> dst_color_blend_factor; | ||||
|         BitField<8, 3, Pica::FramebufferRegs::BlendEquation> color_blend_eq; | ||||
|         BitField<11, 4, Pica::FramebufferRegs::BlendFactor> src_alpha_blend_factor; | ||||
|         BitField<15, 4, Pica::FramebufferRegs::BlendFactor> dst_alpha_blend_factor; | ||||
|         BitField<19, 3, Pica::FramebufferRegs::BlendEquation> alpha_blend_eq; | ||||
|     }; | ||||
| }; | ||||
|  | ||||
| struct DynamicState { | ||||
|     u32 blend_color = 0; | ||||
|     u8 stencil_reference; | ||||
|     u8 stencil_compare_mask; | ||||
|     u8 stencil_write_mask; | ||||
|  | ||||
|     bool operator==(const DynamicState& other) const noexcept { | ||||
|         return std::memcmp(this, &other, sizeof(DynamicState)) == 0; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| union VertexBinding { | ||||
|     u16 value = 0; | ||||
|     BitField<0, 4, u16> binding; | ||||
|     BitField<4, 1, u16> fixed; | ||||
|     BitField<5, 11, u16> stride; | ||||
| }; | ||||
|  | ||||
| union VertexAttribute { | ||||
|     u32 value = 0; | ||||
|     BitField<0, 4, u32> binding; | ||||
|     BitField<4, 4, u32> location; | ||||
|     BitField<8, 3, Pica::PipelineRegs::VertexAttributeFormat> type; | ||||
|     BitField<11, 3, u32> size; | ||||
|     BitField<14, 11, u32> offset; | ||||
| }; | ||||
|  | ||||
| struct VertexLayout { | ||||
|     u8 binding_count; | ||||
|     u8 attribute_count; | ||||
|     std::array<VertexBinding, MAX_VERTEX_BINDINGS> bindings; | ||||
|     std::array<VertexAttribute, MAX_VERTEX_ATTRIBUTES> attributes; | ||||
| }; | ||||
|  | ||||
| struct AttachmentInfo { | ||||
|     VideoCore::PixelFormat color; | ||||
|     VideoCore::PixelFormat depth; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Information about a graphics/compute pipeline | ||||
|  */ | ||||
| struct PipelineInfo { | ||||
|     BlendingState blending; | ||||
|     AttachmentInfo attachments; | ||||
|     RasterizationState rasterization; | ||||
|     DepthStencilState depth_stencil; | ||||
|     DynamicState dynamic; | ||||
|     VertexLayout vertex_layout; | ||||
|  | ||||
|     [[nodiscard]] u64 Hash(const Instance& instance) const; | ||||
|  | ||||
|     [[nodiscard]] bool IsDepthWriteEnabled() const noexcept { | ||||
|         const bool has_stencil = attachments.depth == VideoCore::PixelFormat::D24S8; | ||||
|         const bool depth_write = | ||||
|             depth_stencil.depth_test_enable && depth_stencil.depth_write_enable; | ||||
|         const bool stencil_write = | ||||
|             has_stencil && depth_stencil.stencil_test_enable && dynamic.stencil_write_mask != 0; | ||||
|  | ||||
|         return depth_write || stencil_write; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| struct Shader : public Common::AsyncHandle { | ||||
|     explicit Shader(const Instance& instance); | ||||
|     explicit Shader(const Instance& instance, vk::ShaderStageFlagBits stage, std::string code); | ||||
|     ~Shader(); | ||||
|  | ||||
|     [[nodiscard]] vk::ShaderModule Handle() const noexcept { | ||||
|         return module; | ||||
|     } | ||||
|  | ||||
|     vk::ShaderModule module; | ||||
|     vk::Device device; | ||||
|     std::string program; | ||||
| }; | ||||
|  | ||||
| class GraphicsPipeline : public Common::AsyncHandle { | ||||
| public: | ||||
|     explicit GraphicsPipeline(const Instance& instance, RenderpassCache& renderpass_cache, | ||||
|                               const PipelineInfo& info, vk::PipelineCache pipeline_cache, | ||||
|                               vk::PipelineLayout layout, std::array<Shader*, 3> stages, | ||||
|                               Common::ThreadWorker* worker); | ||||
|     ~GraphicsPipeline(); | ||||
|  | ||||
|     bool TryBuild(bool wait_built); | ||||
|  | ||||
|     bool Build(bool fail_on_compile_required = false); | ||||
|  | ||||
|     [[nodiscard]] vk::Pipeline Handle() const noexcept { | ||||
|         return *pipeline; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const Instance& instance; | ||||
|     RenderpassCache& renderpass_cache; | ||||
|     Common::ThreadWorker* worker; | ||||
|  | ||||
|     vk::UniquePipeline pipeline; | ||||
|     vk::PipelineLayout pipeline_layout; | ||||
|     vk::PipelineCache pipeline_cache; | ||||
|  | ||||
|     PipelineInfo info; | ||||
|     std::array<Shader*, 3> stages; | ||||
|     bool is_pending{}; | ||||
| }; | ||||
|  | ||||
| } // namespace Vulkan | ||||
| @@ -14,6 +14,10 @@ | ||||
|  | ||||
| #include <vk_mem_alloc.h> | ||||
|  | ||||
| #ifdef __APPLE__ | ||||
| #include <mvk_config.h> | ||||
| #endif | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| namespace { | ||||
| @@ -130,12 +134,12 @@ Instance::Instance(bool enable_validation, bool dump_command_buffers) | ||||
|       physical_devices{instance->enumeratePhysicalDevices()} {} | ||||
|  | ||||
| Instance::Instance(Frontend::EmuWindow& window, u32 physical_device_index) | ||||
|     : library{OpenLibrary()}, instance{CreateInstance( | ||||
|                                   *library, window.GetWindowInfo().type, | ||||
|                                   Settings::values.renderer_debug.GetValue(), | ||||
|                                   Settings::values.dump_command_buffers.GetValue())}, | ||||
|       debug_callback{CreateDebugCallback(*instance)}, physical_devices{ | ||||
|                                                           instance->enumeratePhysicalDevices()} { | ||||
|     : library{OpenLibrary(&window)}, instance{CreateInstance( | ||||
|                                          *library, window.GetWindowInfo().type, | ||||
|                                          Settings::values.renderer_debug.GetValue(), | ||||
|                                          Settings::values.dump_command_buffers.GetValue())}, | ||||
|       debug_callback{CreateDebugCallback(*instance, debug_utils_supported)}, | ||||
|       physical_devices{instance->enumeratePhysicalDevices()} { | ||||
|     const std::size_t num_physical_devices = static_cast<u16>(physical_devices.size()); | ||||
|     ASSERT_MSG(physical_device_index < num_physical_devices, | ||||
|                "Invalid physical device index {} provided when only {} devices exist", | ||||
| @@ -146,6 +150,7 @@ Instance::Instance(Frontend::EmuWindow& window, u32 physical_device_index) | ||||
|  | ||||
|     CollectTelemetryParameters(); | ||||
|     CreateDevice(); | ||||
|     CollectToolingInfo(); | ||||
|     CreateFormatTable(); | ||||
|     CreateCustomFormatTable(); | ||||
|     CreateAttribTable(); | ||||
| @@ -209,12 +214,16 @@ FormatTraits Instance::DetermineTraits(VideoCore::PixelFormat pixel_format, vk:: | ||||
|         best_usage |= vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst | | ||||
|                       vk::ImageUsageFlagBits::eTransferSrc; | ||||
|     } | ||||
|     if (supports_attachment) { | ||||
|     // Attachment flag is only needed for color and depth formats. | ||||
|     if (supports_attachment && | ||||
|         VideoCore::GetFormatType(pixel_format) != VideoCore::SurfaceType::Texture) { | ||||
|         best_usage |= (format_aspect & vk::ImageAspectFlagBits::eDepth) | ||||
|                           ? vk::ImageUsageFlagBits::eDepthStencilAttachment | ||||
|                           : vk::ImageUsageFlagBits::eColorAttachment; | ||||
|     } | ||||
|     if (supports_storage) { | ||||
|     // Storage flag is only needed for shadow rendering with RGBA8 texture. | ||||
|     // Keeping it disables can boost performance on mobile drivers. | ||||
|     if (supports_storage && pixel_format == VideoCore::PixelFormat::RGBA8) { | ||||
|         best_usage |= vk::ImageUsageFlagBits::eStorage; | ||||
|     } | ||||
|  | ||||
| @@ -364,6 +373,7 @@ bool Instance::CreateDevice() { | ||||
|         vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT, | ||||
|         vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR, | ||||
|         vk::PhysicalDeviceCustomBorderColorFeaturesEXT, vk::PhysicalDeviceIndexTypeUint8FeaturesEXT, | ||||
|         vk::PhysicalDeviceFragmentShaderInterlockFeaturesEXT, | ||||
|         vk::PhysicalDevicePipelineCreationCacheControlFeaturesEXT>(); | ||||
|     const vk::StructureChain properties_chain = | ||||
|         physical_device.getProperties2<vk::PhysicalDeviceProperties2, | ||||
| @@ -397,12 +407,14 @@ bool Instance::CreateDevice() { | ||||
|         return false; | ||||
|     }; | ||||
|  | ||||
|     const bool is_nvidia = driver_id == vk::DriverIdKHR::eNvidiaProprietary; | ||||
|     const bool is_arm = driver_id == vk::DriverIdKHR::eArmProprietary; | ||||
|     const bool is_qualcomm = driver_id == vk::DriverIdKHR::eQualcommProprietary; | ||||
|  | ||||
|     add_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME); | ||||
|     image_format_list = add_extension(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME); | ||||
|     shader_stencil_export = add_extension(VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME); | ||||
|     tooling_info = add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME); | ||||
|     const bool has_timeline_semaphores = add_extension( | ||||
|         VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME, is_qualcomm, "it is broken on Qualcomm drivers"); | ||||
|     const bool has_portability_subset = add_extension(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); | ||||
| @@ -413,8 +425,12 @@ bool Instance::CreateDevice() { | ||||
|         add_extension(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME, is_qualcomm, | ||||
|                       "it is broken on most Qualcomm driver versions"); | ||||
|     const bool has_index_type_uint8 = add_extension(VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME); | ||||
|     const bool has_fragment_shader_interlock = | ||||
|         add_extension(VK_EXT_FRAGMENT_SHADER_INTERLOCK_EXTENSION_NAME, is_nvidia, | ||||
|                       "it is broken on Nvidia drivers"); | ||||
|     const bool has_pipeline_creation_cache_control = | ||||
|         add_extension(VK_EXT_PIPELINE_CREATION_CACHE_CONTROL_EXTENSION_NAME); | ||||
|         add_extension(VK_EXT_PIPELINE_CREATION_CACHE_CONTROL_EXTENSION_NAME, is_nvidia, | ||||
|                       "it is broken on Nvidia drivers"); | ||||
|  | ||||
|     const auto family_properties = physical_device.getQueueFamilyProperties(); | ||||
|     if (family_properties.empty()) { | ||||
| @@ -453,10 +469,9 @@ bool Instance::CreateDevice() { | ||||
|         }, | ||||
|         vk::PhysicalDeviceFeatures2{ | ||||
|             .features{ | ||||
|                 .robustBufferAccess = features.robustBufferAccess, | ||||
|                 .geometryShader = features.geometryShader, | ||||
|                 .logicOp = features.logicOp, | ||||
|                 .depthClamp = features.depthClamp, | ||||
|                 .largePoints = features.largePoints, | ||||
|                 .samplerAnisotropy = features.samplerAnisotropy, | ||||
|                 .fragmentStoresAndAtomics = features.fragmentStoresAndAtomics, | ||||
|                 .shaderClipDistance = features.shaderClipDistance, | ||||
| @@ -469,6 +484,7 @@ bool Instance::CreateDevice() { | ||||
|         vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT{}, | ||||
|         vk::PhysicalDeviceCustomBorderColorFeaturesEXT{}, | ||||
|         vk::PhysicalDeviceIndexTypeUint8FeaturesEXT{}, | ||||
|         vk::PhysicalDeviceFragmentShaderInterlockFeaturesEXT{}, | ||||
|         vk::PhysicalDevicePipelineCreationCacheControlFeaturesEXT{}, | ||||
|     }; | ||||
|  | ||||
| @@ -507,6 +523,13 @@ bool Instance::CreateDevice() { | ||||
|         device_chain.unlink<vk::PhysicalDeviceIndexTypeUint8FeaturesEXT>(); | ||||
|     } | ||||
|  | ||||
|     if (has_fragment_shader_interlock) { | ||||
|         FEAT_SET(vk::PhysicalDeviceFragmentShaderInterlockFeaturesEXT, fragmentShaderPixelInterlock, | ||||
|                  fragment_shader_interlock) | ||||
|     } else { | ||||
|         device_chain.unlink<vk::PhysicalDeviceFragmentShaderInterlockFeaturesEXT>(); | ||||
|     } | ||||
|  | ||||
|     if (has_extended_dynamic_state) { | ||||
|         FEAT_SET(vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT, extendedDynamicState, | ||||
|                  extended_dynamic_state) | ||||
| @@ -533,6 +556,12 @@ bool Instance::CreateDevice() { | ||||
| #undef PROP_GET | ||||
| #undef FEAT_SET | ||||
|  | ||||
| #ifdef __APPLE__ | ||||
|     if (!SetMoltenVkConfig()) { | ||||
|         LOG_WARNING(Render_Vulkan, "Unable to set MoltenVK configuration"); | ||||
|     } | ||||
| #endif | ||||
|  | ||||
|     try { | ||||
|         device = physical_device.createDeviceUnique(device_chain.get()); | ||||
|     } catch (vk::ExtensionNotPresentError& err) { | ||||
| @@ -580,4 +609,55 @@ void Instance::CollectTelemetryParameters() { | ||||
|     vendor_name = driver.driverName.data(); | ||||
| } | ||||
|  | ||||
| void Instance::CollectToolingInfo() { | ||||
|     if (!tooling_info) { | ||||
|         return; | ||||
|     } | ||||
|     const auto tools = physical_device.getToolProperties(); | ||||
|     for (const vk::PhysicalDeviceToolProperties& tool : tools) { | ||||
|         const std::string_view name = tool.name; | ||||
|         LOG_INFO(Render_Vulkan, "Attached debugging tool: {}", name); | ||||
|         has_renderdoc = has_renderdoc || name == "RenderDoc"; | ||||
|         has_nsight_graphics = has_nsight_graphics || name == "NVIDIA Nsight Graphics"; | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool Instance::SetMoltenVkConfig() { | ||||
| #ifdef __APPLE__ | ||||
|     size_t mvk_config_size = sizeof(MVKConfiguration); | ||||
|     MVKConfiguration mvk_config{}; | ||||
|  | ||||
|     const auto _vkGetMoltenVKConfigurationMVK = | ||||
|         library->GetSymbol<PFN_vkGetMoltenVKConfigurationMVK>("vkGetMoltenVKConfigurationMVK"); | ||||
|     if (!_vkGetMoltenVKConfigurationMVK) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     const auto _vkSetMoltenVKConfigurationMVK = | ||||
|         library->GetSymbol<PFN_vkSetMoltenVKConfigurationMVK>("vkSetMoltenVKConfigurationMVK"); | ||||
|     if (!_vkSetMoltenVKConfigurationMVK) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (_vkGetMoltenVKConfigurationMVK(VK_NULL_HANDLE, &mvk_config, &mvk_config_size) != | ||||
|         VK_SUCCESS) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // Use synchronous queue submits if async presentation is enabled, to avoid threading | ||||
|     // indirection. | ||||
|     mvk_config.synchronousQueueSubmits = Settings::values.async_presentation.GetValue(); | ||||
|     // If the device is lost, make an attempt to resume if possible to avoid crashes. | ||||
|     mvk_config.resumeLostDevice = true; | ||||
|     // Maximize concurrency to improve shader compilation performance. | ||||
|     mvk_config.shouldMaximizeConcurrentCompilation = true; | ||||
|  | ||||
|     if (_vkSetMoltenVKConfigurationMVK(VK_NULL_HANDLE, &mvk_config, &mvk_config_size) != | ||||
|         VK_SUCCESS) { | ||||
|         return false; | ||||
|     } | ||||
| #endif | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| } // namespace Vulkan | ||||
|   | ||||
| @@ -90,6 +90,16 @@ public: | ||||
|         return present_queue; | ||||
|     } | ||||
|  | ||||
|     /// Returns true when a known debugging tool is attached. | ||||
|     bool HasDebuggingToolAttached() const { | ||||
|         return has_renderdoc || has_nsight_graphics; | ||||
|     } | ||||
|  | ||||
|     /// Returns true when VK_EXT_debug_utils is supported. | ||||
|     bool IsExtDebugUtilsSupported() const { | ||||
|         return debug_utils_supported; | ||||
|     } | ||||
|  | ||||
|     /// Returns true if logic operations need shader emulation | ||||
|     bool NeedsLogicOpEmulation() const { | ||||
|         return !features.logicOp; | ||||
| @@ -130,6 +140,11 @@ public: | ||||
|         return index_type_uint8; | ||||
|     } | ||||
|  | ||||
|     /// Returns true when VK_EXT_fragment_shader_interlock is supported | ||||
|     bool IsFragmentShaderInterlockSupported() const { | ||||
|         return fragment_shader_interlock; | ||||
|     } | ||||
|  | ||||
|     /// Returns true when VK_KHR_image_format_list is supported | ||||
|     bool IsImageFormatListSupported() const { | ||||
|         return image_format_list; | ||||
| @@ -145,11 +160,6 @@ public: | ||||
|         return shader_stencil_export; | ||||
|     } | ||||
|  | ||||
|     /// Returns true if VK_EXT_debug_utils is supported | ||||
|     bool IsExtDebugUtilsSupported() const { | ||||
|         return debug_messenger_supported; | ||||
|     } | ||||
|  | ||||
|     /// Returns the vendor ID of the physical device | ||||
|     u32 GetVendorID() const { | ||||
|         return properties.vendorID; | ||||
| @@ -200,6 +210,11 @@ public: | ||||
|         return properties.limits.minUniformBufferOffsetAlignment; | ||||
|     } | ||||
|  | ||||
|     /// Returns the minimum alignemt required for accessing host-mapped device memory | ||||
|     vk::DeviceSize NonCoherentAtomSize() const { | ||||
|         return properties.limits.nonCoherentAtomSize; | ||||
|     } | ||||
|  | ||||
|     /// Returns the maximum supported elements in a texel buffer | ||||
|     u32 MaxTexelBufferElements() const { | ||||
|         return properties.limits.maxTexelBufferElements; | ||||
| @@ -249,6 +264,10 @@ private: | ||||
|  | ||||
|     /// Collects telemetry information from the device. | ||||
|     void CollectTelemetryParameters(); | ||||
|     void CollectToolingInfo(); | ||||
|  | ||||
|     /// Sets MoltenVK configuration to the desired state. | ||||
|     bool SetMoltenVkConfig(); | ||||
|  | ||||
| private: | ||||
|     std::shared_ptr<Common::DynamicLibrary> library; | ||||
| @@ -277,10 +296,14 @@ private: | ||||
|     bool extended_dynamic_state{}; | ||||
|     bool custom_border_color{}; | ||||
|     bool index_type_uint8{}; | ||||
|     bool fragment_shader_interlock{}; | ||||
|     bool image_format_list{}; | ||||
|     bool pipeline_creation_cache_control{}; | ||||
|     bool shader_stencil_export{}; | ||||
|     bool debug_messenger_supported{}; | ||||
|     bool tooling_info{}; | ||||
|     bool debug_utils_supported{}; | ||||
|     bool has_nsight_graphics{}; | ||||
|     bool has_renderdoc{}; | ||||
| }; | ||||
|  | ||||
| } // namespace Vulkan | ||||
|   | ||||
							
								
								
									
										207
									
								
								src/video_core/renderer_vulkan/vk_master_semaphore.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								src/video_core/renderer_vulkan/vk_master_semaphore.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,207 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #include <limits> | ||||
| #include "video_core/renderer_vulkan/vk_instance.h" | ||||
| #include "video_core/renderer_vulkan/vk_master_semaphore.h" | ||||
| #include "video_core/renderer_vulkan/vk_scheduler.h" | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| constexpr u64 WAIT_TIMEOUT = std::numeric_limits<u64>::max(); | ||||
|  | ||||
| MasterSemaphoreTimeline::MasterSemaphoreTimeline(const Instance& instance_) : instance{instance_} { | ||||
|     const vk::StructureChain semaphore_chain = { | ||||
|         vk::SemaphoreCreateInfo{}, | ||||
|         vk::SemaphoreTypeCreateInfoKHR{ | ||||
|             .semaphoreType = vk::SemaphoreType::eTimeline, | ||||
|             .initialValue = 0, | ||||
|         }, | ||||
|     }; | ||||
|     semaphore = instance.GetDevice().createSemaphoreUnique(semaphore_chain.get()); | ||||
| } | ||||
|  | ||||
| MasterSemaphoreTimeline::~MasterSemaphoreTimeline() = default; | ||||
|  | ||||
| void MasterSemaphoreTimeline::Refresh() { | ||||
|     u64 this_tick{}; | ||||
|     u64 counter{}; | ||||
|     do { | ||||
|         this_tick = gpu_tick.load(std::memory_order_acquire); | ||||
|         counter = instance.GetDevice().getSemaphoreCounterValueKHR(*semaphore); | ||||
|         if (counter < this_tick) { | ||||
|             return; | ||||
|         } | ||||
|     } while (!gpu_tick.compare_exchange_weak(this_tick, counter, std::memory_order_release, | ||||
|                                              std::memory_order_relaxed)); | ||||
| } | ||||
|  | ||||
| void MasterSemaphoreTimeline::Wait(u64 tick) { | ||||
|     // No need to wait if the GPU is ahead of the tick | ||||
|     if (IsFree(tick)) { | ||||
|         return; | ||||
|     } | ||||
|     // Update the GPU tick and try again | ||||
|     Refresh(); | ||||
|     if (IsFree(tick)) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // If none of the above is hit, fallback to a regular wait | ||||
|     const vk::SemaphoreWaitInfoKHR wait_info = { | ||||
|         .semaphoreCount = 1, | ||||
|         .pSemaphores = &semaphore.get(), | ||||
|         .pValues = &tick, | ||||
|     }; | ||||
|  | ||||
|     while (instance.GetDevice().waitSemaphoresKHR(&wait_info, WAIT_TIMEOUT) != | ||||
|            vk::Result::eSuccess) { | ||||
|     } | ||||
|     Refresh(); | ||||
| } | ||||
|  | ||||
| void MasterSemaphoreTimeline::SubmitWork(vk::CommandBuffer cmdbuf, vk::Semaphore wait, | ||||
|                                          vk::Semaphore signal, u64 signal_value) { | ||||
|     cmdbuf.end(); | ||||
|  | ||||
|     const u32 num_signal_semaphores = signal ? 2U : 1U; | ||||
|     const std::array signal_values{signal_value, u64(0)}; | ||||
|     const std::array signal_semaphores{Handle(), signal}; | ||||
|  | ||||
|     const u32 num_wait_semaphores = wait ? 2U : 1U; | ||||
|     const std::array wait_values{signal_value - 1, u64(1)}; | ||||
|     const std::array wait_semaphores{Handle(), wait}; | ||||
|  | ||||
|     static constexpr std::array<vk::PipelineStageFlags, 2> wait_stage_masks = { | ||||
|         vk::PipelineStageFlagBits::eAllCommands, | ||||
|         vk::PipelineStageFlagBits::eColorAttachmentOutput, | ||||
|     }; | ||||
|  | ||||
|     const vk::TimelineSemaphoreSubmitInfoKHR timeline_si = { | ||||
|         .waitSemaphoreValueCount = num_wait_semaphores, | ||||
|         .pWaitSemaphoreValues = wait_values.data(), | ||||
|         .signalSemaphoreValueCount = num_signal_semaphores, | ||||
|         .pSignalSemaphoreValues = signal_values.data(), | ||||
|     }; | ||||
|  | ||||
|     const vk::SubmitInfo submit_info = { | ||||
|         .pNext = &timeline_si, | ||||
|         .waitSemaphoreCount = num_wait_semaphores, | ||||
|         .pWaitSemaphores = wait_semaphores.data(), | ||||
|         .pWaitDstStageMask = wait_stage_masks.data(), | ||||
|         .commandBufferCount = 1u, | ||||
|         .pCommandBuffers = &cmdbuf, | ||||
|         .signalSemaphoreCount = num_signal_semaphores, | ||||
|         .pSignalSemaphores = signal_semaphores.data(), | ||||
|     }; | ||||
|  | ||||
|     try { | ||||
|         instance.GetGraphicsQueue().submit(submit_info); | ||||
|     } catch (vk::DeviceLostError& err) { | ||||
|         LOG_CRITICAL(Render_Vulkan, "Device lost during submit: {}", err.what()); | ||||
|         UNREACHABLE(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| constexpr u64 FENCE_RESERVE = 8; | ||||
|  | ||||
| MasterSemaphoreFence::MasterSemaphoreFence(const Instance& instance_) : instance{instance_} { | ||||
|     const vk::Device device{instance.GetDevice()}; | ||||
|     for (u64 i = 0; i < FENCE_RESERVE; i++) { | ||||
|         free_queue.push(device.createFenceUnique({})); | ||||
|     } | ||||
|     wait_thread = std::jthread([this](std::stop_token token) { WaitThread(token); }); | ||||
| } | ||||
|  | ||||
| MasterSemaphoreFence::~MasterSemaphoreFence() = default; | ||||
|  | ||||
| void MasterSemaphoreFence::Refresh() {} | ||||
|  | ||||
| void MasterSemaphoreFence::Wait(u64 tick) { | ||||
|     while (true) { | ||||
|         u64 current_value = gpu_tick.load(std::memory_order_relaxed); | ||||
|         if (current_value >= tick) { | ||||
|             return; | ||||
|         } | ||||
|         gpu_tick.wait(current_value); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void MasterSemaphoreFence::SubmitWork(vk::CommandBuffer cmdbuf, vk::Semaphore wait, | ||||
|                                       vk::Semaphore signal, u64 signal_value) { | ||||
|     cmdbuf.end(); | ||||
|  | ||||
|     const u32 num_signal_semaphores = signal ? 1U : 0U; | ||||
|     const u32 num_wait_semaphores = wait ? 1U : 0U; | ||||
|  | ||||
|     static constexpr std::array<vk::PipelineStageFlags, 1> wait_stage_masks = { | ||||
|         vk::PipelineStageFlagBits::eColorAttachmentOutput, | ||||
|     }; | ||||
|  | ||||
|     const vk::SubmitInfo submit_info = { | ||||
|         .waitSemaphoreCount = num_wait_semaphores, | ||||
|         .pWaitSemaphores = &wait, | ||||
|         .pWaitDstStageMask = wait_stage_masks.data(), | ||||
|         .commandBufferCount = 1u, | ||||
|         .pCommandBuffers = &cmdbuf, | ||||
|         .signalSemaphoreCount = num_signal_semaphores, | ||||
|         .pSignalSemaphores = &signal, | ||||
|     }; | ||||
|  | ||||
|     vk::UniqueFence fence{GetFreeFence()}; | ||||
|     try { | ||||
|         instance.GetGraphicsQueue().submit(submit_info, *fence); | ||||
|     } catch (vk::DeviceLostError& err) { | ||||
|         LOG_CRITICAL(Render_Vulkan, "Device lost during submit: {}", err.what()); | ||||
|         UNREACHABLE(); | ||||
|     } | ||||
|  | ||||
|     std::scoped_lock lock{wait_mutex}; | ||||
|     wait_queue.push({ | ||||
|         .handle = std::move(fence), | ||||
|         .signal_value = signal_value, | ||||
|     }); | ||||
|     wait_cv.notify_one(); | ||||
| } | ||||
|  | ||||
| void MasterSemaphoreFence::WaitThread(std::stop_token token) { | ||||
|     const vk::Device device{instance.GetDevice()}; | ||||
|     while (!token.stop_requested()) { | ||||
|         Fence fence; | ||||
|         { | ||||
|             std::unique_lock lock{wait_mutex}; | ||||
|             Common::CondvarWait(wait_cv, lock, token, [this] { return !wait_queue.empty(); }); | ||||
|             if (token.stop_requested()) { | ||||
|                 return; | ||||
|             } | ||||
|             fence = std::move(wait_queue.front()); | ||||
|             wait_queue.pop(); | ||||
|         } | ||||
|  | ||||
|         const vk::Result result = device.waitForFences(*fence.handle, true, WAIT_TIMEOUT); | ||||
|         if (result != vk::Result::eSuccess) { | ||||
|             LOG_CRITICAL(Render_Vulkan, "Fence wait failed with error {}", vk::to_string(result)); | ||||
|             UNREACHABLE(); | ||||
|         } | ||||
|         device.resetFences(*fence.handle); | ||||
|  | ||||
|         gpu_tick.store(fence.signal_value); | ||||
|         gpu_tick.notify_all(); | ||||
|  | ||||
|         std::scoped_lock lock{free_mutex}; | ||||
|         free_queue.push(std::move(fence.handle)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| vk::UniqueFence MasterSemaphoreFence::GetFreeFence() { | ||||
|     std::scoped_lock lock{free_mutex}; | ||||
|     if (free_queue.empty()) { | ||||
|         return instance.GetDevice().createFenceUnique({}); | ||||
|     } | ||||
|  | ||||
|     vk::UniqueFence fence{std::move(free_queue.front())}; | ||||
|     free_queue.pop(); | ||||
|     return fence; | ||||
| } | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										107
									
								
								src/video_core/renderer_vulkan/vk_master_semaphore.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/video_core/renderer_vulkan/vk_master_semaphore.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <atomic> | ||||
| #include <condition_variable> | ||||
| #include <queue> | ||||
| #include "common/common_types.h" | ||||
| #include "common/polyfill_thread.h" | ||||
| #include "video_core/renderer_vulkan/vk_common.h" | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| class Instance; | ||||
| class Scheduler; | ||||
|  | ||||
| class MasterSemaphore { | ||||
| public: | ||||
|     virtual ~MasterSemaphore() = default; | ||||
|  | ||||
|     [[nodiscard]] u64 CurrentTick() const noexcept { | ||||
|         return current_tick.load(std::memory_order_acquire); | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] u64 KnownGpuTick() const noexcept { | ||||
|         return gpu_tick.load(std::memory_order_acquire); | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] bool IsFree(u64 tick) const noexcept { | ||||
|         return KnownGpuTick() >= tick; | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] u64 NextTick() noexcept { | ||||
|         return current_tick.fetch_add(1, std::memory_order_release); | ||||
|     } | ||||
|  | ||||
|     /// Refresh the known GPU tick | ||||
|     virtual void Refresh() = 0; | ||||
|  | ||||
|     /// Waits for a tick to be hit on the GPU | ||||
|     virtual void Wait(u64 tick) = 0; | ||||
|  | ||||
|     /// Submits the provided command buffer for execution | ||||
|     virtual void SubmitWork(vk::CommandBuffer cmdbuf, vk::Semaphore wait, vk::Semaphore signal, | ||||
|                             u64 signal_value) = 0; | ||||
|  | ||||
| protected: | ||||
|     std::atomic<u64> gpu_tick{0};     ///< Current known GPU tick. | ||||
|     std::atomic<u64> current_tick{1}; ///< Current logical tick. | ||||
| }; | ||||
|  | ||||
| class MasterSemaphoreTimeline : public MasterSemaphore { | ||||
| public: | ||||
|     explicit MasterSemaphoreTimeline(const Instance& instance); | ||||
|     ~MasterSemaphoreTimeline() override; | ||||
|  | ||||
|     [[nodiscard]] vk::Semaphore Handle() const noexcept { | ||||
|         return semaphore.get(); | ||||
|     } | ||||
|  | ||||
|     void Refresh() override; | ||||
|  | ||||
|     void Wait(u64 tick) override; | ||||
|  | ||||
|     void SubmitWork(vk::CommandBuffer cmdbuf, vk::Semaphore wait, vk::Semaphore signal, | ||||
|                     u64 signal_value) override; | ||||
|  | ||||
| private: | ||||
|     const Instance& instance; | ||||
|     vk::UniqueSemaphore semaphore; ///< Timeline semaphore. | ||||
| }; | ||||
|  | ||||
| class MasterSemaphoreFence : public MasterSemaphore { | ||||
| public: | ||||
|     explicit MasterSemaphoreFence(const Instance& instance); | ||||
|     ~MasterSemaphoreFence() override; | ||||
|  | ||||
|     void Refresh() override; | ||||
|  | ||||
|     void Wait(u64 tick) override; | ||||
|  | ||||
|     void SubmitWork(vk::CommandBuffer cmdbuf, vk::Semaphore wait, vk::Semaphore signal, | ||||
|                     u64 signal_value) override; | ||||
|  | ||||
| private: | ||||
|     void WaitThread(std::stop_token token); | ||||
|  | ||||
|     vk::UniqueFence GetFreeFence(); | ||||
|  | ||||
| private: | ||||
|     const Instance& instance; | ||||
|  | ||||
|     struct Fence { | ||||
|         vk::UniqueFence handle; | ||||
|         u64 signal_value; | ||||
|     }; | ||||
|  | ||||
|     std::queue<vk::UniqueFence> free_queue; | ||||
|     std::queue<Fence> wait_queue; | ||||
|     std::mutex free_mutex; | ||||
|     std::mutex wait_mutex; | ||||
|     std::condition_variable_any wait_cv; | ||||
|     std::jthread wait_thread; | ||||
| }; | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										524
									
								
								src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										524
									
								
								src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,524 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <boost/container/static_vector.hpp> | ||||
|  | ||||
| #include "common/common_paths.h" | ||||
| #include "common/file_util.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/microprofile.h" | ||||
| #include "common/settings.h" | ||||
| #include "video_core/renderer_vulkan/pica_to_vk.h" | ||||
| #include "video_core/renderer_vulkan/vk_instance.h" | ||||
| #include "video_core/renderer_vulkan/vk_pipeline_cache.h" | ||||
| #include "video_core/renderer_vulkan/vk_renderpass_cache.h" | ||||
| #include "video_core/renderer_vulkan/vk_scheduler.h" | ||||
| #include "video_core/renderer_vulkan/vk_shader_gen_spv.h" | ||||
| #include "video_core/renderer_vulkan/vk_shader_util.h" | ||||
|  | ||||
| MICROPROFILE_DEFINE(Vulkan_Bind, "Vulkan", "Pipeline Bind", MP_RGB(192, 32, 32)); | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| enum ProgramType : u32 { | ||||
|     VS = 0, | ||||
|     GS = 2, | ||||
|     FS = 1, | ||||
| }; | ||||
|  | ||||
| u32 AttribBytes(Pica::PipelineRegs::VertexAttributeFormat format, u32 size) { | ||||
|     switch (format) { | ||||
|     case Pica::PipelineRegs::VertexAttributeFormat::FLOAT: | ||||
|         return sizeof(float) * size; | ||||
|     case Pica::PipelineRegs::VertexAttributeFormat::SHORT: | ||||
|         return sizeof(u16) * size; | ||||
|     case Pica::PipelineRegs::VertexAttributeFormat::BYTE: | ||||
|     case Pica::PipelineRegs::VertexAttributeFormat::UBYTE: | ||||
|         return sizeof(u8) * size; | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| AttribLoadFlags MakeAttribLoadFlag(Pica::PipelineRegs::VertexAttributeFormat format) { | ||||
|     switch (format) { | ||||
|     case Pica::PipelineRegs::VertexAttributeFormat::BYTE: | ||||
|     case Pica::PipelineRegs::VertexAttributeFormat::SHORT: | ||||
|         return AttribLoadFlags::Sint; | ||||
|     case Pica::PipelineRegs::VertexAttributeFormat::UBYTE: | ||||
|         return AttribLoadFlags::Uint; | ||||
|     default: | ||||
|         return AttribLoadFlags::Float; | ||||
|     } | ||||
| } | ||||
|  | ||||
| constexpr std::array<vk::DescriptorSetLayoutBinding, 5> BUFFER_BINDINGS = {{ | ||||
|     {0, vk::DescriptorType::eUniformBufferDynamic, 1, vk::ShaderStageFlagBits::eVertex}, | ||||
|     {1, vk::DescriptorType::eUniformBufferDynamic, 1, | ||||
|      vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eGeometry | | ||||
|          vk::ShaderStageFlagBits::eFragment}, | ||||
|     {2, vk::DescriptorType::eUniformTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment}, | ||||
|     {3, vk::DescriptorType::eUniformTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment}, | ||||
|     {4, vk::DescriptorType::eUniformTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment}, | ||||
| }}; | ||||
|  | ||||
| constexpr std::array<vk::DescriptorSetLayoutBinding, 4> TEXTURE_BINDINGS = {{ | ||||
|     {0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, | ||||
|     {1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, | ||||
|     {2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, | ||||
|     {3, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, | ||||
| }}; | ||||
|  | ||||
| // TODO: Use descriptor array for shadow cube | ||||
| constexpr std::array<vk::DescriptorSetLayoutBinding, 7> SHADOW_BINDINGS = {{ | ||||
|     {0, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment}, | ||||
|     {1, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment}, | ||||
|     {2, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment}, | ||||
|     {3, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment}, | ||||
|     {4, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment}, | ||||
|     {5, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment}, | ||||
|     {6, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment}, | ||||
| }}; | ||||
|  | ||||
| PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, | ||||
|                              RenderpassCache& renderpass_cache_, DescriptorPool& pool_) | ||||
|     : instance{instance_}, scheduler{scheduler_}, renderpass_cache{renderpass_cache_}, pool{pool_}, | ||||
|       num_worker_threads{std::max(std::thread::hardware_concurrency(), 2U)}, | ||||
|       workers{num_worker_threads, "Pipeline workers"}, | ||||
|       descriptor_set_providers{DescriptorSetProvider{instance, pool, BUFFER_BINDINGS}, | ||||
|                                DescriptorSetProvider{instance, pool, TEXTURE_BINDINGS}, | ||||
|                                DescriptorSetProvider{instance, pool, SHADOW_BINDINGS}}, | ||||
|       trivial_vertex_shader{instance, vk::ShaderStageFlagBits::eVertex, | ||||
|                             GenerateTrivialVertexShader(instance.IsShaderClipDistanceSupported())} { | ||||
|     BuildLayout(); | ||||
| } | ||||
|  | ||||
| void PipelineCache::BuildLayout() { | ||||
|     std::array<vk::DescriptorSetLayout, NUM_RASTERIZER_SETS> descriptor_set_layouts; | ||||
|     std::transform(descriptor_set_providers.begin(), descriptor_set_providers.end(), | ||||
|                    descriptor_set_layouts.begin(), | ||||
|                    [](const auto& provider) { return provider.Layout(); }); | ||||
|  | ||||
|     const vk::PipelineLayoutCreateInfo layout_info = { | ||||
|         .setLayoutCount = NUM_RASTERIZER_SETS, | ||||
|         .pSetLayouts = descriptor_set_layouts.data(), | ||||
|         .pushConstantRangeCount = 0, | ||||
|         .pPushConstantRanges = nullptr, | ||||
|     }; | ||||
|     pipeline_layout = instance.GetDevice().createPipelineLayoutUnique(layout_info); | ||||
| } | ||||
|  | ||||
| PipelineCache::~PipelineCache() { | ||||
|     SaveDiskCache(); | ||||
| } | ||||
|  | ||||
| void PipelineCache::LoadDiskCache() { | ||||
|     if (!Settings::values.use_disk_shader_cache || !EnsureDirectories()) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const std::string cache_file_path = fmt::format("{}{:x}{:x}.bin", GetPipelineCacheDir(), | ||||
|                                                     instance.GetVendorID(), instance.GetDeviceID()); | ||||
|     vk::PipelineCacheCreateInfo cache_info = { | ||||
|         .initialDataSize = 0, | ||||
|         .pInitialData = nullptr, | ||||
|     }; | ||||
|  | ||||
|     std::vector<u8> cache_data; | ||||
|     FileUtil::IOFile cache_file{cache_file_path, "r"}; | ||||
|     if (cache_file.IsOpen()) { | ||||
|         LOG_INFO(Render_Vulkan, "Loading pipeline cache"); | ||||
|  | ||||
|         const u64 cache_file_size = cache_file.GetSize(); | ||||
|         cache_data.resize(cache_file_size); | ||||
|         if (cache_file.ReadBytes(cache_data.data(), cache_file_size)) { | ||||
|             if (!IsCacheValid(cache_data)) { | ||||
|                 LOG_WARNING(Render_Vulkan, "Pipeline cache provided invalid, ignoring"); | ||||
|             } else { | ||||
|                 cache_info.initialDataSize = cache_file_size; | ||||
|                 cache_info.pInitialData = cache_data.data(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         cache_file.Close(); | ||||
|     } | ||||
|  | ||||
|     vk::Device device = instance.GetDevice(); | ||||
|     pipeline_cache = device.createPipelineCacheUnique(cache_info); | ||||
| } | ||||
|  | ||||
| void PipelineCache::SaveDiskCache() { | ||||
|     if (!Settings::values.use_disk_shader_cache || !EnsureDirectories() || !pipeline_cache) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const std::string cache_file_path = fmt::format("{}{:x}{:x}.bin", GetPipelineCacheDir(), | ||||
|                                                     instance.GetVendorID(), instance.GetDeviceID()); | ||||
|     FileUtil::IOFile cache_file{cache_file_path, "wb"}; | ||||
|     if (!cache_file.IsOpen()) { | ||||
|         LOG_ERROR(Render_Vulkan, "Unable to open pipeline cache for writing"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     vk::Device device = instance.GetDevice(); | ||||
|     auto cache_data = device.getPipelineCacheData(*pipeline_cache); | ||||
|     if (!cache_file.WriteBytes(cache_data.data(), cache_data.size())) { | ||||
|         LOG_ERROR(Render_Vulkan, "Error during pipeline cache write"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     cache_file.Close(); | ||||
| } | ||||
|  | ||||
| bool PipelineCache::BindPipeline(const PipelineInfo& info, bool wait_built) { | ||||
|     MICROPROFILE_SCOPE(Vulkan_Bind); | ||||
|  | ||||
|     u64 shader_hash = 0; | ||||
|     for (u32 i = 0; i < MAX_SHADER_STAGES; i++) { | ||||
|         shader_hash = Common::HashCombine(shader_hash, shader_hashes[i]); | ||||
|     } | ||||
|  | ||||
|     const u64 info_hash = info.Hash(instance); | ||||
|     const u64 pipeline_hash = Common::HashCombine(shader_hash, info_hash); | ||||
|  | ||||
|     auto [it, new_pipeline] = graphics_pipelines.try_emplace(pipeline_hash); | ||||
|     if (new_pipeline) { | ||||
|         it.value() = | ||||
|             std::make_unique<GraphicsPipeline>(instance, renderpass_cache, info, *pipeline_cache, | ||||
|                                                *pipeline_layout, current_shaders, &workers); | ||||
|     } | ||||
|  | ||||
|     GraphicsPipeline* const pipeline{it->second.get()}; | ||||
|     if (!pipeline->IsDone() && !pipeline->TryBuild(wait_built)) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     for (u32 i = 0; i < NUM_RASTERIZER_SETS; i++) { | ||||
|         if (!set_dirty[i]) { | ||||
|             continue; | ||||
|         } | ||||
|         bound_descriptor_sets[i] = descriptor_set_providers[i].Acquire(update_data[i]); | ||||
|         set_dirty[i] = false; | ||||
|     } | ||||
|  | ||||
|     const bool is_dirty = scheduler.IsStateDirty(StateFlags::Pipeline); | ||||
|     const bool pipeline_dirty = (current_pipeline != pipeline) || is_dirty; | ||||
|     scheduler.Record([this, is_dirty, pipeline_dirty, pipeline, | ||||
|                       current_dynamic = current_info.dynamic, dynamic = info.dynamic, | ||||
|                       descriptor_sets = bound_descriptor_sets, offsets = offsets, | ||||
|                       current_rasterization = current_info.rasterization, | ||||
|                       current_depth_stencil = current_info.depth_stencil, | ||||
|                       rasterization = info.rasterization, | ||||
|                       depth_stencil = info.depth_stencil](vk::CommandBuffer cmdbuf) { | ||||
|         if (dynamic.stencil_compare_mask != current_dynamic.stencil_compare_mask || is_dirty) { | ||||
|             cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFrontAndBack, | ||||
|                                          dynamic.stencil_compare_mask); | ||||
|         } | ||||
|  | ||||
|         if (dynamic.stencil_write_mask != current_dynamic.stencil_write_mask || is_dirty) { | ||||
|             cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFrontAndBack, | ||||
|                                        dynamic.stencil_write_mask); | ||||
|         } | ||||
|  | ||||
|         if (dynamic.stencil_reference != current_dynamic.stencil_reference || is_dirty) { | ||||
|             cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFrontAndBack, | ||||
|                                        dynamic.stencil_reference); | ||||
|         } | ||||
|  | ||||
|         if (dynamic.blend_color != current_dynamic.blend_color || is_dirty) { | ||||
|             const Common::Vec4f color = PicaToVK::ColorRGBA8(dynamic.blend_color); | ||||
|             cmdbuf.setBlendConstants(color.AsArray()); | ||||
|         } | ||||
|  | ||||
|         if (instance.IsExtendedDynamicStateSupported()) { | ||||
|             if (rasterization.cull_mode != current_rasterization.cull_mode || is_dirty) { | ||||
|                 cmdbuf.setCullModeEXT(PicaToVK::CullMode(rasterization.cull_mode)); | ||||
|                 cmdbuf.setFrontFaceEXT(PicaToVK::FrontFace(rasterization.cull_mode)); | ||||
|             } | ||||
|  | ||||
|             if (depth_stencil.depth_compare_op != current_depth_stencil.depth_compare_op || | ||||
|                 is_dirty) { | ||||
|                 cmdbuf.setDepthCompareOpEXT(PicaToVK::CompareFunc(depth_stencil.depth_compare_op)); | ||||
|             } | ||||
|  | ||||
|             if (depth_stencil.depth_test_enable != current_depth_stencil.depth_test_enable || | ||||
|                 is_dirty) { | ||||
|                 cmdbuf.setDepthTestEnableEXT(depth_stencil.depth_test_enable); | ||||
|             } | ||||
|  | ||||
|             if (depth_stencil.depth_write_enable != current_depth_stencil.depth_write_enable || | ||||
|                 is_dirty) { | ||||
|                 cmdbuf.setDepthWriteEnableEXT(depth_stencil.depth_write_enable); | ||||
|             } | ||||
|  | ||||
|             if (rasterization.topology != current_rasterization.topology || is_dirty) { | ||||
|                 cmdbuf.setPrimitiveTopologyEXT(PicaToVK::PrimitiveTopology(rasterization.topology)); | ||||
|             } | ||||
|  | ||||
|             if (depth_stencil.stencil_test_enable != current_depth_stencil.stencil_test_enable || | ||||
|                 is_dirty) { | ||||
|                 cmdbuf.setStencilTestEnableEXT(depth_stencil.stencil_test_enable); | ||||
|             } | ||||
|  | ||||
|             if (depth_stencil.stencil_fail_op != current_depth_stencil.stencil_fail_op || | ||||
|                 depth_stencil.stencil_pass_op != current_depth_stencil.stencil_pass_op || | ||||
|                 depth_stencil.stencil_depth_fail_op != | ||||
|                     current_depth_stencil.stencil_depth_fail_op || | ||||
|                 depth_stencil.stencil_compare_op != current_depth_stencil.stencil_compare_op || | ||||
|                 is_dirty) { | ||||
|                 cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eFrontAndBack, | ||||
|                                        PicaToVK::StencilOp(depth_stencil.stencil_fail_op), | ||||
|                                        PicaToVK::StencilOp(depth_stencil.stencil_pass_op), | ||||
|                                        PicaToVK::StencilOp(depth_stencil.stencil_depth_fail_op), | ||||
|                                        PicaToVK::CompareFunc(depth_stencil.stencil_compare_op)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (pipeline_dirty) { | ||||
|             if (!pipeline->IsDone()) { | ||||
|                 pipeline->WaitDone(); | ||||
|             } | ||||
|             cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline->Handle()); | ||||
|         } | ||||
|         cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipeline_layout, 0, | ||||
|                                   descriptor_sets, offsets); | ||||
|     }); | ||||
|  | ||||
|     current_info = info; | ||||
|     current_pipeline = pipeline; | ||||
|     scheduler.MarkStateNonDirty(StateFlags::Pipeline); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool PipelineCache::UseProgrammableVertexShader(const Pica::Regs& regs, | ||||
|                                                 Pica::Shader::ShaderSetup& setup, | ||||
|                                                 const VertexLayout& layout) { | ||||
|     PicaVSConfig config{regs.rasterizer, regs.vs, setup, instance}; | ||||
|     config.state.use_geometry_shader = instance.UseGeometryShaders(); | ||||
|  | ||||
|     for (u32 i = 0; i < layout.attribute_count; i++) { | ||||
|         const VertexAttribute& attr = layout.attributes[i]; | ||||
|         const FormatTraits& traits = instance.GetTraits(attr.type, attr.size); | ||||
|         const u32 location = attr.location.Value(); | ||||
|         AttribLoadFlags& flags = config.state.load_flags[location]; | ||||
|  | ||||
|         if (traits.needs_conversion) { | ||||
|             flags = MakeAttribLoadFlag(attr.type); | ||||
|         } | ||||
|         if (traits.needs_emulation) { | ||||
|             flags |= AttribLoadFlags::ZeroW; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     auto [it, new_config] = programmable_vertex_map.try_emplace(config); | ||||
|     if (new_config) { | ||||
|         auto code = GenerateVertexShader(setup, config); | ||||
|         if (!code) { | ||||
|             LOG_ERROR(Render_Vulkan, "Failed to retrieve programmable vertex shader"); | ||||
|             programmable_vertex_map[config] = nullptr; | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         std::string& program = code.value(); | ||||
|         auto [iter, new_program] = programmable_vertex_cache.try_emplace(program, instance); | ||||
|         auto& shader = iter->second; | ||||
|  | ||||
|         if (new_program) { | ||||
|             shader.program = std::move(program); | ||||
|             const vk::Device device = instance.GetDevice(); | ||||
|             workers.QueueWork([device, &shader] { | ||||
|                 shader.module = Compile(shader.program, vk::ShaderStageFlagBits::eVertex, device); | ||||
|                 shader.MarkDone(); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         it->second = &shader; | ||||
|     } | ||||
|  | ||||
|     Shader* const shader{it->second}; | ||||
|     if (!shader) { | ||||
|         LOG_ERROR(Render_Vulkan, "Failed to retrieve programmable vertex shader"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     current_shaders[ProgramType::VS] = shader; | ||||
|     shader_hashes[ProgramType::VS] = config.Hash(); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| void PipelineCache::UseTrivialVertexShader() { | ||||
|     current_shaders[ProgramType::VS] = &trivial_vertex_shader; | ||||
|     shader_hashes[ProgramType::VS] = 0; | ||||
| } | ||||
|  | ||||
| bool PipelineCache::UseFixedGeometryShader(const Pica::Regs& regs) { | ||||
|     if (!instance.UseGeometryShaders()) { | ||||
|         UseTrivialGeometryShader(); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     const PicaFixedGSConfig gs_config{regs, instance}; | ||||
|     auto [it, new_shader] = fixed_geometry_shaders.try_emplace(gs_config, instance); | ||||
|     auto& shader = it->second; | ||||
|  | ||||
|     if (new_shader) { | ||||
|         workers.QueueWork([gs_config, device = instance.GetDevice(), &shader]() { | ||||
|             const std::string code = GenerateFixedGeometryShader(gs_config); | ||||
|             shader.module = Compile(code, vk::ShaderStageFlagBits::eGeometry, device); | ||||
|             shader.MarkDone(); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     current_shaders[ProgramType::GS] = &shader; | ||||
|     shader_hashes[ProgramType::GS] = gs_config.Hash(); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| void PipelineCache::UseTrivialGeometryShader() { | ||||
|     current_shaders[ProgramType::GS] = nullptr; | ||||
|     shader_hashes[ProgramType::GS] = 0; | ||||
| } | ||||
|  | ||||
| void PipelineCache::UseFragmentShader(const Pica::Regs& regs) { | ||||
|     const PicaFSConfig config{regs, instance}; | ||||
|  | ||||
|     const auto [it, new_shader] = fragment_shaders.try_emplace(config, instance); | ||||
|     auto& shader = it->second; | ||||
|  | ||||
|     if (new_shader) { | ||||
|         const bool use_spirv = Settings::values.spirv_shader_gen.GetValue(); | ||||
|         const auto texture0_type = config.state.texture0_type.Value(); | ||||
|         const bool is_shadow = texture0_type == Pica::TexturingRegs::TextureConfig::Shadow2D || | ||||
|                                texture0_type == Pica::TexturingRegs::TextureConfig::ShadowCube || | ||||
|                                config.state.shadow_rendering.Value(); | ||||
|         if (use_spirv && !is_shadow) { | ||||
|             const std::vector code = GenerateFragmentShaderSPV(config); | ||||
|             shader.module = CompileSPV(code, instance.GetDevice()); | ||||
|             shader.MarkDone(); | ||||
|         } else { | ||||
|             workers.QueueWork([config, device = instance.GetDevice(), &shader]() { | ||||
|                 const std::string code = GenerateFragmentShader(config); | ||||
|                 shader.module = Compile(code, vk::ShaderStageFlagBits::eFragment, device); | ||||
|                 shader.MarkDone(); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     current_shaders[ProgramType::FS] = &shader; | ||||
|     shader_hashes[ProgramType::FS] = config.Hash(); | ||||
| } | ||||
|  | ||||
| void PipelineCache::BindTexture(u32 binding, vk::ImageView image_view, vk::Sampler sampler) { | ||||
|     auto& info = update_data[1][binding].image_info; | ||||
|     if (info.imageView == image_view && info.sampler == sampler) { | ||||
|         return; | ||||
|     } | ||||
|     set_dirty[1] = true; | ||||
|     info = vk::DescriptorImageInfo{ | ||||
|         .sampler = sampler, | ||||
|         .imageView = image_view, | ||||
|         .imageLayout = vk::ImageLayout::eGeneral, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| void PipelineCache::BindStorageImage(u32 binding, vk::ImageView image_view) { | ||||
|     auto& info = update_data[2][binding].image_info; | ||||
|     if (info.imageView == image_view) { | ||||
|         return; | ||||
|     } | ||||
|     set_dirty[2] = true; | ||||
|     info = vk::DescriptorImageInfo{ | ||||
|         .imageView = image_view, | ||||
|         .imageLayout = vk::ImageLayout::eGeneral, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| void PipelineCache::BindBuffer(u32 binding, vk::Buffer buffer, u32 offset, u32 size) { | ||||
|     auto& info = update_data[0][binding].buffer_info; | ||||
|     if (info.buffer == buffer && info.offset == offset && info.range == size) { | ||||
|         return; | ||||
|     } | ||||
|     set_dirty[0] = true; | ||||
|     info = vk::DescriptorBufferInfo{ | ||||
|         .buffer = buffer, | ||||
|         .offset = offset, | ||||
|         .range = size, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| void PipelineCache::BindTexelBuffer(u32 binding, vk::BufferView buffer_view) { | ||||
|     auto& view = update_data[0][binding].buffer_view; | ||||
|     if (view != buffer_view) { | ||||
|         set_dirty[0] = true; | ||||
|         view = buffer_view; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void PipelineCache::SetBufferOffset(u32 binding, size_t offset) { | ||||
|     offsets[binding] = static_cast<u32>(offset); | ||||
| } | ||||
|  | ||||
| bool PipelineCache::IsCacheValid(std::span<const u8> data) const { | ||||
|     if (data.size() < sizeof(vk::PipelineCacheHeaderVersionOne)) { | ||||
|         LOG_ERROR(Render_Vulkan, "Pipeline cache failed validation: Invalid header"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     vk::PipelineCacheHeaderVersionOne header; | ||||
|     std::memcpy(&header, data.data(), sizeof(header)); | ||||
|     if (header.headerSize < sizeof(header)) { | ||||
|         LOG_ERROR(Render_Vulkan, "Pipeline cache failed validation: Invalid header length"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (header.headerVersion != vk::PipelineCacheHeaderVersion::eOne) { | ||||
|         LOG_ERROR(Render_Vulkan, "Pipeline cache failed validation: Invalid header version"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (u32 vendor_id = instance.GetVendorID(); header.vendorID != vendor_id) { | ||||
|         LOG_ERROR( | ||||
|             Render_Vulkan, | ||||
|             "Pipeline cache failed validation: Incorrect vendor ID (file: {:#X}, device: {:#X})", | ||||
|             header.vendorID, vendor_id); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (u32 device_id = instance.GetDeviceID(); header.deviceID != device_id) { | ||||
|         LOG_ERROR( | ||||
|             Render_Vulkan, | ||||
|             "Pipeline cache failed validation: Incorrect device ID (file: {:#X}, device: {:#X})", | ||||
|             header.deviceID, device_id); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (header.pipelineCacheUUID != instance.GetPipelineCacheUUID()) { | ||||
|         LOG_ERROR(Render_Vulkan, "Pipeline cache failed validation: Incorrect UUID"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool PipelineCache::EnsureDirectories() const { | ||||
|     const auto create_dir = [](const std::string& dir) { | ||||
|         if (!FileUtil::CreateDir(dir)) { | ||||
|             LOG_ERROR(Render_Vulkan, "Failed to create directory={}", dir); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     }; | ||||
|  | ||||
|     return create_dir(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)) && | ||||
|            create_dir(GetPipelineCacheDir()); | ||||
| } | ||||
|  | ||||
| std::string PipelineCache::GetPipelineCacheDir() const { | ||||
|     return FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + "vulkan" + DIR_SEP; | ||||
| } | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										123
									
								
								src/video_core/renderer_vulkan/vk_pipeline_cache.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/video_core/renderer_vulkan/vk_pipeline_cache.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <bitset> | ||||
| #include <tsl/robin_map.h> | ||||
|  | ||||
| #include "video_core/renderer_vulkan/vk_descriptor_pool.h" | ||||
| #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" | ||||
|  | ||||
| namespace Pica { | ||||
| struct Regs; | ||||
| } | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| class Instance; | ||||
| class Scheduler; | ||||
| class RenderpassCache; | ||||
| class DescriptorPool; | ||||
|  | ||||
| constexpr u32 NUM_RASTERIZER_SETS = 3; | ||||
| constexpr u32 NUM_DYNAMIC_OFFSETS = 2; | ||||
|  | ||||
| /** | ||||
|  * Stores a collection of rasterizer pipelines used during rendering. | ||||
|  */ | ||||
| class PipelineCache { | ||||
| public: | ||||
|     explicit PipelineCache(const Instance& instance, Scheduler& scheduler, | ||||
|                            RenderpassCache& renderpass_cache, DescriptorPool& pool); | ||||
|     ~PipelineCache(); | ||||
|  | ||||
|     [[nodiscard]] DescriptorSetProvider& TextureProvider() noexcept { | ||||
|         return descriptor_set_providers[1]; | ||||
|     } | ||||
|  | ||||
|     /// Loads the pipeline cache stored to disk | ||||
|     void LoadDiskCache(); | ||||
|  | ||||
|     /// Stores the generated pipeline cache to disk | ||||
|     void SaveDiskCache(); | ||||
|  | ||||
|     /// Binds a pipeline using the provided information | ||||
|     bool BindPipeline(const PipelineInfo& info, bool wait_built = false); | ||||
|  | ||||
|     /// Binds a PICA decompiled vertex shader | ||||
|     bool UseProgrammableVertexShader(const Pica::Regs& regs, Pica::Shader::ShaderSetup& setup, | ||||
|                                      const VertexLayout& layout); | ||||
|  | ||||
|     /// Binds a passthrough vertex shader | ||||
|     void UseTrivialVertexShader(); | ||||
|  | ||||
|     /// Binds a PICA decompiled geometry shader | ||||
|     bool UseFixedGeometryShader(const Pica::Regs& regs); | ||||
|  | ||||
|     /// Binds a passthrough geometry shader | ||||
|     void UseTrivialGeometryShader(); | ||||
|  | ||||
|     /// Binds a fragment shader generated from PICA state | ||||
|     void UseFragmentShader(const Pica::Regs& regs); | ||||
|  | ||||
|     /// Binds a texture to the specified binding | ||||
|     void BindTexture(u32 binding, vk::ImageView image_view, vk::Sampler sampler); | ||||
|  | ||||
|     /// Binds a storage image to the specified binding | ||||
|     void BindStorageImage(u32 binding, vk::ImageView image_view); | ||||
|  | ||||
|     /// Binds a buffer to the specified binding | ||||
|     void BindBuffer(u32 binding, vk::Buffer buffer, u32 offset, u32 size); | ||||
|  | ||||
|     /// Binds a buffer to the specified binding | ||||
|     void BindTexelBuffer(u32 binding, vk::BufferView buffer_view); | ||||
|  | ||||
|     /// Sets the dynamic offset for the uniform buffer at binding | ||||
|     void SetBufferOffset(u32 binding, size_t offset); | ||||
|  | ||||
| private: | ||||
|     /// Builds the rasterizer pipeline layout | ||||
|     void BuildLayout(); | ||||
|  | ||||
|     /// Returns true when the disk data can be used by the current driver | ||||
|     bool IsCacheValid(std::span<const u8> cache_data) const; | ||||
|  | ||||
|     /// Create shader disk cache directories. Returns true on success. | ||||
|     bool EnsureDirectories() const; | ||||
|  | ||||
|     /// Returns the pipeline cache storage dir | ||||
|     std::string GetPipelineCacheDir() const; | ||||
|  | ||||
| private: | ||||
|     const Instance& instance; | ||||
|     Scheduler& scheduler; | ||||
|     RenderpassCache& renderpass_cache; | ||||
|     DescriptorPool& pool; | ||||
|  | ||||
|     vk::UniquePipelineCache pipeline_cache; | ||||
|     vk::UniquePipelineLayout pipeline_layout; | ||||
|     std::size_t num_worker_threads; | ||||
|     Common::ThreadWorker workers; | ||||
|     PipelineInfo current_info{}; | ||||
|     GraphicsPipeline* current_pipeline{}; | ||||
|     tsl::robin_map<u64, std::unique_ptr<GraphicsPipeline>, Common::IdentityHash<u64>> | ||||
|         graphics_pipelines; | ||||
|  | ||||
|     std::array<DescriptorSetProvider, NUM_RASTERIZER_SETS> descriptor_set_providers; | ||||
|     std::array<DescriptorSetData, NUM_RASTERIZER_SETS> update_data{}; | ||||
|     std::array<vk::DescriptorSet, NUM_RASTERIZER_SETS> bound_descriptor_sets{}; | ||||
|     std::array<u32, NUM_DYNAMIC_OFFSETS> offsets{}; | ||||
|     std::bitset<NUM_RASTERIZER_SETS> set_dirty{}; | ||||
|  | ||||
|     std::array<u64, MAX_SHADER_STAGES> shader_hashes; | ||||
|     std::array<Shader*, MAX_SHADER_STAGES> current_shaders; | ||||
|     std::unordered_map<PicaVSConfig, Shader*> programmable_vertex_map; | ||||
|     std::unordered_map<std::string, Shader> programmable_vertex_cache; | ||||
|     std::unordered_map<PicaFixedGSConfig, Shader> fixed_geometry_shaders; | ||||
|     std::unordered_map<PicaFSConfig, Shader> fragment_shaders; | ||||
|     Shader trivial_vertex_shader; | ||||
| }; | ||||
|  | ||||
| } // namespace Vulkan | ||||
| @@ -95,7 +95,14 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL DebugReportCallback(VkDebugReportFlagsEXT | ||||
| } | ||||
| } // Anonymous namespace | ||||
|  | ||||
| std::shared_ptr<Common::DynamicLibrary> OpenLibrary() { | ||||
| std::shared_ptr<Common::DynamicLibrary> OpenLibrary( | ||||
|     [[maybe_unused]] Frontend::GraphicsContext* context) { | ||||
| #ifdef ANDROID | ||||
|     // Android may override the Vulkan driver from the frontend. | ||||
|     if (auto library = context->GetDriverLibrary(); library) { | ||||
|         return library; | ||||
|     } | ||||
| #endif | ||||
|     auto library = std::make_shared<Common::DynamicLibrary>(); | ||||
| #ifdef __APPLE__ | ||||
|     const std::string filename = Common::DynamicLibrary::GetLibraryName("vulkan"); | ||||
| @@ -273,16 +280,14 @@ vk::UniqueInstance CreateInstance(const Common::DynamicLibrary& library, | ||||
|     const auto vkGetInstanceProcAddr = | ||||
|         library.GetSymbol<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr"); | ||||
|     if (!vkGetInstanceProcAddr) { | ||||
|         LOG_CRITICAL(Render_Vulkan, "Failed GetSymbol vkGetInstanceProcAddr"); | ||||
|         return {}; | ||||
|         throw std::runtime_error("Failed GetSymbol vkGetInstanceProcAddr"); | ||||
|     } | ||||
|     VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr); | ||||
|  | ||||
|     const auto extensions = GetInstanceExtensions(window_type, enable_validation); | ||||
|     const u32 available_version = vk::enumerateInstanceVersion(); | ||||
|     if (available_version < VK_API_VERSION_1_1) { | ||||
|         LOG_CRITICAL(Render_Vulkan, "Vulkan 1.0 is not supported, 1.1 is required!"); | ||||
|         return {}; | ||||
|         throw std::runtime_error("Vulkan 1.0 is not supported, 1.1 is required!"); | ||||
|     } | ||||
|  | ||||
|     const vk::ApplicationInfo application_info = { | ||||
| @@ -343,7 +348,7 @@ vk::UniqueDebugReportCallbackEXT CreateDebugReportCallback(vk::Instance instance | ||||
|     return instance.createDebugReportCallbackEXTUnique(callback_ci); | ||||
| } | ||||
|  | ||||
| DebugCallback CreateDebugCallback(vk::Instance instance) { | ||||
| DebugCallback CreateDebugCallback(vk::Instance instance, bool& debug_utils_supported) { | ||||
|     if (!Settings::values.renderer_debug) { | ||||
|         return {}; | ||||
|     } | ||||
| @@ -356,7 +361,8 @@ DebugCallback CreateDebugCallback(vk::Instance instance) { | ||||
|         return std::strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, prop.extensionName) == 0; | ||||
|     }); | ||||
|     // Prefer debug util messenger if available. | ||||
|     if (it != properties.end()) { | ||||
|     debug_utils_supported = it != properties.end(); | ||||
|     if (debug_utils_supported) { | ||||
|         return CreateDebugMessenger(instance); | ||||
|     } | ||||
|     // Otherwise fallback to debug report callback. | ||||
|   | ||||
| @@ -13,6 +13,7 @@ | ||||
|  | ||||
| namespace Frontend { | ||||
| class EmuWindow; | ||||
| class GraphicsContext; | ||||
| enum class WindowSystemType : u8; | ||||
| } // namespace Frontend | ||||
|  | ||||
| @@ -21,7 +22,8 @@ namespace Vulkan { | ||||
| using DebugCallback = | ||||
|     std::variant<vk::UniqueDebugUtilsMessengerEXT, vk::UniqueDebugReportCallbackEXT>; | ||||
|  | ||||
| std::shared_ptr<Common::DynamicLibrary> OpenLibrary(); | ||||
| std::shared_ptr<Common::DynamicLibrary> OpenLibrary( | ||||
|     [[maybe_unused]] Frontend::GraphicsContext* context = nullptr); | ||||
|  | ||||
| vk::SurfaceKHR CreateSurface(vk::Instance instance, const Frontend::EmuWindow& emu_window); | ||||
|  | ||||
| @@ -29,6 +31,6 @@ vk::UniqueInstance CreateInstance(const Common::DynamicLibrary& library, | ||||
|                                   Frontend::WindowSystemType window_type, bool enable_validation, | ||||
|                                   bool dump_command_buffers); | ||||
|  | ||||
| DebugCallback CreateDebugCallback(vk::Instance instance); | ||||
| DebugCallback CreateDebugCallback(vk::Instance instance, bool& debug_utils_supported); | ||||
|  | ||||
| } // namespace Vulkan | ||||
|   | ||||
							
								
								
									
										515
									
								
								src/video_core/renderer_vulkan/vk_present_window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										515
									
								
								src/video_core/renderer_vulkan/vk_present_window.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,515 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include "common/microprofile.h" | ||||
| #include "common/settings.h" | ||||
| #include "common/thread.h" | ||||
| #include "core/frontend/emu_window.h" | ||||
| #include "video_core/renderer_vulkan/vk_instance.h" | ||||
| #include "video_core/renderer_vulkan/vk_platform.h" | ||||
| #include "video_core/renderer_vulkan/vk_present_window.h" | ||||
| #include "video_core/renderer_vulkan/vk_scheduler.h" | ||||
| #include "video_core/renderer_vulkan/vk_swapchain.h" | ||||
|  | ||||
| #include <vk_mem_alloc.h> | ||||
|  | ||||
| MICROPROFILE_DEFINE(Vulkan_WaitPresent, "Vulkan", "Wait For Present", MP_RGB(128, 128, 128)); | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| bool CanBlitToSwapchain(const vk::PhysicalDevice& physical_device, vk::Format format) { | ||||
|     const vk::FormatProperties props{physical_device.getFormatProperties(format)}; | ||||
|     return static_cast<bool>(props.optimalTilingFeatures & vk::FormatFeatureFlagBits::eBlitDst); | ||||
| } | ||||
|  | ||||
| [[nodiscard]] vk::ImageSubresourceLayers MakeImageSubresourceLayers() { | ||||
|     return vk::ImageSubresourceLayers{ | ||||
|         .aspectMask = vk::ImageAspectFlagBits::eColor, | ||||
|         .mipLevel = 0, | ||||
|         .baseArrayLayer = 0, | ||||
|         .layerCount = 1, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| [[nodiscard]] vk::ImageBlit MakeImageBlit(s32 frame_width, s32 frame_height, s32 swapchain_width, | ||||
|                                           s32 swapchain_height) { | ||||
|     return vk::ImageBlit{ | ||||
|         .srcSubresource = MakeImageSubresourceLayers(), | ||||
|         .srcOffsets = | ||||
|             std::array{ | ||||
|                 vk::Offset3D{ | ||||
|                     .x = 0, | ||||
|                     .y = 0, | ||||
|                     .z = 0, | ||||
|                 }, | ||||
|                 vk::Offset3D{ | ||||
|                     .x = frame_width, | ||||
|                     .y = frame_height, | ||||
|                     .z = 1, | ||||
|                 }, | ||||
|             }, | ||||
|         .dstSubresource = MakeImageSubresourceLayers(), | ||||
|         .dstOffsets = | ||||
|             std::array{ | ||||
|                 vk::Offset3D{ | ||||
|                     .x = 0, | ||||
|                     .y = 0, | ||||
|                     .z = 0, | ||||
|                 }, | ||||
|                 vk::Offset3D{ | ||||
|                     .x = swapchain_width, | ||||
|                     .y = swapchain_height, | ||||
|                     .z = 1, | ||||
|                 }, | ||||
|             }, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| [[nodiscard]] vk::ImageCopy MakeImageCopy(u32 frame_width, u32 frame_height, u32 swapchain_width, | ||||
|                                           u32 swapchain_height) { | ||||
|     return vk::ImageCopy{ | ||||
|         .srcSubresource = MakeImageSubresourceLayers(), | ||||
|         .srcOffset = | ||||
|             vk::Offset3D{ | ||||
|                 .x = 0, | ||||
|                 .y = 0, | ||||
|                 .z = 0, | ||||
|             }, | ||||
|         .dstSubresource = MakeImageSubresourceLayers(), | ||||
|         .dstOffset = | ||||
|             vk::Offset3D{ | ||||
|                 .x = 0, | ||||
|                 .y = 0, | ||||
|                 .z = 0, | ||||
|             }, | ||||
|         .extent = | ||||
|             vk::Extent3D{ | ||||
|                 .width = std::min(frame_width, swapchain_width), | ||||
|                 .height = std::min(frame_height, swapchain_height), | ||||
|                 .depth = 1, | ||||
|             }, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| } // Anonymous namespace | ||||
|  | ||||
| PresentWindow::PresentWindow(Frontend::EmuWindow& emu_window_, const Instance& instance_, | ||||
|                              Scheduler& scheduler_) | ||||
|     : emu_window{emu_window_}, instance{instance_}, scheduler{scheduler_}, | ||||
|       surface{CreateSurface(instance.GetInstance(), emu_window)}, | ||||
|       next_surface{surface}, swapchain{instance, emu_window.GetFramebufferLayout().width, | ||||
|                                        emu_window.GetFramebufferLayout().height, surface}, | ||||
|       graphics_queue{instance.GetGraphicsQueue()}, present_renderpass{CreateRenderpass()}, | ||||
|       vsync_enabled{Settings::values.use_vsync_new.GetValue()}, | ||||
|       blit_supported{ | ||||
|           CanBlitToSwapchain(instance.GetPhysicalDevice(), swapchain.GetSurfaceFormat().format)}, | ||||
|       use_present_thread{Settings::values.async_presentation.GetValue()}, | ||||
|       last_render_surface{emu_window.GetWindowInfo().render_surface} { | ||||
|  | ||||
|     const u32 num_images = swapchain.GetImageCount(); | ||||
|     const vk::Device device = instance.GetDevice(); | ||||
|  | ||||
|     const vk::CommandPoolCreateInfo pool_info = { | ||||
|         .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer | | ||||
|                  vk::CommandPoolCreateFlagBits::eTransient, | ||||
|         .queueFamilyIndex = instance.GetGraphicsQueueFamilyIndex(), | ||||
|     }; | ||||
|     command_pool = device.createCommandPool(pool_info); | ||||
|  | ||||
|     const vk::CommandBufferAllocateInfo alloc_info = { | ||||
|         .commandPool = command_pool, | ||||
|         .level = vk::CommandBufferLevel::ePrimary, | ||||
|         .commandBufferCount = num_images, | ||||
|     }; | ||||
|     const std::vector command_buffers = device.allocateCommandBuffers(alloc_info); | ||||
|  | ||||
|     swap_chain.resize(num_images); | ||||
|     for (u32 i = 0; i < num_images; i++) { | ||||
|         Frame& frame = swap_chain[i]; | ||||
|         frame.cmdbuf = command_buffers[i]; | ||||
|         frame.render_ready = device.createSemaphore({}); | ||||
|         frame.present_done = device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled}); | ||||
|         free_queue.push(&frame); | ||||
|     } | ||||
|  | ||||
|     if (use_present_thread) { | ||||
|         present_thread = std::jthread([this](std::stop_token token) { PresentThread(token); }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| PresentWindow::~PresentWindow() { | ||||
|     scheduler.Finish(); | ||||
|     const vk::Device device = instance.GetDevice(); | ||||
|     device.destroyCommandPool(command_pool); | ||||
|     device.destroyRenderPass(present_renderpass); | ||||
|     for (auto& frame : swap_chain) { | ||||
|         device.destroyImageView(frame.image_view); | ||||
|         device.destroyFramebuffer(frame.framebuffer); | ||||
|         device.destroySemaphore(frame.render_ready); | ||||
|         device.destroyFence(frame.present_done); | ||||
|         vmaDestroyImage(instance.GetAllocator(), frame.image, frame.allocation); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void PresentWindow::RecreateFrame(Frame* frame, u32 width, u32 height) { | ||||
|     vk::Device device = instance.GetDevice(); | ||||
|     if (frame->framebuffer) { | ||||
|         device.destroyFramebuffer(frame->framebuffer); | ||||
|     } | ||||
|     if (frame->image_view) { | ||||
|         device.destroyImageView(frame->image_view); | ||||
|     } | ||||
|     if (frame->image) { | ||||
|         vmaDestroyImage(instance.GetAllocator(), frame->image, frame->allocation); | ||||
|     } | ||||
|  | ||||
|     const vk::Format format = swapchain.GetSurfaceFormat().format; | ||||
|     const vk::ImageCreateInfo image_info = { | ||||
|         .imageType = vk::ImageType::e2D, | ||||
|         .format = format, | ||||
|         .extent = {width, height, 1}, | ||||
|         .mipLevels = 1, | ||||
|         .arrayLayers = 1, | ||||
|         .samples = vk::SampleCountFlagBits::e1, | ||||
|         .usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc, | ||||
|     }; | ||||
|  | ||||
|     const VmaAllocationCreateInfo alloc_info = { | ||||
|         .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT, | ||||
|         .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, | ||||
|         .requiredFlags = 0, | ||||
|         .preferredFlags = 0, | ||||
|         .pool = VK_NULL_HANDLE, | ||||
|         .pUserData = nullptr, | ||||
|     }; | ||||
|  | ||||
|     VkImage unsafe_image{}; | ||||
|     VkImageCreateInfo unsafe_image_info = static_cast<VkImageCreateInfo>(image_info); | ||||
|  | ||||
|     VkResult result = vmaCreateImage(instance.GetAllocator(), &unsafe_image_info, &alloc_info, | ||||
|                                      &unsafe_image, &frame->allocation, nullptr); | ||||
|     if (result != VK_SUCCESS) [[unlikely]] { | ||||
|         LOG_CRITICAL(Render_Vulkan, "Failed allocating texture with error {}", result); | ||||
|         UNREACHABLE(); | ||||
|     } | ||||
|     frame->image = vk::Image{unsafe_image}; | ||||
|  | ||||
|     const vk::ImageViewCreateInfo view_info = { | ||||
|         .image = frame->image, | ||||
|         .viewType = vk::ImageViewType::e2D, | ||||
|         .format = format, | ||||
|         .subresourceRange{ | ||||
|             .aspectMask = vk::ImageAspectFlagBits::eColor, | ||||
|             .baseMipLevel = 0, | ||||
|             .levelCount = 1, | ||||
|             .baseArrayLayer = 0, | ||||
|             .layerCount = 1, | ||||
|         }, | ||||
|     }; | ||||
|     frame->image_view = device.createImageView(view_info); | ||||
|  | ||||
|     const vk::FramebufferCreateInfo framebuffer_info = { | ||||
|         .renderPass = present_renderpass, | ||||
|         .attachmentCount = 1, | ||||
|         .pAttachments = &frame->image_view, | ||||
|         .width = width, | ||||
|         .height = height, | ||||
|         .layers = 1, | ||||
|     }; | ||||
|     frame->framebuffer = instance.GetDevice().createFramebuffer(framebuffer_info); | ||||
|  | ||||
|     frame->width = width; | ||||
|     frame->height = height; | ||||
| } | ||||
|  | ||||
| Frame* PresentWindow::GetRenderFrame() { | ||||
|     MICROPROFILE_SCOPE(Vulkan_WaitPresent); | ||||
|  | ||||
|     // Wait for free presentation frames | ||||
|     std::unique_lock lock{free_mutex}; | ||||
|     free_cv.wait(lock, [this] { return !free_queue.empty(); }); | ||||
|  | ||||
|     // Take the frame from the queue | ||||
|     Frame* frame = free_queue.front(); | ||||
|     free_queue.pop(); | ||||
|  | ||||
|     vk::Device device = instance.GetDevice(); | ||||
|     vk::Result result{}; | ||||
|  | ||||
|     const auto wait = [&]() { | ||||
|         result = device.waitForFences(frame->present_done, false, std::numeric_limits<u64>::max()); | ||||
|         return result; | ||||
|     }; | ||||
|  | ||||
|     // Wait for the presentation to be finished so all frame resources are free | ||||
|     while (wait() != vk::Result::eSuccess) { | ||||
|         // Retry if the waiting times out | ||||
|         if (result == vk::Result::eTimeout) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         // eErrorInitializationFailed occurs on Mali GPU drivers due to them | ||||
|         // using the ppoll() syscall which isn't correctly restarted after a signal, | ||||
|         // we need to manually retry waiting in that case | ||||
|         if (result == vk::Result::eErrorInitializationFailed) { | ||||
|             continue; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     device.resetFences(frame->present_done); | ||||
|     return frame; | ||||
| } | ||||
|  | ||||
| void PresentWindow::Present(Frame* frame) { | ||||
|     if (!use_present_thread) { | ||||
|         scheduler.WaitWorker(); | ||||
|         CopyToSwapchain(frame); | ||||
|         free_queue.push(frame); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     scheduler.Record([this, frame](vk::CommandBuffer) { | ||||
|         std::unique_lock lock{queue_mutex}; | ||||
|         present_queue.push(frame); | ||||
|         frame_cv.notify_one(); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| void PresentWindow::WaitPresent() { | ||||
|     if (!use_present_thread) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Wait for the present queue to be empty | ||||
|     { | ||||
|         std::unique_lock queue_lock{queue_mutex}; | ||||
|         frame_cv.wait(queue_lock, [this] { return present_queue.empty(); }); | ||||
|     } | ||||
|  | ||||
|     // The above condition will be satisfied when the last frame is taken from the queue. | ||||
|     // To ensure that frame has been presented as well take hold of the swapchain | ||||
|     // mutex. | ||||
|     std::scoped_lock swapchain_lock{swapchain_mutex}; | ||||
| } | ||||
|  | ||||
| void PresentWindow::PresentThread(std::stop_token token) { | ||||
|     Common::SetCurrentThreadName("VulkanPresent"); | ||||
|     while (!token.stop_requested()) { | ||||
|         std::unique_lock lock{queue_mutex}; | ||||
|  | ||||
|         // Wait for presentation frames | ||||
|         Common::CondvarWait(frame_cv, lock, token, [this] { return !present_queue.empty(); }); | ||||
|         if (token.stop_requested()) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Take the frame and notify anyone waiting | ||||
|         Frame* frame = present_queue.front(); | ||||
|         present_queue.pop(); | ||||
|         frame_cv.notify_one(); | ||||
|  | ||||
|         // By exchanging the lock ownership we take the swapchain lock | ||||
|         // before the queue lock goes out of scope. This way the swapchain | ||||
|         // lock in WaitPresent is guaranteed to occur after here. | ||||
|         std::exchange(lock, std::unique_lock{swapchain_mutex}); | ||||
|  | ||||
|         CopyToSwapchain(frame); | ||||
|  | ||||
|         // Free the frame for reuse | ||||
|         std::scoped_lock fl{free_mutex}; | ||||
|         free_queue.push(frame); | ||||
|         free_cv.notify_one(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void PresentWindow::NotifySurfaceChanged() { | ||||
| #ifdef ANDROID | ||||
|     std::scoped_lock lock{recreate_surface_mutex}; | ||||
|     next_surface = CreateSurface(instance.GetInstance(), emu_window); | ||||
|     recreate_surface_cv.notify_one(); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void PresentWindow::CopyToSwapchain(Frame* frame) { | ||||
|     const auto recreate_swapchain = [&] { | ||||
| #ifdef ANDROID | ||||
|         { | ||||
|             std::unique_lock lock{recreate_surface_mutex}; | ||||
|             recreate_surface_cv.wait(lock, [this]() { return surface != next_surface; }); | ||||
|             surface = next_surface; | ||||
|         } | ||||
| #endif | ||||
|         std::scoped_lock submit_lock{scheduler.submit_mutex}; | ||||
|         graphics_queue.waitIdle(); | ||||
|         swapchain.Create(frame->width, frame->height, surface); | ||||
|     }; | ||||
|  | ||||
| #ifndef ANDROID | ||||
|     const bool use_vsync = Settings::values.use_vsync_new.GetValue(); | ||||
|     const bool size_changed = | ||||
|         swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height; | ||||
|     const bool vsync_changed = vsync_enabled != use_vsync; | ||||
|     if (vsync_changed || size_changed) [[unlikely]] { | ||||
|         vsync_enabled = use_vsync; | ||||
|         recreate_swapchain(); | ||||
|     } | ||||
| #endif | ||||
|  | ||||
|     while (!swapchain.AcquireNextImage()) { | ||||
|         recreate_swapchain(); | ||||
|     } | ||||
|  | ||||
|     const vk::Image swapchain_image = swapchain.Image(); | ||||
|  | ||||
|     const vk::CommandBufferBeginInfo begin_info = { | ||||
|         .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit, | ||||
|     }; | ||||
|     const vk::CommandBuffer cmdbuf = frame->cmdbuf; | ||||
|     cmdbuf.begin(begin_info); | ||||
|  | ||||
|     const vk::Extent2D extent = swapchain.GetExtent(); | ||||
|     const std::array pre_barriers{ | ||||
|         vk::ImageMemoryBarrier{ | ||||
|             .srcAccessMask = vk::AccessFlagBits::eNone, | ||||
|             .dstAccessMask = vk::AccessFlagBits::eTransferWrite, | ||||
|             .oldLayout = vk::ImageLayout::eUndefined, | ||||
|             .newLayout = vk::ImageLayout::eTransferDstOptimal, | ||||
|             .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|             .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|             .image = swapchain_image, | ||||
|             .subresourceRange{ | ||||
|                 .aspectMask = vk::ImageAspectFlagBits::eColor, | ||||
|                 .baseMipLevel = 0, | ||||
|                 .levelCount = 1, | ||||
|                 .baseArrayLayer = 0, | ||||
|                 .layerCount = VK_REMAINING_ARRAY_LAYERS, | ||||
|             }, | ||||
|         }, | ||||
|         vk::ImageMemoryBarrier{ | ||||
|             .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, | ||||
|             .dstAccessMask = vk::AccessFlagBits::eTransferRead, | ||||
|             .oldLayout = vk::ImageLayout::eTransferSrcOptimal, | ||||
|             .newLayout = vk::ImageLayout::eTransferSrcOptimal, | ||||
|             .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|             .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|             .image = frame->image, | ||||
|             .subresourceRange{ | ||||
|                 .aspectMask = vk::ImageAspectFlagBits::eColor, | ||||
|                 .baseMipLevel = 0, | ||||
|                 .levelCount = 1, | ||||
|                 .baseArrayLayer = 0, | ||||
|                 .layerCount = VK_REMAINING_ARRAY_LAYERS, | ||||
|             }, | ||||
|         }, | ||||
|     }; | ||||
|     const vk::ImageMemoryBarrier post_barrier{ | ||||
|         .srcAccessMask = vk::AccessFlagBits::eTransferWrite, | ||||
|         .dstAccessMask = vk::AccessFlagBits::eMemoryRead, | ||||
|         .oldLayout = vk::ImageLayout::eTransferDstOptimal, | ||||
|         .newLayout = vk::ImageLayout::ePresentSrcKHR, | ||||
|         .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|         .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|         .image = swapchain_image, | ||||
|         .subresourceRange{ | ||||
|             .aspectMask = vk::ImageAspectFlagBits::eColor, | ||||
|             .baseMipLevel = 0, | ||||
|             .levelCount = 1, | ||||
|             .baseArrayLayer = 0, | ||||
|             .layerCount = VK_REMAINING_ARRAY_LAYERS, | ||||
|         }, | ||||
|     }; | ||||
|  | ||||
|     cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, | ||||
|                            vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, | ||||
|                            {}, {}, pre_barriers); | ||||
|  | ||||
|     if (blit_supported) { | ||||
|         cmdbuf.blitImage(frame->image, vk::ImageLayout::eTransferSrcOptimal, swapchain_image, | ||||
|                          vk::ImageLayout::eTransferDstOptimal, | ||||
|                          MakeImageBlit(frame->width, frame->height, extent.width, extent.height), | ||||
|                          vk::Filter::eLinear); | ||||
|     } else { | ||||
|         cmdbuf.copyImage(frame->image, vk::ImageLayout::eTransferSrcOptimal, swapchain_image, | ||||
|                          vk::ImageLayout::eTransferDstOptimal, | ||||
|                          MakeImageCopy(frame->width, frame->height, extent.width, extent.height)); | ||||
|     } | ||||
|  | ||||
|     cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, | ||||
|                            vk::PipelineStageFlagBits::eAllCommands, | ||||
|                            vk::DependencyFlagBits::eByRegion, {}, {}, post_barrier); | ||||
|  | ||||
|     cmdbuf.end(); | ||||
|  | ||||
|     static constexpr std::array<vk::PipelineStageFlags, 2> wait_stage_masks = { | ||||
|         vk::PipelineStageFlagBits::eColorAttachmentOutput, | ||||
|         vk::PipelineStageFlagBits::eAllGraphics, | ||||
|     }; | ||||
|  | ||||
|     const vk::Semaphore present_ready = swapchain.GetPresentReadySemaphore(); | ||||
|     const vk::Semaphore image_acquired = swapchain.GetImageAcquiredSemaphore(); | ||||
|     const std::array wait_semaphores = {image_acquired, frame->render_ready}; | ||||
|  | ||||
|     vk::SubmitInfo submit_info = { | ||||
|         .waitSemaphoreCount = static_cast<u32>(wait_semaphores.size()), | ||||
|         .pWaitSemaphores = wait_semaphores.data(), | ||||
|         .pWaitDstStageMask = wait_stage_masks.data(), | ||||
|         .commandBufferCount = 1u, | ||||
|         .pCommandBuffers = &cmdbuf, | ||||
|         .signalSemaphoreCount = 1, | ||||
|         .pSignalSemaphores = &present_ready, | ||||
|     }; | ||||
|  | ||||
|     std::scoped_lock submit_lock{scheduler.submit_mutex}; | ||||
|  | ||||
|     try { | ||||
|         graphics_queue.submit(submit_info, frame->present_done); | ||||
|     } catch (vk::DeviceLostError& err) { | ||||
|         LOG_CRITICAL(Render_Vulkan, "Device lost during present submit: {}", err.what()); | ||||
|         UNREACHABLE(); | ||||
|     } | ||||
|  | ||||
|     swapchain.Present(); | ||||
| } | ||||
|  | ||||
| vk::RenderPass PresentWindow::CreateRenderpass() { | ||||
|     const vk::AttachmentReference color_ref = { | ||||
|         .attachment = 0, | ||||
|         .layout = vk::ImageLayout::eGeneral, | ||||
|     }; | ||||
|  | ||||
|     const vk::SubpassDescription subpass = { | ||||
|         .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, | ||||
|         .inputAttachmentCount = 0, | ||||
|         .pInputAttachments = nullptr, | ||||
|         .colorAttachmentCount = 1u, | ||||
|         .pColorAttachments = &color_ref, | ||||
|         .pResolveAttachments = 0, | ||||
|         .pDepthStencilAttachment = nullptr, | ||||
|     }; | ||||
|  | ||||
|     const vk::AttachmentDescription color_attachment = { | ||||
|         .format = swapchain.GetSurfaceFormat().format, | ||||
|         .loadOp = vk::AttachmentLoadOp::eClear, | ||||
|         .storeOp = vk::AttachmentStoreOp::eStore, | ||||
|         .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, | ||||
|         .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, | ||||
|         .initialLayout = vk::ImageLayout::eUndefined, | ||||
|         .finalLayout = vk::ImageLayout::eTransferSrcOptimal, | ||||
|     }; | ||||
|  | ||||
|     const vk::RenderPassCreateInfo renderpass_info = { | ||||
|         .attachmentCount = 1, | ||||
|         .pAttachments = &color_attachment, | ||||
|         .subpassCount = 1, | ||||
|         .pSubpasses = &subpass, | ||||
|         .dependencyCount = 0, | ||||
|         .pDependencies = nullptr, | ||||
|     }; | ||||
|  | ||||
|     return instance.GetDevice().createRenderPass(renderpass_info); | ||||
| } | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										100
									
								
								src/video_core/renderer_vulkan/vk_present_window.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/video_core/renderer_vulkan/vk_present_window.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <atomic> | ||||
| #include <condition_variable> | ||||
| #include <mutex> | ||||
| #include <queue> | ||||
| #include "common/polyfill_thread.h" | ||||
| #include "video_core/renderer_vulkan/vk_swapchain.h" | ||||
|  | ||||
| VK_DEFINE_HANDLE(VmaAllocation) | ||||
|  | ||||
| namespace Frontend { | ||||
| class EmuWindow; | ||||
| } | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| class Instance; | ||||
| class Swapchain; | ||||
| class Scheduler; | ||||
| class RenderpassCache; | ||||
|  | ||||
| struct Frame { | ||||
|     u32 width; | ||||
|     u32 height; | ||||
|     VmaAllocation allocation; | ||||
|     vk::Framebuffer framebuffer; | ||||
|     vk::Image image; | ||||
|     vk::ImageView image_view; | ||||
|     vk::Semaphore render_ready; | ||||
|     vk::Fence present_done; | ||||
|     vk::CommandBuffer cmdbuf; | ||||
| }; | ||||
|  | ||||
| class PresentWindow final { | ||||
| public: | ||||
|     explicit PresentWindow(Frontend::EmuWindow& emu_window, const Instance& instance, | ||||
|                            Scheduler& scheduler); | ||||
|     ~PresentWindow(); | ||||
|  | ||||
|     /// Waits for all queued frames to finish presenting. | ||||
|     void WaitPresent(); | ||||
|  | ||||
|     /// Returns the last used render frame. | ||||
|     Frame* GetRenderFrame(); | ||||
|  | ||||
|     /// Recreates the render frame to match provided parameters. | ||||
|     void RecreateFrame(Frame* frame, u32 width, u32 height); | ||||
|  | ||||
|     /// Queues the provided frame for presentation. | ||||
|     void Present(Frame* frame); | ||||
|  | ||||
|     /// This is called to notify the rendering backend of a surface change | ||||
|     void NotifySurfaceChanged(); | ||||
|  | ||||
|     [[nodiscard]] vk::RenderPass Renderpass() const noexcept { | ||||
|         return present_renderpass; | ||||
|     } | ||||
|  | ||||
|     u32 ImageCount() const noexcept { | ||||
|         return swapchain.GetImageCount(); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void PresentThread(std::stop_token token); | ||||
|  | ||||
|     void CopyToSwapchain(Frame* frame); | ||||
|  | ||||
|     vk::RenderPass CreateRenderpass(); | ||||
|  | ||||
| private: | ||||
|     Frontend::EmuWindow& emu_window; | ||||
|     const Instance& instance; | ||||
|     Scheduler& scheduler; | ||||
|     vk::SurfaceKHR surface; | ||||
|     vk::SurfaceKHR next_surface{}; | ||||
|     Swapchain swapchain; | ||||
|     vk::CommandPool command_pool; | ||||
|     vk::Queue graphics_queue; | ||||
|     vk::RenderPass present_renderpass; | ||||
|     std::vector<Frame> swap_chain; | ||||
|     std::queue<Frame*> free_queue; | ||||
|     std::queue<Frame*> present_queue; | ||||
|     std::condition_variable free_cv; | ||||
|     std::condition_variable recreate_surface_cv; | ||||
|     std::condition_variable_any frame_cv; | ||||
|     std::mutex swapchain_mutex; | ||||
|     std::mutex recreate_surface_mutex; | ||||
|     std::mutex queue_mutex; | ||||
|     std::mutex free_mutex; | ||||
|     std::jthread present_thread; | ||||
|     bool vsync_enabled{}; | ||||
|     bool blit_supported; | ||||
|     bool use_present_thread{true}; | ||||
|     void* last_render_surface{}; | ||||
| }; | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										1145
									
								
								src/video_core/renderer_vulkan/vk_rasterizer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1145
									
								
								src/video_core/renderer_vulkan/vk_rasterizer.cpp
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										171
									
								
								src/video_core/renderer_vulkan/vk_rasterizer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								src/video_core/renderer_vulkan/vk_rasterizer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,171 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "core/hw/gpu.h" | ||||
| #include "video_core/rasterizer_accelerated.h" | ||||
| #include "video_core/renderer_vulkan/vk_pipeline_cache.h" | ||||
| #include "video_core/renderer_vulkan/vk_renderpass_cache.h" | ||||
| #include "video_core/renderer_vulkan/vk_stream_buffer.h" | ||||
| #include "video_core/renderer_vulkan/vk_texture_runtime.h" | ||||
|  | ||||
| namespace Frontend { | ||||
| class EmuWindow; | ||||
| } | ||||
|  | ||||
| namespace VideoCore { | ||||
| class CustomTexManager; | ||||
| class RendererBase; | ||||
| } // namespace VideoCore | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| struct ScreenInfo; | ||||
|  | ||||
| class Instance; | ||||
| class Scheduler; | ||||
| class RenderpassCache; | ||||
| class DescriptorPool; | ||||
|  | ||||
| class RasterizerVulkan : public VideoCore::RasterizerAccelerated { | ||||
| public: | ||||
|     explicit RasterizerVulkan(Memory::MemorySystem& memory, | ||||
|                               VideoCore::CustomTexManager& custom_tex_manager, | ||||
|                               VideoCore::RendererBase& renderer, Frontend::EmuWindow& emu_window, | ||||
|                               const Instance& instance, Scheduler& scheduler, DescriptorPool& pool, | ||||
|                               RenderpassCache& renderpass_cache, u32 image_count); | ||||
|     ~RasterizerVulkan() override; | ||||
|  | ||||
|     void TickFrame(); | ||||
|     void LoadDiskResources(const std::atomic_bool& stop_loading, | ||||
|                            const VideoCore::DiskResourceLoadCallback& callback) override; | ||||
|  | ||||
|     void DrawTriangles() override; | ||||
|     void FlushAll() override; | ||||
|     void FlushRegion(PAddr addr, u32 size) override; | ||||
|     void InvalidateRegion(PAddr addr, u32 size) override; | ||||
|     void FlushAndInvalidateRegion(PAddr addr, u32 size) override; | ||||
|     void ClearAll(bool flush) override; | ||||
|     bool AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) override; | ||||
|     bool AccelerateTextureCopy(const GPU::Regs::DisplayTransferConfig& config) override; | ||||
|     bool AccelerateFill(const GPU::Regs::MemoryFillConfig& config) override; | ||||
|     bool AccelerateDisplay(const GPU::Regs::FramebufferConfig& config, PAddr framebuffer_addr, | ||||
|                            u32 pixel_stride, ScreenInfo& screen_info); | ||||
|     bool AccelerateDrawBatch(bool is_indexed) override; | ||||
|  | ||||
|     void SyncFixedState() override; | ||||
|  | ||||
| private: | ||||
|     void NotifyFixedFunctionPicaRegisterChanged(u32 id) override; | ||||
|  | ||||
|     /// Syncs the clip enabled status to match the PICA register | ||||
|     void SyncClipEnabled(); | ||||
|  | ||||
|     /// Syncs the cull mode to match the PICA register | ||||
|     void SyncCullMode(); | ||||
|  | ||||
|     /// Syncs the blend enabled status to match the PICA register | ||||
|     void SyncBlendEnabled(); | ||||
|  | ||||
|     /// Syncs the blend functions to match the PICA register | ||||
|     void SyncBlendFuncs(); | ||||
|  | ||||
|     /// Syncs the blend color to match the PICA register | ||||
|     void SyncBlendColor(); | ||||
|  | ||||
|     /// Syncs the logic op states to match the PICA register | ||||
|     void SyncLogicOp(); | ||||
|  | ||||
|     /// Syncs the color write mask to match the PICA register state | ||||
|     void SyncColorWriteMask(); | ||||
|  | ||||
|     /// Syncs the stencil write mask to match the PICA register state | ||||
|     void SyncStencilWriteMask(); | ||||
|  | ||||
|     /// Syncs the depth write mask to match the PICA register state | ||||
|     void SyncDepthWriteMask(); | ||||
|  | ||||
|     /// Syncs the stencil test states to match the PICA register | ||||
|     void SyncStencilTest(); | ||||
|  | ||||
|     /// Syncs the depth test states to match the PICA register | ||||
|     void SyncDepthTest(); | ||||
|  | ||||
|     /// Syncs and uploads the lighting, fog and proctex LUTs | ||||
|     void SyncAndUploadLUTs(); | ||||
|     void SyncAndUploadLUTsLF(); | ||||
|  | ||||
|     /// Syncs all enabled PICA texture units | ||||
|     void SyncTextureUnits(const Framebuffer* framebuffer); | ||||
|  | ||||
|     /// Binds the PICA shadow cube required for shadow mapping | ||||
|     void BindShadowCube(const Pica::TexturingRegs::FullTextureConfig& texture); | ||||
|  | ||||
|     /// Binds a texture cube to texture unit 0 | ||||
|     void BindTextureCube(const Pica::TexturingRegs::FullTextureConfig& texture); | ||||
|  | ||||
|     /// Makes a temporary copy of the framebuffer if a feedback loop is detected | ||||
|     bool IsFeedbackLoop(u32 texture_index, const Framebuffer* framebuffer, Surface& surface, | ||||
|                         Sampler& sampler); | ||||
|  | ||||
|     /// Unbinds all special texture unit 0 texture configurations | ||||
|     void UnbindSpecial(); | ||||
|  | ||||
|     /// Upload the uniform blocks to the uniform buffer object | ||||
|     void UploadUniforms(bool accelerate_draw); | ||||
|  | ||||
|     /// Generic draw function for DrawTriangles and AccelerateDrawBatch | ||||
|     bool Draw(bool accelerate, bool is_indexed); | ||||
|  | ||||
|     /// Internal implementation for AccelerateDrawBatch | ||||
|     bool AccelerateDrawBatchInternal(bool is_indexed); | ||||
|  | ||||
|     /// Setup index array for AccelerateDrawBatch | ||||
|     void SetupIndexArray(); | ||||
|  | ||||
|     /// Setup vertex array for AccelerateDrawBatch | ||||
|     void SetupVertexArray(); | ||||
|  | ||||
|     /// Setup the fixed attribute emulation in vulkan | ||||
|     void SetupFixedAttribs(); | ||||
|  | ||||
|     /// Setup vertex shader for AccelerateDrawBatch | ||||
|     bool SetupVertexShader(); | ||||
|  | ||||
|     /// Setup geometry shader for AccelerateDrawBatch | ||||
|     bool SetupGeometryShader(); | ||||
|  | ||||
|     /// Creates the vertex layout struct used for software shader pipelines | ||||
|     void MakeSoftwareVertexLayout(); | ||||
|  | ||||
| private: | ||||
|     const Instance& instance; | ||||
|     Scheduler& scheduler; | ||||
|     RenderpassCache& renderpass_cache; | ||||
|     PipelineCache pipeline_cache; | ||||
|     TextureRuntime runtime; | ||||
|     RasterizerCache res_cache; | ||||
|  | ||||
|     VertexLayout software_layout; | ||||
|     std::array<u32, 16> binding_offsets{}; | ||||
|     std::array<bool, 16> enable_attributes{}; | ||||
|     std::array<vk::Buffer, 16> vertex_buffers; | ||||
|     VertexArrayInfo vertex_info; | ||||
|     PipelineInfo pipeline_info{}; | ||||
|  | ||||
|     StreamBuffer stream_buffer;     ///< Vertex+Index buffer | ||||
|     StreamBuffer uniform_buffer;    ///< Uniform buffer | ||||
|     StreamBuffer texture_buffer;    ///< Texture buffer | ||||
|     StreamBuffer texture_lf_buffer; ///< Texture Light-Fog buffer | ||||
|     vk::UniqueBufferView texture_lf_view; | ||||
|     vk::UniqueBufferView texture_rg_view; | ||||
|     vk::UniqueBufferView texture_rgba_view; | ||||
|     u64 uniform_buffer_alignment; | ||||
|     u64 uniform_size_aligned_vs; | ||||
|     u64 uniform_size_aligned_fs; | ||||
|     bool async_shaders{false}; | ||||
| }; | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										10
									
								
								src/video_core/renderer_vulkan/vk_rasterizer_cache.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/video_core/renderer_vulkan/vk_rasterizer_cache.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include "video_core/rasterizer_cache/rasterizer_cache.h" | ||||
| #include "video_core/renderer_vulkan/vk_texture_runtime.h" | ||||
|  | ||||
| namespace VideoCore { | ||||
| template class RasterizerCache<Vulkan::Traits>; | ||||
| } // namespace VideoCore | ||||
							
								
								
									
										221
									
								
								src/video_core/renderer_vulkan/vk_renderpass_cache.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								src/video_core/renderer_vulkan/vk_renderpass_cache.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,221 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <limits> | ||||
| #include "common/assert.h" | ||||
| #include "video_core/rasterizer_cache/pixel_format.h" | ||||
| #include "video_core/renderer_vulkan/vk_instance.h" | ||||
| #include "video_core/renderer_vulkan/vk_renderpass_cache.h" | ||||
| #include "video_core/renderer_vulkan/vk_scheduler.h" | ||||
| #include "video_core/renderer_vulkan/vk_texture_runtime.h" | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| constexpr u32 MIN_DRAWS_TO_FLUSH = 20; | ||||
|  | ||||
| using VideoCore::PixelFormat; | ||||
| using VideoCore::SurfaceType; | ||||
|  | ||||
| RenderpassCache::RenderpassCache(const Instance& instance, Scheduler& scheduler) | ||||
|     : instance{instance}, scheduler{scheduler} {} | ||||
|  | ||||
| RenderpassCache::~RenderpassCache() = default; | ||||
|  | ||||
| void RenderpassCache::BeginRendering(const Framebuffer* framebuffer, | ||||
|                                      Common::Rectangle<u32> draw_rect) { | ||||
|     const vk::Rect2D render_area = { | ||||
|         .offset{ | ||||
|             .x = static_cast<s32>(draw_rect.left), | ||||
|             .y = static_cast<s32>(draw_rect.bottom), | ||||
|         }, | ||||
|         .extent{ | ||||
|             .width = draw_rect.GetWidth(), | ||||
|             .height = draw_rect.GetHeight(), | ||||
|         }, | ||||
|     }; | ||||
|     const RenderPass new_pass = { | ||||
|         .framebuffer = framebuffer->Handle(), | ||||
|         .render_pass = framebuffer->RenderPass(), | ||||
|         .render_area = render_area, | ||||
|         .clear = {}, | ||||
|         .do_clear = false, | ||||
|     }; | ||||
|     images = framebuffer->Images(); | ||||
|     aspects = framebuffer->Aspects(); | ||||
|     BeginRendering(new_pass); | ||||
| } | ||||
|  | ||||
| void RenderpassCache::BeginRendering(const RenderPass& new_pass) { | ||||
|     if (pass == new_pass) [[likely]] { | ||||
|         num_draws++; | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     EndRendering(); | ||||
|     scheduler.Record([info = new_pass](vk::CommandBuffer cmdbuf) { | ||||
|         const vk::RenderPassBeginInfo renderpass_begin_info = { | ||||
|             .renderPass = info.render_pass, | ||||
|             .framebuffer = info.framebuffer, | ||||
|             .renderArea = info.render_area, | ||||
|             .clearValueCount = info.do_clear ? 1u : 0u, | ||||
|             .pClearValues = &info.clear, | ||||
|         }; | ||||
|         cmdbuf.beginRenderPass(renderpass_begin_info, vk::SubpassContents::eInline); | ||||
|     }); | ||||
|  | ||||
|     pass = new_pass; | ||||
| } | ||||
|  | ||||
| void RenderpassCache::EndRendering() { | ||||
|     if (!pass.render_pass) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     pass.render_pass = vk::RenderPass{}; | ||||
|     scheduler.Record([images = images, aspects = aspects](vk::CommandBuffer cmdbuf) { | ||||
|         u32 num_barriers = 0; | ||||
|         vk::PipelineStageFlags pipeline_flags{}; | ||||
|         std::array<vk::ImageMemoryBarrier, 2> barriers; | ||||
|         for (u32 i = 0; i < images.size(); i++) { | ||||
|             if (!images[i]) { | ||||
|                 continue; | ||||
|             } | ||||
|             const bool is_color = static_cast<bool>(aspects[i] & vk::ImageAspectFlagBits::eColor); | ||||
|             if (is_color) { | ||||
|                 pipeline_flags |= vk::PipelineStageFlagBits::eColorAttachmentOutput; | ||||
|             } else { | ||||
|                 pipeline_flags |= vk::PipelineStageFlagBits::eEarlyFragmentTests | | ||||
|                                   vk::PipelineStageFlagBits::eLateFragmentTests; | ||||
|             } | ||||
|             barriers[num_barriers++] = vk::ImageMemoryBarrier{ | ||||
|                 .srcAccessMask = is_color ? vk::AccessFlagBits::eColorAttachmentWrite | ||||
|                                           : vk::AccessFlagBits::eDepthStencilAttachmentWrite, | ||||
|                 .dstAccessMask = | ||||
|                     vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead, | ||||
|                 .oldLayout = vk::ImageLayout::eGeneral, | ||||
|                 .newLayout = vk::ImageLayout::eGeneral, | ||||
|                 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|                 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||||
|                 .image = images[i], | ||||
|                 .subresourceRange{ | ||||
|                     .aspectMask = aspects[i], | ||||
|                     .baseMipLevel = 0, | ||||
|                     .levelCount = 1, | ||||
|                     .baseArrayLayer = 0, | ||||
|                     .layerCount = VK_REMAINING_ARRAY_LAYERS, | ||||
|                 }, | ||||
|             }; | ||||
|         } | ||||
|         cmdbuf.endRenderPass(); | ||||
|         cmdbuf.pipelineBarrier(pipeline_flags, | ||||
|                                vk::PipelineStageFlagBits::eFragmentShader | | ||||
|                                    vk::PipelineStageFlagBits::eTransfer, | ||||
|                                vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, | ||||
|                                num_barriers, barriers.data()); | ||||
|     }); | ||||
|  | ||||
|     // The Mali guide recommends flushing at the end of each major renderpass | ||||
|     // Testing has shown this has a significant effect on rendering performance | ||||
|     if (num_draws > MIN_DRAWS_TO_FLUSH && instance.ShouldFlush()) { | ||||
|         scheduler.Flush(); | ||||
|         num_draws = 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| vk::RenderPass RenderpassCache::GetRenderpass(VideoCore::PixelFormat color, | ||||
|                                               VideoCore::PixelFormat depth, bool is_clear) { | ||||
|     std::scoped_lock lock{cache_mutex}; | ||||
|  | ||||
|     const u32 color_index = | ||||
|         color == VideoCore::PixelFormat::Invalid ? MAX_COLOR_FORMATS : static_cast<u32>(color); | ||||
|     const u32 depth_index = depth == VideoCore::PixelFormat::Invalid | ||||
|                                 ? MAX_DEPTH_FORMATS | ||||
|                                 : (static_cast<u32>(depth) - 14); | ||||
|  | ||||
|     ASSERT_MSG(color_index <= MAX_COLOR_FORMATS && depth_index <= MAX_DEPTH_FORMATS, | ||||
|                "Invalid color index {} and/or depth_index {}", color_index, depth_index); | ||||
|  | ||||
|     vk::UniqueRenderPass& renderpass = cached_renderpasses[color_index][depth_index][is_clear]; | ||||
|     if (!renderpass) { | ||||
|         const vk::Format color_format = instance.GetTraits(color).native; | ||||
|         const vk::Format depth_format = instance.GetTraits(depth).native; | ||||
|         const vk::AttachmentLoadOp load_op = | ||||
|             is_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad; | ||||
|         renderpass = CreateRenderPass(color_format, depth_format, load_op); | ||||
|     } | ||||
|  | ||||
|     return *renderpass; | ||||
| } | ||||
|  | ||||
| vk::UniqueRenderPass RenderpassCache::CreateRenderPass(vk::Format color, vk::Format depth, | ||||
|                                                        vk::AttachmentLoadOp load_op) const { | ||||
|     u32 attachment_count = 0; | ||||
|     std::array<vk::AttachmentDescription, 2> attachments; | ||||
|  | ||||
|     bool use_color = false; | ||||
|     vk::AttachmentReference color_attachment_ref{}; | ||||
|     bool use_depth = false; | ||||
|     vk::AttachmentReference depth_attachment_ref{}; | ||||
|  | ||||
|     if (color != vk::Format::eUndefined) { | ||||
|         attachments[attachment_count] = vk::AttachmentDescription{ | ||||
|             .format = color, | ||||
|             .loadOp = load_op, | ||||
|             .storeOp = vk::AttachmentStoreOp::eStore, | ||||
|             .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, | ||||
|             .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, | ||||
|             .initialLayout = vk::ImageLayout::eGeneral, | ||||
|             .finalLayout = vk::ImageLayout::eGeneral, | ||||
|         }; | ||||
|  | ||||
|         color_attachment_ref = vk::AttachmentReference{ | ||||
|             .attachment = attachment_count++, | ||||
|             .layout = vk::ImageLayout::eGeneral, | ||||
|         }; | ||||
|  | ||||
|         use_color = true; | ||||
|     } | ||||
|  | ||||
|     if (depth != vk::Format::eUndefined) { | ||||
|         attachments[attachment_count] = vk::AttachmentDescription{ | ||||
|             .format = depth, | ||||
|             .loadOp = load_op, | ||||
|             .storeOp = vk::AttachmentStoreOp::eStore, | ||||
|             .stencilLoadOp = load_op, | ||||
|             .stencilStoreOp = vk::AttachmentStoreOp::eStore, | ||||
|             .initialLayout = vk::ImageLayout::eGeneral, | ||||
|             .finalLayout = vk::ImageLayout::eGeneral, | ||||
|         }; | ||||
|  | ||||
|         depth_attachment_ref = vk::AttachmentReference{ | ||||
|             .attachment = attachment_count++, | ||||
|             .layout = vk::ImageLayout::eGeneral, | ||||
|         }; | ||||
|  | ||||
|         use_depth = true; | ||||
|     } | ||||
|  | ||||
|     const vk::SubpassDescription subpass = { | ||||
|         .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, | ||||
|         .inputAttachmentCount = 0, | ||||
|         .pInputAttachments = nullptr, | ||||
|         .colorAttachmentCount = use_color ? 1u : 0u, | ||||
|         .pColorAttachments = &color_attachment_ref, | ||||
|         .pResolveAttachments = 0, | ||||
|         .pDepthStencilAttachment = use_depth ? &depth_attachment_ref : nullptr, | ||||
|     }; | ||||
|  | ||||
|     const vk::RenderPassCreateInfo renderpass_info = { | ||||
|         .attachmentCount = attachment_count, | ||||
|         .pAttachments = attachments.data(), | ||||
|         .subpassCount = 1, | ||||
|         .pSubpasses = &subpass, | ||||
|         .dependencyCount = 0, | ||||
|         .pDependencies = nullptr, | ||||
|     }; | ||||
|  | ||||
|     return instance.GetDevice().createRenderPassUnique(renderpass_info); | ||||
| } | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										74
									
								
								src/video_core/renderer_vulkan/vk_renderpass_cache.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/video_core/renderer_vulkan/vk_renderpass_cache.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <mutex> | ||||
|  | ||||
| #include "common/math_util.h" | ||||
| #include "video_core/renderer_vulkan/vk_common.h" | ||||
|  | ||||
| namespace VideoCore { | ||||
| enum class PixelFormat : u32; | ||||
| } | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| class Instance; | ||||
| class Scheduler; | ||||
| class Framebuffer; | ||||
|  | ||||
| struct RenderPass { | ||||
|     vk::Framebuffer framebuffer; | ||||
|     vk::RenderPass render_pass; | ||||
|     vk::Rect2D render_area; | ||||
|     vk::ClearValue clear; | ||||
|     bool do_clear; | ||||
|  | ||||
|     bool operator==(const RenderPass& other) const noexcept { | ||||
|         return std::tie(framebuffer, render_pass, render_area, do_clear) == | ||||
|                    std::tie(other.framebuffer, other.render_pass, other.render_area, | ||||
|                             other.do_clear) && | ||||
|                std::memcmp(&clear, &other.clear, sizeof(vk::ClearValue)) == 0; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| class RenderpassCache { | ||||
|     static constexpr size_t MAX_COLOR_FORMATS = 13; | ||||
|     static constexpr size_t MAX_DEPTH_FORMATS = 4; | ||||
|  | ||||
| public: | ||||
|     explicit RenderpassCache(const Instance& instance, Scheduler& scheduler); | ||||
|     ~RenderpassCache(); | ||||
|  | ||||
|     /// Begins a new renderpass with the provided framebuffer as render target. | ||||
|     void BeginRendering(const Framebuffer* framebuffer, Common::Rectangle<u32> draw_rect); | ||||
|  | ||||
|     /// Begins a new renderpass with the provided render state. | ||||
|     void BeginRendering(const RenderPass& new_pass); | ||||
|  | ||||
|     /// Exits from any currently active renderpass instance | ||||
|     void EndRendering(); | ||||
|  | ||||
|     /// Returns the renderpass associated with the color-depth format pair | ||||
|     vk::RenderPass GetRenderpass(VideoCore::PixelFormat color, VideoCore::PixelFormat depth, | ||||
|                                  bool is_clear); | ||||
|  | ||||
| private: | ||||
|     /// Creates a renderpass configured appropriately and stores it in cached_renderpasses | ||||
|     vk::UniqueRenderPass CreateRenderPass(vk::Format color, vk::Format depth, | ||||
|                                           vk::AttachmentLoadOp load_op) const; | ||||
|  | ||||
| private: | ||||
|     const Instance& instance; | ||||
|     Scheduler& scheduler; | ||||
|     vk::UniqueRenderPass cached_renderpasses[MAX_COLOR_FORMATS + 1][MAX_DEPTH_FORMATS + 1][2]; | ||||
|     std::mutex cache_mutex; | ||||
|     std::array<vk::Image, 2> images; | ||||
|     std::array<vk::ImageAspectFlags, 2> aspects; | ||||
|     RenderPass pass{}; | ||||
|     u32 num_draws{}; | ||||
| }; | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										113
									
								
								src/video_core/renderer_vulkan/vk_resource_pool.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/video_core/renderer_vulkan/vk_resource_pool.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| // Copyright 2020 yuzu Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <cstddef> | ||||
| #include <optional> | ||||
| #include "video_core/renderer_vulkan/vk_instance.h" | ||||
| #include "video_core/renderer_vulkan/vk_master_semaphore.h" | ||||
| #include "video_core/renderer_vulkan/vk_resource_pool.h" | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| ResourcePool::ResourcePool(MasterSemaphore* master_semaphore_, size_t grow_step_) | ||||
|     : master_semaphore{master_semaphore_}, grow_step{grow_step_} {} | ||||
|  | ||||
| std::size_t ResourcePool::CommitResource() { | ||||
|     // Refresh semaphore to query updated results | ||||
|     master_semaphore->Refresh(); | ||||
|     const u64 gpu_tick = master_semaphore->KnownGpuTick(); | ||||
|     const auto search = [this, gpu_tick](std::size_t begin, | ||||
|                                          std::size_t end) -> std::optional<std::size_t> { | ||||
|         for (std::size_t iterator = begin; iterator < end; ++iterator) { | ||||
|             if (gpu_tick >= ticks[iterator]) { | ||||
|                 ticks[iterator] = master_semaphore->CurrentTick(); | ||||
|                 return iterator; | ||||
|             } | ||||
|         } | ||||
|         return std::nullopt; | ||||
|     }; | ||||
|  | ||||
|     // Try to find a free resource from the hinted position to the end. | ||||
|     std::optional<std::size_t> found = search(hint_iterator, ticks.size()); | ||||
|     if (!found) { | ||||
|         // Search from beginning to the hinted position. | ||||
|         found = search(0, hint_iterator); | ||||
|         if (!found) { | ||||
|             // Both searches failed, the pool is full; handle it. | ||||
|             const std::size_t free_resource = ManageOverflow(); | ||||
|  | ||||
|             ticks[free_resource] = master_semaphore->CurrentTick(); | ||||
|             found = free_resource; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Free iterator is hinted to the resource after the one that's been commited. | ||||
|     hint_iterator = (*found + 1) % ticks.size(); | ||||
|     return *found; | ||||
| } | ||||
|  | ||||
| std::size_t ResourcePool::ManageOverflow() { | ||||
|     const std::size_t old_capacity = ticks.size(); | ||||
|     Grow(); | ||||
|  | ||||
|     // The last entry is guaranted to be free, since it's the first element of the freshly | ||||
|     // allocated resources. | ||||
|     return old_capacity; | ||||
| } | ||||
|  | ||||
| void ResourcePool::Grow() { | ||||
|     const size_t old_capacity = ticks.size(); | ||||
|     ticks.resize(old_capacity + grow_step); | ||||
|     Allocate(old_capacity, old_capacity + grow_step); | ||||
| } | ||||
|  | ||||
| constexpr size_t COMMAND_BUFFER_POOL_SIZE = 4; | ||||
|  | ||||
| struct CommandPool::Pool { | ||||
|     vk::CommandPool handle; | ||||
|     std::array<vk::CommandBuffer, COMMAND_BUFFER_POOL_SIZE> cmdbufs; | ||||
| }; | ||||
|  | ||||
| CommandPool::CommandPool(const Instance& instance, MasterSemaphore* master_semaphore) | ||||
|     : ResourcePool{master_semaphore, COMMAND_BUFFER_POOL_SIZE}, instance{instance} {} | ||||
|  | ||||
| CommandPool::~CommandPool() { | ||||
|     vk::Device device = instance.GetDevice(); | ||||
|     for (Pool& pool : pools) { | ||||
|         device.destroyCommandPool(pool.handle); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void CommandPool::Allocate(std::size_t begin, std::size_t end) { | ||||
|     // Command buffers are going to be commited, recorded, executed every single usage cycle. | ||||
|     // They are also going to be reseted when commited. | ||||
|     Pool& pool = pools.emplace_back(); | ||||
|  | ||||
|     const vk::CommandPoolCreateInfo pool_create_info = { | ||||
|         .flags = vk::CommandPoolCreateFlagBits::eTransient | | ||||
|                  vk::CommandPoolCreateFlagBits::eResetCommandBuffer, | ||||
|         .queueFamilyIndex = instance.GetGraphicsQueueFamilyIndex(), | ||||
|     }; | ||||
|  | ||||
|     vk::Device device = instance.GetDevice(); | ||||
|     pool.handle = device.createCommandPool(pool_create_info); | ||||
|  | ||||
|     const vk::CommandBufferAllocateInfo buffer_alloc_info = { | ||||
|         .commandPool = pool.handle, | ||||
|         .level = vk::CommandBufferLevel::ePrimary, | ||||
|         .commandBufferCount = COMMAND_BUFFER_POOL_SIZE, | ||||
|     }; | ||||
|  | ||||
|     auto buffers = device.allocateCommandBuffers(buffer_alloc_info); | ||||
|     std::copy(buffers.begin(), buffers.end(), pool.cmdbufs.begin()); | ||||
| } | ||||
|  | ||||
| vk::CommandBuffer CommandPool::Commit() { | ||||
|     const std::size_t index = CommitResource(); | ||||
|     const auto pool_index = index / COMMAND_BUFFER_POOL_SIZE; | ||||
|     const auto sub_index = index % COMMAND_BUFFER_POOL_SIZE; | ||||
|     return pools[pool_index].cmdbufs[sub_index]; | ||||
| } | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										67
									
								
								src/video_core/renderer_vulkan/vk_resource_pool.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/video_core/renderer_vulkan/vk_resource_pool.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| // Copyright 2020 yuzu Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <vector> | ||||
| #include "common/common_types.h" | ||||
| #include "video_core/renderer_vulkan/vk_common.h" | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| class Instance; | ||||
| class MasterSemaphore; | ||||
|  | ||||
| /** | ||||
|  * Handles a pool of resources protected by fences. Manages resource overflow allocating more | ||||
|  * resources. | ||||
|  */ | ||||
| class ResourcePool { | ||||
| public: | ||||
|     explicit ResourcePool() = default; | ||||
|     explicit ResourcePool(MasterSemaphore* master_semaphore, std::size_t grow_step); | ||||
|     virtual ~ResourcePool() = default; | ||||
|  | ||||
|     ResourcePool& operator=(ResourcePool&&) noexcept = default; | ||||
|     ResourcePool(ResourcePool&&) noexcept = default; | ||||
|  | ||||
|     ResourcePool& operator=(const ResourcePool&) = default; | ||||
|     ResourcePool(const ResourcePool&) = default; | ||||
|  | ||||
| protected: | ||||
|     std::size_t CommitResource(); | ||||
|  | ||||
|     /// Called when a chunk of resources have to be allocated. | ||||
|     virtual void Allocate(std::size_t begin, std::size_t end) = 0; | ||||
|  | ||||
| private: | ||||
|     /// Manages pool overflow allocating new resources. | ||||
|     std::size_t ManageOverflow(); | ||||
|  | ||||
|     /// Allocates a new page of resources. | ||||
|     void Grow(); | ||||
|  | ||||
| protected: | ||||
|     MasterSemaphore* master_semaphore{nullptr}; | ||||
|     std::size_t grow_step = 0;     ///< Number of new resources created after an overflow | ||||
|     std::size_t hint_iterator = 0; ///< Hint to where the next free resources is likely to be found | ||||
|     std::vector<u64> ticks;        ///< Ticks for each resource | ||||
| }; | ||||
|  | ||||
| class CommandPool final : public ResourcePool { | ||||
| public: | ||||
|     explicit CommandPool(const Instance& instance, MasterSemaphore* master_semaphore); | ||||
|     ~CommandPool() override; | ||||
|  | ||||
|     void Allocate(std::size_t begin, std::size_t end) override; | ||||
|  | ||||
|     vk::CommandBuffer Commit(); | ||||
|  | ||||
| private: | ||||
|     struct Pool; | ||||
|     const Instance& instance; | ||||
|     std::vector<Pool> pools; | ||||
| }; | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										202
									
								
								src/video_core/renderer_vulkan/vk_scheduler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								src/video_core/renderer_vulkan/vk_scheduler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
| // Copyright 2019 yuzu Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <mutex> | ||||
| #include <utility> | ||||
| #include "common/microprofile.h" | ||||
| #include "common/settings.h" | ||||
| #include "common/thread.h" | ||||
| #include "video_core/renderer_vulkan/vk_instance.h" | ||||
| #include "video_core/renderer_vulkan/vk_renderpass_cache.h" | ||||
| #include "video_core/renderer_vulkan/vk_scheduler.h" | ||||
|  | ||||
| MICROPROFILE_DEFINE(Vulkan_WaitForWorker, "Vulkan", "Wait for worker", MP_RGB(255, 192, 192)); | ||||
| MICROPROFILE_DEFINE(Vulkan_Submit, "Vulkan", "Submit Exectution", MP_RGB(255, 192, 255)); | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| std::unique_ptr<MasterSemaphore> MakeMasterSemaphore(const Instance& instance) { | ||||
|     if (instance.IsTimelineSemaphoreSupported()) { | ||||
|         return std::make_unique<MasterSemaphoreTimeline>(instance); | ||||
|     } else { | ||||
|         return std::make_unique<MasterSemaphoreFence>(instance); | ||||
|     } | ||||
| } | ||||
|  | ||||
| } // Anonymous namespace | ||||
|  | ||||
| void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) { | ||||
|     auto command = first; | ||||
|     while (command != nullptr) { | ||||
|         auto next = command->GetNext(); | ||||
|         command->Execute(cmdbuf); | ||||
|         command->~Command(); | ||||
|         command = next; | ||||
|     } | ||||
|     submit = false; | ||||
|     command_offset = 0; | ||||
|     first = nullptr; | ||||
|     last = nullptr; | ||||
| } | ||||
|  | ||||
| Scheduler::Scheduler(const Instance& instance, RenderpassCache& renderpass_cache) | ||||
|     : renderpass_cache{renderpass_cache}, master_semaphore{MakeMasterSemaphore(instance)}, | ||||
|       command_pool{instance, master_semaphore.get()}, use_worker_thread{true} { | ||||
|     AllocateWorkerCommandBuffers(); | ||||
|     if (use_worker_thread) { | ||||
|         AcquireNewChunk(); | ||||
|         worker_thread = std::jthread([this](std::stop_token token) { WorkerThread(token); }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| Scheduler::~Scheduler() = default; | ||||
|  | ||||
| void Scheduler::Flush(vk::Semaphore signal, vk::Semaphore wait) { | ||||
|     // When flushing, we only send data to the worker thread; no waiting is necessary. | ||||
|     SubmitExecution(signal, wait); | ||||
| } | ||||
|  | ||||
| void Scheduler::Finish(vk::Semaphore signal, vk::Semaphore wait) { | ||||
|     // When finishing, we need to wait for the submission to have executed on the device. | ||||
|     const u64 presubmit_tick = CurrentTick(); | ||||
|     SubmitExecution(signal, wait); | ||||
|     Wait(presubmit_tick); | ||||
| } | ||||
|  | ||||
| void Scheduler::WaitWorker() { | ||||
|     if (!use_worker_thread) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     MICROPROFILE_SCOPE(Vulkan_WaitForWorker); | ||||
|     DispatchWork(); | ||||
|  | ||||
|     // Ensure the queue is drained. | ||||
|     { | ||||
|         std::unique_lock ql{queue_mutex}; | ||||
|         event_cv.wait(ql, [this] { return work_queue.empty(); }); | ||||
|     } | ||||
|  | ||||
|     // Now wait for execution to finish. | ||||
|     // This needs to be done in the same order as WorkerThread. | ||||
|     std::scoped_lock el{execution_mutex}; | ||||
| } | ||||
|  | ||||
| void Scheduler::Wait(u64 tick) { | ||||
|     if (tick >= master_semaphore->CurrentTick()) { | ||||
|         // Make sure we are not waiting for the current tick without signalling | ||||
|         Flush(); | ||||
|     } | ||||
|     master_semaphore->Wait(tick); | ||||
| } | ||||
|  | ||||
| void Scheduler::DispatchWork() { | ||||
|     if (!use_worker_thread || chunk->Empty()) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     { | ||||
|         std::scoped_lock ql{queue_mutex}; | ||||
|         work_queue.push(std::move(chunk)); | ||||
|     } | ||||
|  | ||||
|     event_cv.notify_all(); | ||||
|     AcquireNewChunk(); | ||||
| } | ||||
|  | ||||
| void Scheduler::WorkerThread(std::stop_token stop_token) { | ||||
|     Common::SetCurrentThreadName("VulkanWorker"); | ||||
|  | ||||
|     const auto TryPopQueue{[this](auto& work) -> bool { | ||||
|         if (work_queue.empty()) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         work = std::move(work_queue.front()); | ||||
|         work_queue.pop(); | ||||
|         event_cv.notify_all(); | ||||
|         return true; | ||||
|     }}; | ||||
|  | ||||
|     while (!stop_token.stop_requested()) { | ||||
|         std::unique_ptr<CommandChunk> work; | ||||
|  | ||||
|         { | ||||
|             std::unique_lock lk{queue_mutex}; | ||||
|  | ||||
|             // Wait for work. | ||||
|             Common::CondvarWait(event_cv, lk, stop_token, [&] { return TryPopQueue(work); }); | ||||
|  | ||||
|             // If we've been asked to stop, we're done. | ||||
|             if (stop_token.stop_requested()) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // Exchange lock ownership so that we take the execution lock before | ||||
|             // the queue lock goes out of scope. This allows us to force execution | ||||
|             // to complete in the next step. | ||||
|             std::exchange(lk, std::unique_lock{execution_mutex}); | ||||
|  | ||||
|             // Perform the work, tracking whether the chunk was a submission | ||||
|             // before executing. | ||||
|             const bool has_submit = work->HasSubmit(); | ||||
|             work->ExecuteAll(current_cmdbuf); | ||||
|  | ||||
|             // If the chunk was a submission, reallocate the command buffer. | ||||
|             if (has_submit) { | ||||
|                 AllocateWorkerCommandBuffers(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         { | ||||
|             std::scoped_lock rl{reserve_mutex}; | ||||
|  | ||||
|             // Recycle the chunk back to the reserve. | ||||
|             chunk_reserve.emplace_back(std::move(work)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Scheduler::AllocateWorkerCommandBuffers() { | ||||
|     const vk::CommandBufferBeginInfo begin_info = { | ||||
|         .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit, | ||||
|     }; | ||||
|  | ||||
|     current_cmdbuf = command_pool.Commit(); | ||||
|     current_cmdbuf.begin(begin_info); | ||||
| } | ||||
|  | ||||
| void Scheduler::SubmitExecution(vk::Semaphore signal_semaphore, vk::Semaphore wait_semaphore) { | ||||
|     state = StateFlags::AllDirty; | ||||
|     const u64 signal_value = master_semaphore->NextTick(); | ||||
|  | ||||
|     renderpass_cache.EndRendering(); | ||||
|     Record([signal_semaphore, wait_semaphore, signal_value, this](vk::CommandBuffer cmdbuf) { | ||||
|         MICROPROFILE_SCOPE(Vulkan_Submit); | ||||
|         std::scoped_lock lock{submit_mutex}; | ||||
|         master_semaphore->SubmitWork(cmdbuf, wait_semaphore, signal_semaphore, signal_value); | ||||
|     }); | ||||
|  | ||||
|     if (!use_worker_thread) { | ||||
|         AllocateWorkerCommandBuffers(); | ||||
|     } else { | ||||
|         chunk->MarkSubmit(); | ||||
|         DispatchWork(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Scheduler::AcquireNewChunk() { | ||||
|     std::scoped_lock lock{reserve_mutex}; | ||||
|     if (chunk_reserve.empty()) { | ||||
|         chunk = std::make_unique<CommandChunk>(); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     chunk = std::move(chunk_reserve.back()); | ||||
|     chunk_reserve.pop_back(); | ||||
| } | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										210
									
								
								src/video_core/renderer_vulkan/vk_scheduler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								src/video_core/renderer_vulkan/vk_scheduler.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,210 @@ | ||||
| // Copyright 2019 yuzu Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
| #include <utility> | ||||
| #include "common/alignment.h" | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/polyfill_thread.h" | ||||
| #include "video_core/renderer_vulkan/vk_master_semaphore.h" | ||||
| #include "video_core/renderer_vulkan/vk_resource_pool.h" | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| enum class StateFlags { | ||||
|     AllDirty = 0, | ||||
|     Renderpass = 1 << 0, | ||||
|     Pipeline = 1 << 1, | ||||
|     DescriptorSets = 1 << 2 | ||||
| }; | ||||
|  | ||||
| DECLARE_ENUM_FLAG_OPERATORS(StateFlags) | ||||
|  | ||||
| class Instance; | ||||
| class RenderpassCache; | ||||
|  | ||||
| /// The scheduler abstracts command buffer and fence management with an interface that's able to do | ||||
| /// OpenGL-like operations on Vulkan command buffers. | ||||
| class Scheduler { | ||||
| public: | ||||
|     explicit Scheduler(const Instance& instance, RenderpassCache& renderpass_cache); | ||||
|     ~Scheduler(); | ||||
|  | ||||
|     /// Sends the current execution context to the GPU. | ||||
|     void Flush(vk::Semaphore signal = nullptr, vk::Semaphore wait = nullptr); | ||||
|  | ||||
|     /// Sends the current execution context to the GPU and waits for it to complete. | ||||
|     void Finish(vk::Semaphore signal = nullptr, vk::Semaphore wait = nullptr); | ||||
|  | ||||
|     /// Waits for the worker thread to finish executing everything. After this function returns it's | ||||
|     /// safe to touch worker resources. | ||||
|     void WaitWorker(); | ||||
|  | ||||
|     /// Waits for the given tick to trigger on the GPU. | ||||
|     void Wait(u64 tick); | ||||
|  | ||||
|     /// Sends currently recorded work to the worker thread. | ||||
|     void DispatchWork(); | ||||
|  | ||||
|     /// Records the command to the current chunk. | ||||
|     template <typename T> | ||||
|     void Record(T&& command) { | ||||
|         if (!use_worker_thread) { | ||||
|             command(current_cmdbuf); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (chunk->Record(command)) { | ||||
|             return; | ||||
|         } | ||||
|         DispatchWork(); | ||||
|         (void)chunk->Record(command); | ||||
|     } | ||||
|  | ||||
|     /// Marks the provided state as non dirty | ||||
|     void MarkStateNonDirty(StateFlags flag) noexcept { | ||||
|         state |= flag; | ||||
|     } | ||||
|  | ||||
|     /// Marks the provided state as dirty | ||||
|     void MakeDirty(StateFlags flag) noexcept { | ||||
|         state &= ~flag; | ||||
|     } | ||||
|  | ||||
|     /// Returns true if the state is dirty | ||||
|     [[nodiscard]] bool IsStateDirty(StateFlags flag) const noexcept { | ||||
|         return False(state & flag); | ||||
|     } | ||||
|  | ||||
|     /// Returns the current command buffer tick. | ||||
|     [[nodiscard]] u64 CurrentTick() const noexcept { | ||||
|         return master_semaphore->CurrentTick(); | ||||
|     } | ||||
|  | ||||
|     /// Returns true when a tick has been triggered by the GPU. | ||||
|     [[nodiscard]] bool IsFree(u64 tick) const noexcept { | ||||
|         return master_semaphore->IsFree(tick); | ||||
|     } | ||||
|  | ||||
|     /// Returns the master timeline semaphore. | ||||
|     [[nodiscard]] MasterSemaphore* GetMasterSemaphore() noexcept { | ||||
|         return master_semaphore.get(); | ||||
|     } | ||||
|  | ||||
|     std::mutex submit_mutex; | ||||
|  | ||||
| private: | ||||
|     class Command { | ||||
|     public: | ||||
|         virtual ~Command() = default; | ||||
|  | ||||
|         virtual void Execute(vk::CommandBuffer cmdbuf) const = 0; | ||||
|  | ||||
|         Command* GetNext() const { | ||||
|             return next; | ||||
|         } | ||||
|  | ||||
|         void SetNext(Command* next_) { | ||||
|             next = next_; | ||||
|         } | ||||
|  | ||||
|     private: | ||||
|         Command* next = nullptr; | ||||
|     }; | ||||
|  | ||||
|     template <typename T> | ||||
|     class TypedCommand final : public Command { | ||||
|     public: | ||||
|         explicit TypedCommand(T&& command_) : command{std::move(command_)} {} | ||||
|         ~TypedCommand() override = default; | ||||
|  | ||||
|         TypedCommand(TypedCommand&&) = delete; | ||||
|         TypedCommand& operator=(TypedCommand&&) = delete; | ||||
|  | ||||
|         void Execute(vk::CommandBuffer cmdbuf) const override { | ||||
|             command(cmdbuf); | ||||
|         } | ||||
|  | ||||
|     private: | ||||
|         T command; | ||||
|     }; | ||||
|  | ||||
|     class CommandChunk final { | ||||
|     public: | ||||
|         void ExecuteAll(vk::CommandBuffer cmdbuf); | ||||
|  | ||||
|         template <typename T> | ||||
|         bool Record(T& command) { | ||||
|             using FuncType = TypedCommand<T>; | ||||
|             static_assert(sizeof(FuncType) < sizeof(data), "Lambda is too large"); | ||||
|  | ||||
|             recorded_counts++; | ||||
|             command_offset = Common::AlignUp(command_offset, alignof(FuncType)); | ||||
|             if (command_offset > sizeof(data) - sizeof(FuncType)) { | ||||
|                 return false; | ||||
|             } | ||||
|             Command* const current_last = last; | ||||
|             last = new (data.data() + command_offset) FuncType(std::move(command)); | ||||
|  | ||||
|             if (current_last) { | ||||
|                 current_last->SetNext(last); | ||||
|             } else { | ||||
|                 first = last; | ||||
|             } | ||||
|             command_offset += sizeof(FuncType); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         void MarkSubmit() { | ||||
|             submit = true; | ||||
|         } | ||||
|  | ||||
|         bool Empty() const { | ||||
|             return recorded_counts == 0; | ||||
|         } | ||||
|  | ||||
|         bool HasSubmit() const { | ||||
|             return submit; | ||||
|         } | ||||
|  | ||||
|     private: | ||||
|         Command* first = nullptr; | ||||
|         Command* last = nullptr; | ||||
|  | ||||
|         std::size_t recorded_counts = 0; | ||||
|         std::size_t command_offset = 0; | ||||
|         bool submit = false; | ||||
|         alignas(std::max_align_t) std::array<u8, 0x8000> data{}; | ||||
|     }; | ||||
|  | ||||
| private: | ||||
|     void WorkerThread(std::stop_token stop_token); | ||||
|  | ||||
|     void AllocateWorkerCommandBuffers(); | ||||
|  | ||||
|     void SubmitExecution(vk::Semaphore signal_semaphore, vk::Semaphore wait_semaphore); | ||||
|  | ||||
|     void AcquireNewChunk(); | ||||
|  | ||||
| private: | ||||
|     RenderpassCache& renderpass_cache; | ||||
|     std::unique_ptr<MasterSemaphore> master_semaphore; | ||||
|     CommandPool command_pool; | ||||
|     std::unique_ptr<CommandChunk> chunk; | ||||
|     std::queue<std::unique_ptr<CommandChunk>> work_queue; | ||||
|     std::vector<std::unique_ptr<CommandChunk>> chunk_reserve; | ||||
|     vk::CommandBuffer current_cmdbuf; | ||||
|     StateFlags state{}; | ||||
|     std::mutex execution_mutex; | ||||
|     std::mutex reserve_mutex; | ||||
|     std::mutex queue_mutex; | ||||
|     std::condition_variable_any event_cv; | ||||
|     std::jthread worker_thread; | ||||
|     bool use_worker_thread; | ||||
| }; | ||||
|  | ||||
| } // namespace Vulkan | ||||
| @@ -46,7 +46,7 @@ static std::string GetVertexInterfaceDeclaration(bool is_output, bool use_clip_p | ||||
|     if (is_output) { | ||||
|         // gl_PerVertex redeclaration is required for separate shader object | ||||
|         out += "out gl_PerVertex {\n"; | ||||
|         out += "    vec4 gl_Position;\n"; | ||||
|         out += "    invariant vec4 gl_Position;\n"; | ||||
|         if (use_clip_planes) { | ||||
|             out += "    float gl_ClipDistance[2];\n"; | ||||
|         } | ||||
| @@ -236,6 +236,10 @@ PicaFSConfig::PicaFSConfig(const Pica::Regs& regs, const Instance& instance) { | ||||
|     state.shadow_rendering.Assign(regs.framebuffer.output_merger.fragment_operation_mode == | ||||
|                                   FramebufferRegs::FragmentOperationMode::Shadow); | ||||
|     state.shadow_texture_orthographic.Assign(regs.texturing.shadow.orthographic != 0); | ||||
|  | ||||
|     // We only need fragment shader interlock when shadow rendering. | ||||
|     state.use_fragment_shader_interlock.Assign(state.shadow_rendering && | ||||
|                                                instance.IsFragmentShaderInterlockSupported()); | ||||
| } | ||||
|  | ||||
| void PicaShaderConfigCommon::Init(const Pica::RasterizerRegs& rasterizer, | ||||
| @@ -1194,8 +1198,31 @@ float ProcTexNoiseCoef(vec2 x) { | ||||
|  | ||||
| std::string GenerateFragmentShader(const PicaFSConfig& config) { | ||||
|     const auto& state = config.state; | ||||
|     std::string out = "#version 450 core\n" | ||||
|                       "#extension GL_ARB_separate_shader_objects : enable\n\n"; | ||||
|     std::string out = R"( | ||||
| #version 450 core | ||||
| #extension GL_ARB_separate_shader_objects : enable | ||||
| )"; | ||||
|  | ||||
|     if (state.use_fragment_shader_interlock) { | ||||
|         out += R"( | ||||
| #if defined(GL_ARB_fragment_shader_interlock) | ||||
| #extension GL_ARB_fragment_shader_interlock : enable | ||||
| #define beginInvocationInterlock beginInvocationInterlockARB | ||||
| #define endInvocationInterlock endInvocationInterlockARB | ||||
| #elif defined(GL_NV_fragment_shader_interlock) | ||||
| #extension GL_NV_fragment_shader_interlock : enable | ||||
| #define beginInvocationInterlock beginInvocationInterlockNV | ||||
| #define endInvocationInterlock endInvocationInterlockNV | ||||
| #elif defined(GL_INTEL_fragment_shader_ordering) | ||||
| #extension GL_INTEL_fragment_shader_ordering : enable | ||||
| #define beginInvocationInterlock beginFragmentShaderOrderingINTEL | ||||
| #define endInvocationInterlock | ||||
| #endif | ||||
|  | ||||
| layout(pixel_interlock_ordered) in; | ||||
| )"; | ||||
|     } | ||||
|  | ||||
|     out += GetVertexInterfaceDeclaration(false); | ||||
|  | ||||
|     out += R"( | ||||
| @@ -1280,6 +1307,19 @@ uint EncodeShadow(uvec2 pixel) { | ||||
|     return (pixel.x << 8) | pixel.y; | ||||
| } | ||||
|  | ||||
| uint UpdateShadow(uint pixel, uint d, uint s) { | ||||
|     uvec2 ref = DecodeShadow(pixel); | ||||
|     if (d < ref.x) { | ||||
|         if (s == 0u) { | ||||
|             ref.x = d; | ||||
|         } else { | ||||
|             s = uint(float(s) / (shadow_bias_constant + shadow_bias_linear * float(d) / float(ref.x))); | ||||
|             ref.y = min(s, ref.y); | ||||
|         } | ||||
|     } | ||||
|     return EncodeShadow(ref); | ||||
| } | ||||
|  | ||||
| float CompareShadow(uint pixel, uint z) { | ||||
|     uvec2 p = DecodeShadow(pixel); | ||||
|     return mix(float(p.y) * (1.0 / 255.0), 0.0, p.x <= z); | ||||
| @@ -1511,10 +1551,11 @@ vec4 secondary_fragment_color = vec4(0.0); | ||||
|                "gl_FragCoord.y < float(scissor_y2))) discard;\n"; | ||||
|     } | ||||
|  | ||||
|     // After perspective divide, OpenGL transform z_over_w from [-1, 1] to [near, far]. Here we use | ||||
|     // default near = 0 and far = 1, and undo the transformation to get the original z_over_w, then | ||||
|     // do our own transformation according to PICA specification. | ||||
|     out += "float z_over_w = 2.0 * gl_FragCoord.z - 1.0;\n" | ||||
|     // The PICA depth range is [-1, 0] while in Vulkan that range is [0, 1]. | ||||
|     // Thus in the vertex shader we flip the sign of the z component to place | ||||
|     // it in the correct range. Here we undo the transformation to get the original z_over_w, | ||||
|     // then do our own transformation according to PICA specification. | ||||
|     out += "float z_over_w = -gl_FragCoord.z;\n" | ||||
|            "float depth = z_over_w * depth_scale + depth_offset;\n"; | ||||
|     if (state.depthmap_enable == RasterizerRegs::DepthBuffering::WBuffering) { | ||||
|         out += "depth /= gl_FragCoord.w;\n"; | ||||
| @@ -1577,26 +1618,26 @@ vec4 secondary_fragment_color = vec4(0.0); | ||||
| uint d = uint(clamp(depth, 0.0, 1.0) * float(0xFFFFFF)); | ||||
| uint s = uint(last_tex_env_out.g * float(0xFF)); | ||||
| ivec2 image_coord = ivec2(gl_FragCoord.xy); | ||||
|  | ||||
| )"; | ||||
|         if (state.use_fragment_shader_interlock) { | ||||
|             out += R"( | ||||
| beginInvocationInterlock(); | ||||
| uint old_shadow = imageLoad(shadow_buffer, image_coord).x; | ||||
| uint new_shadow = UpdateShadow(old_shadow, d, s); | ||||
| imageStore(shadow_buffer, image_coord, uvec4(new_shadow)); | ||||
| endInvocationInterlock(); | ||||
| )"; | ||||
|         } else { | ||||
|             out += R"( | ||||
| uint old = imageLoad(shadow_buffer, image_coord).x; | ||||
| uint new1; | ||||
| uint old2; | ||||
| do { | ||||
|     old2 = old; | ||||
|  | ||||
|     uvec2 ref = DecodeShadow(old); | ||||
|     if (d < ref.x) { | ||||
|         if (s == 0u) { | ||||
|             ref.x = d; | ||||
|         } else { | ||||
|             s = uint(float(s) / (shadow_bias_constant + shadow_bias_linear * float(d) / float(ref.x))); | ||||
|             ref.y = min(s, ref.y); | ||||
|         } | ||||
|     } | ||||
|     new1 = EncodeShadow(ref); | ||||
|  | ||||
|     new1 = UpdateShadow(old, d, s); | ||||
| } while ((old = imageAtomicCompSwap(shadow_buffer, image_coord, old, new1)) != old2); | ||||
| )"; | ||||
|         } | ||||
|     } else { | ||||
|         out += "gl_FragDepth = depth;\n"; | ||||
|         // Round the final fragment color to maintain the PICA's 8 bits of precision | ||||
| @@ -1652,6 +1693,7 @@ std::string GenerateTrivialVertexShader(bool use_clip_planes) { | ||||
|     out += UniformBlockDef; | ||||
|  | ||||
|     out += R"( | ||||
| const float EPSILON_Z = 0.00000001f; | ||||
|  | ||||
| void main() { | ||||
|     primary_color = vert_color; | ||||
| @@ -1661,14 +1703,17 @@ void main() { | ||||
|     texcoord0_w = vert_texcoord0_w; | ||||
|     normquat = vert_normquat; | ||||
|     view = vert_view; | ||||
|     gl_Position = vert_position; | ||||
|     gl_Position.z = (gl_Position.z + gl_Position.w) / 2.0; | ||||
|     vec4 vtx_pos = vert_position; | ||||
|     if (abs(vtx_pos.z) < EPSILON_Z) { | ||||
|         vtx_pos.z = 0.f; | ||||
|     } | ||||
|     gl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w); | ||||
| )"; | ||||
|     if (use_clip_planes) { | ||||
|         out += R"( | ||||
|         gl_ClipDistance[0] = -vert_position.z; // fixed PICA clipping plane z <= 0 | ||||
|         gl_ClipDistance[0] = -vtx_pos.z; // fixed PICA clipping plane z <= 0 | ||||
|         if (enable_clip1) { | ||||
|             gl_ClipDistance[1] = dot(clip_coef, vert_position); | ||||
|             gl_ClipDistance[1] = dot(clip_coef, vtx_pos); | ||||
|         } else { | ||||
|             gl_ClipDistance[1] = 0; | ||||
|         } | ||||
| @@ -1768,6 +1813,7 @@ layout (set = 0, binding = 0, std140) uniform vs_config { | ||||
|             return "0.0"; | ||||
|         }; | ||||
|  | ||||
|         out += "const float EPSILON_Z = 0.00000001f;\n\n"; | ||||
|         out += "vec4 GetVertexQuaternion() {\n"; | ||||
|         out += "    return vec4(" + semantic(VSOutputAttributes::QUATERNION_X) + ", " + | ||||
|                semantic(VSOutputAttributes::QUATERNION_Y) + ", " + | ||||
| @@ -1780,8 +1826,10 @@ layout (set = 0, binding = 0, std140) uniform vs_config { | ||||
|                semantic(VSOutputAttributes::POSITION_Y) + ", " + | ||||
|                semantic(VSOutputAttributes::POSITION_Z) + ", " + | ||||
|                semantic(VSOutputAttributes::POSITION_W) + ");\n"; | ||||
|         out += "    gl_Position = vtx_pos;\n"; | ||||
|         out += "    gl_Position.z = (gl_Position.z + gl_Position.w) / 2.0;\n"; | ||||
|         out += "    if (abs(vtx_pos.z) < EPSILON_Z) {\n"; | ||||
|         out += "        vtx_pos.z = 0.f;\n"; | ||||
|         out += "    }\n"; | ||||
|         out += "    gl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w);\n"; | ||||
|         if (config.use_clip_planes) { | ||||
|             out += "    gl_ClipDistance[0] = -vtx_pos.z;\n"; // fixed PICA clipping plane z <= 0 | ||||
|             out += "    if (enable_clip1) {\n"; | ||||
| @@ -1855,6 +1903,7 @@ struct Vertex { | ||||
|         return "0.0"; | ||||
|     }; | ||||
|  | ||||
|     out += "const float EPSILON_Z = 0.00000001f;\n\n"; | ||||
|     out += "vec4 GetVertexQuaternion(Vertex vtx) {\n"; | ||||
|     out += "    return vec4(" + semantic(VSOutputAttributes::QUATERNION_X) + ", " + | ||||
|            semantic(VSOutputAttributes::QUATERNION_Y) + ", " + | ||||
| @@ -1867,8 +1916,10 @@ struct Vertex { | ||||
|            semantic(VSOutputAttributes::POSITION_Y) + ", " + | ||||
|            semantic(VSOutputAttributes::POSITION_Z) + ", " + | ||||
|            semantic(VSOutputAttributes::POSITION_W) + ");\n"; | ||||
|     out += "    gl_Position = vtx_pos;\n"; | ||||
|     out += "    gl_Position.z = (gl_Position.z + gl_Position.w) / 2.0;\n"; | ||||
|     out += "    if (abs(vtx_pos.z) < EPSILON_Z) {\n"; | ||||
|     out += "        vtx_pos.z = 0.f;\n"; | ||||
|     out += "    }\n"; | ||||
|     out += "    gl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w);\n"; | ||||
|     if (use_clip_planes) { | ||||
|         out += "    gl_ClipDistance[0] = -vtx_pos.z;\n"; // fixed PICA clipping plane z <= 0 | ||||
|         out += "    if (enable_clip1) {\n"; | ||||
|   | ||||
| @@ -55,6 +55,7 @@ struct PicaFSConfigState { | ||||
|         BitField<23, 4, Pica::FramebufferRegs::LogicOp> logic_op; | ||||
|         BitField<27, 1, u32> shadow_rendering; | ||||
|         BitField<28, 1, u32> shadow_texture_orthographic; | ||||
|         BitField<29, 1, u32> use_fragment_shader_interlock; | ||||
|     }; | ||||
|  | ||||
|     union { | ||||
|   | ||||
| @@ -53,7 +53,7 @@ void FragmentModule::Generate() { | ||||
|  | ||||
|     combiner_buffer = ConstF32(0.f, 0.f, 0.f, 0.f); | ||||
|     next_combiner_buffer = GetShaderDataMember(vec_ids.Get(4), ConstS32(27)); | ||||
|     last_tex_env_out = ConstF32(0.f, 0.f, 0.f, 0.f); | ||||
|     last_tex_env_out = rounded_primary_color; | ||||
|  | ||||
|     // Write shader bytecode to emulate PICA TEV stages | ||||
|     for (std::size_t index = 0; index < config.state.tev_stages.size(); ++index) { | ||||
| @@ -115,7 +115,7 @@ void FragmentModule::WriteDepth() { | ||||
|     const Id input_pointer_id{TypePointer(spv::StorageClass::Input, f32_id)}; | ||||
|     const Id gl_frag_coord_z{ | ||||
|         OpLoad(f32_id, OpAccessChain(input_pointer_id, gl_frag_coord_id, ConstU32(2u)))}; | ||||
|     const Id z_over_w{OpFma(f32_id, ConstF32(2.f), gl_frag_coord_z, ConstF32(-1.f))}; | ||||
|     const Id z_over_w{OpFNegate(f32_id, gl_frag_coord_z)}; | ||||
|     const Id depth_scale{GetShaderDataMember(f32_id, ConstS32(2))}; | ||||
|     const Id depth_offset{GetShaderDataMember(f32_id, ConstS32(3))}; | ||||
|     depth = OpFma(f32_id, z_over_w, depth_scale, depth_offset); | ||||
|   | ||||
| @@ -160,7 +160,7 @@ bool InitializeCompiler() { | ||||
|  | ||||
| vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device) { | ||||
|     if (!InitializeCompiler()) { | ||||
|         return VK_NULL_HANDLE; | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     EProfile profile = ECoreProfile; | ||||
| @@ -182,7 +182,7 @@ vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, v | ||||
|                        includer)) [[unlikely]] { | ||||
|         LOG_INFO(Render_Vulkan, "Shader Info Log:\n{}\n{}", shader->getInfoLog(), | ||||
|                  shader->getInfoDebugLog()); | ||||
|         return VK_NULL_HANDLE; | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     // Even though there's only a single shader, we still need to link it to generate SPV | ||||
| @@ -191,7 +191,7 @@ vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, v | ||||
|     if (!program->link(messages)) { | ||||
|         LOG_INFO(Render_Vulkan, "Program Info Log:\n{}\n{}", program->getInfoLog(), | ||||
|                  program->getInfoDebugLog()); | ||||
|         return VK_NULL_HANDLE; | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     glslang::TIntermediate* intermediate = program->getIntermediate(lang); | ||||
| @@ -227,7 +227,7 @@ vk::ShaderModule CompileSPV(std::span<const u32> code, vk::Device device) { | ||||
|         UNREACHABLE_MSG("{}", err.what()); | ||||
|     } | ||||
|  | ||||
|     return VK_NULL_HANDLE; | ||||
|     return {}; | ||||
| } | ||||
|  | ||||
| } // namespace Vulkan | ||||
|   | ||||
							
								
								
									
										209
									
								
								src/video_core/renderer_vulkan/vk_stream_buffer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								src/video_core/renderer_vulkan/vk_stream_buffer.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,209 @@ | ||||
| // Copyright 2019 yuzu Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <limits> | ||||
| #include "common/alignment.h" | ||||
| #include "common/assert.h" | ||||
| #include "video_core/renderer_vulkan/vk_instance.h" | ||||
| #include "video_core/renderer_vulkan/vk_scheduler.h" | ||||
| #include "video_core/renderer_vulkan/vk_stream_buffer.h" | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| std::string_view BufferTypeName(BufferType type) { | ||||
|     switch (type) { | ||||
|     case BufferType::Upload: | ||||
|         return "Upload"; | ||||
|     case BufferType::Download: | ||||
|         return "Download"; | ||||
|     case BufferType::Stream: | ||||
|         return "Stream"; | ||||
|     default: | ||||
|         return "Invalid"; | ||||
|     } | ||||
| } | ||||
|  | ||||
| vk::MemoryPropertyFlags MakePropertyFlags(BufferType type) { | ||||
|     switch (type) { | ||||
|     case BufferType::Upload: | ||||
|         return vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent; | ||||
|     case BufferType::Download: | ||||
|         return vk::MemoryPropertyFlagBits::eHostVisible | | ||||
|                vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostCached; | ||||
|     case BufferType::Stream: | ||||
|         return vk::MemoryPropertyFlagBits::eDeviceLocal | vk::MemoryPropertyFlagBits::eHostVisible | | ||||
|                vk::MemoryPropertyFlagBits::eHostCoherent; | ||||
|     default: | ||||
|         UNREACHABLE_MSG("Unknown buffer type {}", type); | ||||
|         return vk::MemoryPropertyFlagBits::eHostVisible; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Find a memory type with the passed requirements | ||||
| std::optional<u32> FindMemoryType(const vk::PhysicalDeviceMemoryProperties& properties, | ||||
|                                   vk::MemoryPropertyFlags wanted) { | ||||
|     for (u32 i = 0; i < properties.memoryTypeCount; ++i) { | ||||
|         const auto flags = properties.memoryTypes[i].propertyFlags; | ||||
|         if ((flags & wanted) == wanted) { | ||||
|             return i; | ||||
|         } | ||||
|     } | ||||
|     return std::nullopt; | ||||
| } | ||||
|  | ||||
| /// Get the preferred host visible memory type. | ||||
| u32 GetMemoryType(const vk::PhysicalDeviceMemoryProperties& properties, BufferType type) { | ||||
|     vk::MemoryPropertyFlags flags = MakePropertyFlags(type); | ||||
|     std::optional preferred_type = FindMemoryType(properties, flags); | ||||
|  | ||||
|     constexpr std::array remove_flags = { | ||||
|         vk::MemoryPropertyFlagBits::eHostCached, | ||||
|         vk::MemoryPropertyFlagBits::eHostCoherent, | ||||
|     }; | ||||
|  | ||||
|     for (u32 i = 0; i < remove_flags.size() && !preferred_type; i++) { | ||||
|         flags &= ~remove_flags[i]; | ||||
|         preferred_type = FindMemoryType(properties, flags); | ||||
|     } | ||||
|     ASSERT_MSG(preferred_type, "No suitable memory type found"); | ||||
|     return preferred_type.value(); | ||||
| } | ||||
|  | ||||
| constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000; | ||||
| constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000; | ||||
|  | ||||
| } // Anonymous namespace | ||||
|  | ||||
| StreamBuffer::StreamBuffer(const Instance& instance_, Scheduler& scheduler_, | ||||
|                            vk::BufferUsageFlags usage_, u64 size, BufferType type_) | ||||
|     : instance{instance_}, scheduler{scheduler_}, device{instance.GetDevice()}, | ||||
|       stream_buffer_size{size}, usage{usage_}, type{type_} { | ||||
|     CreateBuffers(size); | ||||
|     ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE); | ||||
|     ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE); | ||||
| } | ||||
|  | ||||
| StreamBuffer::~StreamBuffer() { | ||||
|     device.unmapMemory(memory); | ||||
|     device.destroyBuffer(buffer); | ||||
|     device.freeMemory(memory); | ||||
| } | ||||
|  | ||||
| std::tuple<u8*, u64, bool> StreamBuffer::Map(u64 size, u64 alignment) { | ||||
|     if (!is_coherent && type == BufferType::Stream) { | ||||
|         size = Common::AlignUp(size, instance.NonCoherentAtomSize()); | ||||
|     } | ||||
|  | ||||
|     ASSERT(size <= stream_buffer_size); | ||||
|     mapped_size = size; | ||||
|  | ||||
|     if (alignment > 0) { | ||||
|         offset = Common::AlignUp(offset, alignment); | ||||
|     } | ||||
|  | ||||
|     bool invalidate{false}; | ||||
|     if (offset + size > stream_buffer_size) { | ||||
|         // The buffer would overflow, save the amount of used watches and reset the state. | ||||
|         invalidate = true; | ||||
|         invalidation_mark = current_watch_cursor; | ||||
|         current_watch_cursor = 0; | ||||
|         offset = 0; | ||||
|  | ||||
|         // Swap watches and reset waiting cursors. | ||||
|         std::swap(previous_watches, current_watches); | ||||
|         wait_cursor = 0; | ||||
|         wait_bound = 0; | ||||
|     } | ||||
|  | ||||
|     const u64 mapped_upper_bound = offset + size; | ||||
|     WaitPendingOperations(mapped_upper_bound); | ||||
|  | ||||
|     return std::make_tuple(mapped + offset, offset, invalidate); | ||||
| } | ||||
|  | ||||
| void StreamBuffer::Commit(u64 size) { | ||||
|     if (!is_coherent && type == BufferType::Stream) { | ||||
|         size = Common::AlignUp(size, instance.NonCoherentAtomSize()); | ||||
|     } | ||||
|  | ||||
|     ASSERT_MSG(size <= mapped_size, "Reserved size {} is too small compared to {}", mapped_size, | ||||
|                size); | ||||
|  | ||||
|     const vk::MappedMemoryRange range = { | ||||
|         .memory = memory, | ||||
|         .offset = offset, | ||||
|         .size = size, | ||||
|     }; | ||||
|  | ||||
|     if (!is_coherent && type == BufferType::Download) { | ||||
|         device.invalidateMappedMemoryRanges(range); | ||||
|     } else if (!is_coherent) { | ||||
|         device.flushMappedMemoryRanges(range); | ||||
|     } | ||||
|  | ||||
|     offset += size; | ||||
|  | ||||
|     if (current_watch_cursor + 1 >= current_watches.size()) { | ||||
|         // Ensure that there are enough watches. | ||||
|         ReserveWatches(current_watches, WATCHES_RESERVE_CHUNK); | ||||
|     } | ||||
|     auto& watch = current_watches[current_watch_cursor++]; | ||||
|     watch.upper_bound = offset; | ||||
|     watch.tick = scheduler.CurrentTick(); | ||||
| } | ||||
|  | ||||
| void StreamBuffer::CreateBuffers(u64 prefered_size) { | ||||
|     const vk::Device device = instance.GetDevice(); | ||||
|     const auto memory_properties = instance.GetPhysicalDevice().getMemoryProperties(); | ||||
|     const u32 preferred_type = GetMemoryType(memory_properties, type); | ||||
|     const vk::MemoryType mem_type = memory_properties.memoryTypes[preferred_type]; | ||||
|     const u32 preferred_heap = mem_type.heapIndex; | ||||
|     is_coherent = | ||||
|         static_cast<bool>(mem_type.propertyFlags & vk::MemoryPropertyFlagBits::eHostCoherent); | ||||
|  | ||||
|     // Substract from the preferred heap size some bytes to avoid getting out of memory. | ||||
|     const VkDeviceSize heap_size = memory_properties.memoryHeaps[preferred_heap].size; | ||||
|     // As per DXVK's example, using `heap_size / 2` | ||||
|     const VkDeviceSize allocable_size = heap_size / 2; | ||||
|     buffer = device.createBuffer({ | ||||
|         .size = std::min(prefered_size, allocable_size), | ||||
|         .usage = usage, | ||||
|     }); | ||||
|  | ||||
|     const auto requirements = device.getBufferMemoryRequirements(buffer); | ||||
|     stream_buffer_size = static_cast<u64>(requirements.size); | ||||
|  | ||||
|     LOG_INFO(Render_Vulkan, "Creating {} buffer with size {} KB with flags {}", | ||||
|              BufferTypeName(type), stream_buffer_size / 1024, | ||||
|              vk::to_string(mem_type.propertyFlags)); | ||||
|  | ||||
|     memory = device.allocateMemory({ | ||||
|         .allocationSize = requirements.size, | ||||
|         .memoryTypeIndex = preferred_type, | ||||
|     }); | ||||
|  | ||||
|     device.bindBufferMemory(buffer, memory, 0); | ||||
|     mapped = reinterpret_cast<u8*>(device.mapMemory(memory, 0, VK_WHOLE_SIZE)); | ||||
| } | ||||
|  | ||||
| void StreamBuffer::ReserveWatches(std::vector<Watch>& watches, std::size_t grow_size) { | ||||
|     watches.resize(watches.size() + grow_size); | ||||
| } | ||||
|  | ||||
| void StreamBuffer::WaitPendingOperations(u64 requested_upper_bound) { | ||||
|     if (!invalidation_mark) { | ||||
|         return; | ||||
|     } | ||||
|     while (requested_upper_bound > wait_bound && wait_cursor < *invalidation_mark) { | ||||
|         auto& watch = previous_watches[wait_cursor]; | ||||
|         wait_bound = watch.upper_bound; | ||||
|         scheduler.Wait(watch.tick); | ||||
|         ++wait_cursor; | ||||
|     } | ||||
| } | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										86
									
								
								src/video_core/renderer_vulkan/vk_stream_buffer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/video_core/renderer_vulkan/vk_stream_buffer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| // Copyright 2019 yuzu Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <optional> | ||||
| #include <span> | ||||
| #include <tuple> | ||||
| #include <vector> | ||||
| #include "video_core/renderer_vulkan/vk_common.h" | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| enum class BufferType : u32 { | ||||
|     Upload = 0, | ||||
|     Download = 1, | ||||
|     Stream = 2, | ||||
| }; | ||||
|  | ||||
| class Instance; | ||||
| class Scheduler; | ||||
|  | ||||
| class StreamBuffer final { | ||||
|     static constexpr std::size_t MAX_BUFFER_VIEWS = 3; | ||||
|  | ||||
| public: | ||||
|     explicit StreamBuffer(const Instance& instance, Scheduler& scheduler, | ||||
|                           vk::BufferUsageFlags usage, u64 size, | ||||
|                           BufferType type = BufferType::Stream); | ||||
|     ~StreamBuffer(); | ||||
|  | ||||
|     /** | ||||
|      * Reserves a region of memory from the stream buffer. | ||||
|      * @param size Size to reserve. | ||||
|      * @returns A pair of a raw memory pointer (with offset added), and the buffer offset | ||||
|      */ | ||||
|     std::tuple<u8*, u64, bool> Map(u64 size, u64 alignment); | ||||
|  | ||||
|     /// Ensures that "size" bytes of memory are available to the GPU, potentially recording a copy. | ||||
|     void Commit(u64 size); | ||||
|  | ||||
|     vk::Buffer Handle() const noexcept { | ||||
|         return buffer; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     struct Watch { | ||||
|         u64 tick{}; | ||||
|         u64 upper_bound{}; | ||||
|     }; | ||||
|  | ||||
|     /// Creates Vulkan buffer handles committing the required the required memory. | ||||
|     void CreateBuffers(u64 prefered_size); | ||||
|  | ||||
|     /// Increases the amount of watches available. | ||||
|     void ReserveWatches(std::vector<Watch>& watches, std::size_t grow_size); | ||||
|  | ||||
|     void WaitPendingOperations(u64 requested_upper_bound); | ||||
|  | ||||
| private: | ||||
|     const Instance& instance; ///< Vulkan instance. | ||||
|     Scheduler& scheduler;     ///< Command scheduler. | ||||
|  | ||||
|     vk::Device device; | ||||
|     vk::Buffer buffer;        ///< Mapped buffer. | ||||
|     vk::DeviceMemory memory;  ///< Memory allocation. | ||||
|     u8* mapped{};             ///< Pointer to the mapped memory | ||||
|     u64 stream_buffer_size{}; ///< Stream buffer size. | ||||
|     vk::BufferUsageFlags usage{}; | ||||
|     BufferType type; | ||||
|  | ||||
|     u64 offset{};       ///< Buffer iterator. | ||||
|     u64 mapped_size{};  ///< Size reserved for the current copy. | ||||
|     bool is_coherent{}; ///< True if the buffer is coherent | ||||
|  | ||||
|     std::vector<Watch> current_watches;           ///< Watches recorded in the current iteration. | ||||
|     std::size_t current_watch_cursor{};           ///< Count of watches, reset on invalidation. | ||||
|     std::optional<std::size_t> invalidation_mark; ///< Number of watches used in the previous cycle. | ||||
|  | ||||
|     std::vector<Watch> previous_watches; ///< Watches used in the previous iteration. | ||||
|     std::size_t wait_cursor{};           ///< Last watch being waited for completion. | ||||
|     u64 wait_bound{};                    ///< Highest offset being watched for completion. | ||||
| }; | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										259
									
								
								src/video_core/renderer_vulkan/vk_swapchain.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										259
									
								
								src/video_core/renderer_vulkan/vk_swapchain.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,259 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <limits> | ||||
| #include "common/logging/log.h" | ||||
| #include "common/microprofile.h" | ||||
| #include "common/settings.h" | ||||
| #include "video_core/renderer_vulkan/vk_instance.h" | ||||
| #include "video_core/renderer_vulkan/vk_swapchain.h" | ||||
|  | ||||
| MICROPROFILE_DEFINE(Vulkan_Acquire, "Vulkan", "Swapchain Acquire", MP_RGB(185, 66, 245)); | ||||
| MICROPROFILE_DEFINE(Vulkan_Present, "Vulkan", "Swapchain Present", MP_RGB(66, 185, 245)); | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| Swapchain::Swapchain(const Instance& instance_, u32 width, u32 height, vk::SurfaceKHR surface_) | ||||
|     : instance{instance_}, surface{surface_} { | ||||
|     FindPresentFormat(); | ||||
|     SetPresentMode(); | ||||
|     Create(width, height, surface); | ||||
| } | ||||
|  | ||||
| Swapchain::~Swapchain() { | ||||
|     Destroy(); | ||||
|     instance.GetInstance().destroySurfaceKHR(surface); | ||||
| } | ||||
|  | ||||
| void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_) { | ||||
|     width = width_; | ||||
|     height = height_; | ||||
|     surface = surface_; | ||||
|     needs_recreation = false; | ||||
|  | ||||
|     Destroy(); | ||||
|  | ||||
|     SetPresentMode(); | ||||
|     SetSurfaceProperties(); | ||||
|  | ||||
|     const std::array queue_family_indices = { | ||||
|         instance.GetGraphicsQueueFamilyIndex(), | ||||
|         instance.GetPresentQueueFamilyIndex(), | ||||
|     }; | ||||
|  | ||||
|     const bool exclusive = queue_family_indices[0] == queue_family_indices[1]; | ||||
|     const u32 queue_family_indices_count = exclusive ? 1u : 2u; | ||||
|     const vk::SharingMode sharing_mode = | ||||
|         exclusive ? vk::SharingMode::eExclusive : vk::SharingMode::eConcurrent; | ||||
|     const vk::SwapchainCreateInfoKHR swapchain_info = { | ||||
|         .surface = surface, | ||||
|         .minImageCount = image_count, | ||||
|         .imageFormat = surface_format.format, | ||||
|         .imageColorSpace = surface_format.colorSpace, | ||||
|         .imageExtent = extent, | ||||
|         .imageArrayLayers = 1, | ||||
|         .imageUsage = vk::ImageUsageFlagBits::eColorAttachment | | ||||
|                       vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst, | ||||
|         .imageSharingMode = sharing_mode, | ||||
|         .queueFamilyIndexCount = queue_family_indices_count, | ||||
|         .pQueueFamilyIndices = queue_family_indices.data(), | ||||
|         .preTransform = transform, | ||||
|         .compositeAlpha = composite_alpha, | ||||
|         .presentMode = present_mode, | ||||
|         .clipped = true, | ||||
|         .oldSwapchain = nullptr, | ||||
|     }; | ||||
|  | ||||
|     try { | ||||
|         swapchain = instance.GetDevice().createSwapchainKHR(swapchain_info); | ||||
|     } catch (vk::SystemError& err) { | ||||
|         LOG_CRITICAL(Render_Vulkan, "{}", err.what()); | ||||
|         UNREACHABLE(); | ||||
|     } | ||||
|  | ||||
|     SetupImages(); | ||||
|     RefreshSemaphores(); | ||||
| } | ||||
|  | ||||
| bool Swapchain::AcquireNextImage() { | ||||
|     MICROPROFILE_SCOPE(Vulkan_Acquire); | ||||
|     vk::Device device = instance.GetDevice(); | ||||
|     vk::Result result = | ||||
|         device.acquireNextImageKHR(swapchain, std::numeric_limits<u64>::max(), | ||||
|                                    image_acquired[frame_index], VK_NULL_HANDLE, &image_index); | ||||
|  | ||||
|     switch (result) { | ||||
|     case vk::Result::eSuccess: | ||||
|         break; | ||||
|     case vk::Result::eSuboptimalKHR: | ||||
|     case vk::Result::eErrorSurfaceLostKHR: | ||||
|     case vk::Result::eErrorOutOfDateKHR: | ||||
|         needs_recreation = true; | ||||
|         break; | ||||
|     default: | ||||
|         LOG_CRITICAL(Render_Vulkan, "Swapchain acquire returned unknown result {}", result); | ||||
|         UNREACHABLE(); | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     return !needs_recreation; | ||||
| } | ||||
|  | ||||
| void Swapchain::Present() { | ||||
|     if (needs_recreation) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     const vk::PresentInfoKHR present_info = { | ||||
|         .waitSemaphoreCount = 1, | ||||
|         .pWaitSemaphores = &present_ready[image_index], | ||||
|         .swapchainCount = 1, | ||||
|         .pSwapchains = &swapchain, | ||||
|         .pImageIndices = &image_index, | ||||
|     }; | ||||
|  | ||||
|     MICROPROFILE_SCOPE(Vulkan_Present); | ||||
|     try { | ||||
|         [[maybe_unused]] vk::Result result = instance.GetPresentQueue().presentKHR(present_info); | ||||
|     } catch (vk::OutOfDateKHRError&) { | ||||
|         needs_recreation = true; | ||||
|     } catch (const vk::SystemError& err) { | ||||
|         LOG_CRITICAL(Render_Vulkan, "Swapchain presentation failed {}", err.what()); | ||||
|         UNREACHABLE(); | ||||
|     } | ||||
|  | ||||
|     frame_index = (frame_index + 1) % image_count; | ||||
| } | ||||
|  | ||||
| void Swapchain::FindPresentFormat() { | ||||
|     const auto formats = instance.GetPhysicalDevice().getSurfaceFormatsKHR(surface); | ||||
|  | ||||
|     // If there is a single undefined surface format, the device doesn't care, so we'll just use | ||||
|     // RGBA. | ||||
|     if (formats[0].format == vk::Format::eUndefined) { | ||||
|         surface_format.format = vk::Format::eR8G8B8A8Unorm; | ||||
|         surface_format.colorSpace = vk::ColorSpaceKHR::eSrgbNonlinear; | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Try to find a suitable format. | ||||
|     for (const vk::SurfaceFormatKHR& sformat : formats) { | ||||
|         vk::Format format = sformat.format; | ||||
|         if (format != vk::Format::eR8G8B8A8Unorm && format != vk::Format::eB8G8R8A8Unorm) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         surface_format.format = format; | ||||
|         surface_format.colorSpace = sformat.colorSpace; | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     LOG_CRITICAL(Render_Vulkan, "Unable to find required swapchain format!"); | ||||
|     UNREACHABLE(); | ||||
| } | ||||
|  | ||||
| void Swapchain::SetPresentMode() { | ||||
|     const auto modes = instance.GetPhysicalDevice().getSurfacePresentModesKHR(surface); | ||||
|     const bool use_vsync = Settings::values.use_vsync_new.GetValue(); | ||||
|     const auto find_mode = [&modes](vk::PresentModeKHR requested) { | ||||
|         const auto it = | ||||
|             std::find_if(modes.begin(), modes.end(), | ||||
|                          [&requested](vk::PresentModeKHR mode) { return mode == requested; }); | ||||
|  | ||||
|         return it != modes.end(); | ||||
|     }; | ||||
|  | ||||
|     present_mode = vk::PresentModeKHR::eFifo; | ||||
|     const bool has_immediate = find_mode(vk::PresentModeKHR::eImmediate); | ||||
|     const bool has_mailbox = find_mode(vk::PresentModeKHR::eMailbox); | ||||
|     if (!has_immediate && !has_mailbox) { | ||||
|         LOG_WARNING(Render_Vulkan, "Forcing Fifo present mode as no alternatives are available"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // If the user has disabled vsync use immediate mode for the least latency. | ||||
|     // This may have screen tearing. | ||||
|     if (!use_vsync) { | ||||
|         present_mode = | ||||
|             has_immediate ? vk::PresentModeKHR::eImmediate : vk::PresentModeKHR::eMailbox; | ||||
|         return; | ||||
|     } | ||||
|     // If vsync is enabled attempt to use mailbox mode in case the user wants to speedup/slowdown | ||||
|     // the game. If mailbox is not available use immediate and warn about it. | ||||
|     if (use_vsync && Settings::values.frame_limit.GetValue() > 100) { | ||||
|         present_mode = has_mailbox ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eImmediate; | ||||
|         if (!has_mailbox) { | ||||
|             LOG_WARNING( | ||||
|                 Render_Vulkan, | ||||
|                 "Vsync enabled while frame limiting and no mailbox support, expect tearing"); | ||||
|         } | ||||
|         return; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Swapchain::SetSurfaceProperties() { | ||||
|     const vk::SurfaceCapabilitiesKHR capabilities = | ||||
|         instance.GetPhysicalDevice().getSurfaceCapabilitiesKHR(surface); | ||||
|  | ||||
|     extent = capabilities.currentExtent; | ||||
|     if (capabilities.currentExtent.width == std::numeric_limits<u32>::max()) { | ||||
|         extent.width = std::max(capabilities.minImageExtent.width, | ||||
|                                 std::min(capabilities.maxImageExtent.width, width)); | ||||
|         extent.height = std::max(capabilities.minImageExtent.height, | ||||
|                                  std::min(capabilities.maxImageExtent.height, height)); | ||||
|     } | ||||
|  | ||||
|     // Select number of images in swap chain, we prefer one buffer in the background to work on | ||||
|     image_count = capabilities.minImageCount + 1; | ||||
|     if (capabilities.maxImageCount > 0) { | ||||
|         image_count = std::min(image_count, capabilities.maxImageCount); | ||||
|     } | ||||
|  | ||||
|     // Prefer identity transform if possible | ||||
|     transform = vk::SurfaceTransformFlagBitsKHR::eIdentity; | ||||
|     if (!(capabilities.supportedTransforms & transform)) { | ||||
|         transform = capabilities.currentTransform; | ||||
|     } | ||||
|  | ||||
|     // Opaque is not supported everywhere. | ||||
|     composite_alpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; | ||||
|     if (!(capabilities.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::eOpaque)) { | ||||
|         composite_alpha = vk::CompositeAlphaFlagBitsKHR::eInherit; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Swapchain::Destroy() { | ||||
|     vk::Device device = instance.GetDevice(); | ||||
|     if (swapchain) { | ||||
|         device.destroySwapchainKHR(swapchain); | ||||
|     } | ||||
|     for (u32 i = 0; i < image_count; i++) { | ||||
|         device.destroySemaphore(image_acquired[i]); | ||||
|         device.destroySemaphore(present_ready[i]); | ||||
|     } | ||||
|     image_acquired.clear(); | ||||
|     present_ready.clear(); | ||||
| } | ||||
|  | ||||
| void Swapchain::RefreshSemaphores() { | ||||
|     const vk::Device device = instance.GetDevice(); | ||||
|     image_acquired.resize(image_count); | ||||
|     present_ready.resize(image_count); | ||||
|  | ||||
|     for (vk::Semaphore& semaphore : image_acquired) { | ||||
|         semaphore = device.createSemaphore({}); | ||||
|     } | ||||
|     for (vk::Semaphore& semaphore : present_ready) { | ||||
|         semaphore = device.createSemaphore({}); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Swapchain::SetupImages() { | ||||
|     vk::Device device = instance.GetDevice(); | ||||
|     images = device.getSwapchainImagesKHR(swapchain); | ||||
|     image_count = static_cast<u32>(images.size()); | ||||
| } | ||||
|  | ||||
| } // namespace Vulkan | ||||
							
								
								
									
										110
									
								
								src/video_core/renderer_vulkan/vk_swapchain.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								src/video_core/renderer_vulkan/vk_swapchain.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| // Copyright 2023 Citra Emulator Project | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <mutex> | ||||
| #include <vector> | ||||
| #include "common/common_types.h" | ||||
| #include "video_core/renderer_vulkan/vk_common.h" | ||||
|  | ||||
| namespace Vulkan { | ||||
|  | ||||
| class Instance; | ||||
| class Scheduler; | ||||
|  | ||||
| class Swapchain { | ||||
| public: | ||||
|     explicit Swapchain(const Instance& instance, u32 width, u32 height, vk::SurfaceKHR surface); | ||||
|     ~Swapchain(); | ||||
|  | ||||
|     /// Creates (or recreates) the swapchain with a given size. | ||||
|     void Create(u32 width, u32 height, vk::SurfaceKHR surface); | ||||
|  | ||||
|     /// Acquires the next image in the swapchain. | ||||
|     bool AcquireNextImage(); | ||||
|  | ||||
|     /// Presents the current image and move to the next one | ||||
|     void Present(); | ||||
|  | ||||
|     vk::SurfaceKHR GetSurface() const { | ||||
|         return surface; | ||||
|     } | ||||
|  | ||||
|     vk::Image Image() const { | ||||
|         return images[image_index]; | ||||
|     } | ||||
|  | ||||
|     vk::SurfaceFormatKHR GetSurfaceFormat() const { | ||||
|         return surface_format; | ||||
|     } | ||||
|  | ||||
|     vk::SwapchainKHR GetHandle() const { | ||||
|         return swapchain; | ||||
|     } | ||||
|  | ||||
|     u32 GetWidth() const { | ||||
|         return width; | ||||
|     } | ||||
|  | ||||
|     u32 GetHeight() const { | ||||
|         return height; | ||||
|     } | ||||
|  | ||||
|     u32 GetImageCount() const { | ||||
|         return image_count; | ||||
|     } | ||||
|  | ||||
|     vk::Extent2D GetExtent() const { | ||||
|         return extent; | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] vk::Semaphore GetImageAcquiredSemaphore() const { | ||||
|         return image_acquired[frame_index]; | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] vk::Semaphore GetPresentReadySemaphore() const { | ||||
|         return present_ready[image_index]; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     /// Selects the best available swapchain image format | ||||
|     void FindPresentFormat(); | ||||
|  | ||||
|     /// Sets the best available present mode | ||||
|     void SetPresentMode(); | ||||
|  | ||||
|     /// Sets the surface properties according to device capabilities | ||||
|     void SetSurfaceProperties(); | ||||
|  | ||||
|     /// Destroys current swapchain resources | ||||
|     void Destroy(); | ||||
|  | ||||
|     /// Performs creation of image views and framebuffers from the swapchain images | ||||
|     void SetupImages(); | ||||
|  | ||||
|     /// Creates the image acquired and present ready semaphores | ||||
|     void RefreshSemaphores(); | ||||
|  | ||||
| private: | ||||
|     const Instance& instance; | ||||
|     vk::SwapchainKHR swapchain{}; | ||||
|     vk::SurfaceKHR surface{}; | ||||
|     vk::SurfaceFormatKHR surface_format; | ||||
|     vk::PresentModeKHR present_mode; | ||||
|     vk::Extent2D extent; | ||||
|     vk::SurfaceTransformFlagBitsKHR transform; | ||||
|     vk::CompositeAlphaFlagBitsKHR composite_alpha; | ||||
|     std::vector<vk::Image> images; | ||||
|     std::vector<vk::Semaphore> image_acquired; | ||||
|     std::vector<vk::Semaphore> present_ready; | ||||
|     u32 width = 0; | ||||
|     u32 height = 0; | ||||
|     u32 image_count = 0; | ||||
|     u32 image_index = 0; | ||||
|     u32 frame_index = 0; | ||||
|     bool needs_recreation = true; | ||||
| }; | ||||
|  | ||||
| } // namespace Vulkan | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user