diff --git a/app/build.gradle b/app/build.gradle index ddef139..7d484fb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,6 +8,18 @@ def keystoreProperties = new Properties() if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } +def signingKeys = ['storeFile', 'storePassword', 'keyAlias', 'keyPassword'] +def missingSigningKeys = signingKeys.findAll { !keystoreProperties.getProperty(it)?.trim() } +def hasReleaseSigning = keystorePropertiesFile.exists() && missingSigningKeys.isEmpty() +if (hasReleaseSigning) { + def configuredStoreFile = file(keystoreProperties.getProperty('storeFile')) + if (!configuredStoreFile.exists()) { + logger.lifecycle("[app] Release keystore file not found at ${configuredStoreFile}. Falling back to debug signing for release.") + hasReleaseSigning = false + } +} else { + logger.lifecycle("[app] Release signing not fully configured (${missingSigningKeys}). Falling back to debug signing for release.") +} android { namespace = 'com.izzy2lost.psx2' @@ -16,7 +28,7 @@ android { signingConfigs { release { - if (keystorePropertiesFile.exists()) { + if (hasReleaseSigning) { storeFile file(keystoreProperties['storeFile']) storePassword keystoreProperties['storePassword'] keyAlias keystoreProperties['keyAlias'] @@ -29,8 +41,8 @@ android { applicationId = "com.izzy2lost.psx2" minSdk = 26 targetSdk = 36 - versionCode 21 - versionName "1.1.9" + versionCode 23 + versionName "1.2.1" // APK base.archivesName = "PSX2_${versionCode}_${new Date().format('yyyyMMddHHmm')}" @@ -57,7 +69,7 @@ android { minifyEnabled = false shrinkResources = false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - signingConfig signingConfigs.release + signingConfig hasReleaseSigning ? signingConfigs.release : signingConfigs.debug ndk { debugSymbolLevel 'SYMBOL_TABLE' } diff --git a/app/src/main/assets/resources/shaders/opengl/cas.glsl b/app/src/main/assets/resources/shaders/opengl/cas.glsl index baa1ecd..df05e86 100644 --- a/app/src/main/assets/resources/shaders/opengl/cas.glsl +++ b/app/src/main/assets/resources/shaders/opengl/cas.glsl @@ -20,7 +20,8 @@ uniform uvec4 const0; uniform uvec4 const1; uniform ivec2 srcOffset; -layout(binding=0) uniform sampler2D imgSrc; +// Sampler binding defaults to 0 if not explicitly set; texture unit 0 is used in code. +uniform sampler2D imgSrc; layout(binding=0, rgba8) uniform writeonly image2D imgDst; #define A_GPU 1 diff --git a/app/src/main/assets/resources/shaders/opengl/tfx_fs.glsl b/app/src/main/assets/resources/shaders/opengl/tfx_fs.glsl index 053aa23..7765ff8 100644 --- a/app/src/main/assets/resources/shaders/opengl/tfx_fs.glsl +++ b/app/src/main/assets/resources/shaders/opengl/tfx_fs.glsl @@ -79,7 +79,7 @@ in SHADER // Basically the only scenario where this'll happen is RGBA masked and DATE is active. #undef PS_NO_COLOR #define PS_NO_COLOR 0 - #if defined(GL_EXT_shader_framebuffer_fetch) + #if defined(GLAD_GL_EXT_shader_pixel_local_storage) #undef TARGET_0_QUALIFIER #define TARGET_0_QUALIFIER inout #define LAST_FRAG_COLOR SV_Target0 diff --git a/app/src/main/cpp/pcsx2/Config.h b/app/src/main/cpp/pcsx2/Config.h index 730c81c..240da28 100644 --- a/app/src/main/cpp/pcsx2/Config.h +++ b/app/src/main/cpp/pcsx2/Config.h @@ -321,7 +321,7 @@ enum class BiFiltering : u8 enum class TriFiltering : s8 { - Automatic = -1, + Automatic, Off, PS2, Forced, @@ -847,6 +847,7 @@ struct Pcsx2Config int AudioCaptureBitrate = DEFAULT_AUDIO_CAPTURE_BITRATE; std::string Adapter; + std::string CustomDriverPath; std::string HWDumpDirectory; std::string SWDumpDirectory; diff --git a/app/src/main/cpp/pcsx2/GS/GS.cpp b/app/src/main/cpp/pcsx2/GS/GS.cpp index 9f3ced4..1727279 100644 --- a/app/src/main/cpp/pcsx2/GS/GS.cpp +++ b/app/src/main/cpp/pcsx2/GS/GS.cpp @@ -34,6 +34,10 @@ #include "GS/Renderers/Vulkan/GSDeviceVK.h" #endif +#ifdef __ANDROID__ +#include "AndroidDeviceDetection.h" +#endif + #ifdef _WIN32 #include "GS/Renderers/DX11/GSDevice11.h" @@ -342,20 +346,24 @@ bool GSopen(const Pcsx2Config::GSOptions& config, GSRendererType renderer, u8* b { GSConfig = config; - // If the selected renderer is Auto (often from a per-game settings layer), - // prefer the base/global renderer when it is explicitly set, otherwise - // fall back to the hardware/platform preferred renderer. if (renderer == GSRendererType::Auto) { const int base_renderer_val = Host::GetBaseIntSettingValue("EmuCore/GS", "Renderer", static_cast(GSRendererType::Auto)); const GSRendererType base_renderer = static_cast(base_renderer_val); - if (base_renderer != GSRendererType::Auto) - renderer = base_renderer; - else - renderer = GSUtil::GetPreferredRenderer(); + renderer = (base_renderer != GSRendererType::Auto) ? base_renderer : GSUtil::GetPreferredRenderer(); } +#if defined(__ANDROID__) && defined(ENABLE_OPENGL) + // Don't attempt Vulkan first on Mali; it is known unstable on many devices. + if (renderer == GSRendererType::VK && + AndroidDeviceDetection::DetectGPUVendor() == AndroidDeviceDetection::GPUVendor::ARM) + { + Console.Warning("Mali detected with Vulkan selected, forcing OpenGL for startup stability."); + renderer = GSRendererType::OGL; + } +#endif + bool res = OpenGSDevice(renderer, true, false, vsync_mode, allow_present_throttle); if (res) { diff --git a/app/src/main/cpp/pcsx2/GS/GSUtil.cpp b/app/src/main/cpp/pcsx2/GS/GSUtil.cpp index b641276..25e81d7 100644 --- a/app/src/main/cpp/pcsx2/GS/GSUtil.cpp +++ b/app/src/main/cpp/pcsx2/GS/GSUtil.cpp @@ -5,8 +5,8 @@ #include "GS/GSExtra.h" #include "GS/GSUtil.h" #include "MultiISA.h" -#include "common/StringUtil.h" #include "common/Console.h" +#include "common/StringUtil.h" #include @@ -219,21 +219,17 @@ GSRendererType GSUtil::GetPreferredRenderer() // Use D3D device info to select renderer. preferred_renderer = D3D::GetPreferredRenderer(); #elif defined(__ANDROID__) - // Android: Detect GPU vendor and choose appropriate renderer + // Android: Detect GPU vendor and choose appropriate renderer. using namespace AndroidDeviceDetection; - GPUVendor vendor = DetectGPUVendor(); - + const GPUVendor vendor = DetectGPUVendor(); + if (vendor == GPUVendor::ARM) { - // Mediatek/Mali GPUs: Prefer OpenGL over Vulkan - // Vulkan drivers on Mali are often buggy, especially on Mediatek - // OpenGL works but may have 2D graphics issues that need workarounds - Console.Warning("Mediatek/Mali GPU detected: Using OpenGL renderer (Vulkan has known issues)"); + // Mali/Mediatek: prefer OpenGL for stability. + Console.Warning("Mali GPU detected: preferring OpenGL renderer."); #if defined(ENABLE_OPENGL) preferred_renderer = GSRendererType::OGL; #elif defined(ENABLE_VULKAN) - // Fallback to Vulkan if OpenGL not available (but warn user) - Console.Error("OpenGL not available, falling back to Vulkan (may have issues on Mali)"); preferred_renderer = GSRendererType::VK; #else preferred_renderer = GSRendererType::SW; @@ -241,8 +237,8 @@ GSRendererType GSUtil::GetPreferredRenderer() } else if (vendor == GPUVendor::Qualcomm) { - // Snapdragon/Adreno GPUs: Prefer Vulkan (good driver support) - Console.WriteLn("Qualcomm/Adreno GPU detected: Using Vulkan renderer"); + // Adreno: Vulkan is typically fine. + Console.WriteLn("Adreno GPU detected: preferring Vulkan renderer."); #if defined(ENABLE_VULKAN) preferred_renderer = GSRendererType::VK; #elif defined(ENABLE_OPENGL) @@ -253,22 +249,14 @@ GSRendererType GSUtil::GetPreferredRenderer() } else { - // Unknown vendor: Try Vulkan first, then OpenGL - Console.WriteLn("Unknown GPU vendor: Trying Vulkan renderer"); -#if defined(ENABLE_VULKAN) - if (GSDeviceVK::IsSuitableDefaultRenderer()) - preferred_renderer = GSRendererType::VK; -#endif - if (preferred_renderer == GSRendererType::Auto) - { + // Unknown Android GPU: prefer OpenGL for startup stability. #if defined(ENABLE_OPENGL) - preferred_renderer = GSRendererType::OGL; + preferred_renderer = GSRendererType::OGL; #elif defined(ENABLE_VULKAN) - preferred_renderer = GSRendererType::VK; + preferred_renderer = GSRendererType::VK; #else - preferred_renderer = GSRendererType::SW; + preferred_renderer = GSRendererType::SW; #endif - } } #else // Linux: Prefer Vulkan if the driver isn't buggy. @@ -277,14 +265,14 @@ GSRendererType GSUtil::GetPreferredRenderer() preferred_renderer = GSRendererType::VK; #endif - // Otherwise, whatever is available. - if (preferred_renderer == GSRendererType::Auto) // If it's still auto, VK wasn't selected. + // Otherwise, whatever is available. + if (preferred_renderer == GSRendererType::Auto) // If it's still auto, VK wasn't selected. #if defined(ENABLE_OPENGL) - preferred_renderer = GSRendererType::OGL; + preferred_renderer = GSRendererType::OGL; #elif defined(ENABLE_VULKAN) - preferred_renderer = GSRendererType::VK; + preferred_renderer = GSRendererType::VK; #else - preferred_renderer = GSRendererType::SW; + preferred_renderer = GSRendererType::SW; #endif #endif } diff --git a/app/src/main/cpp/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/app/src/main/cpp/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index aca64eb..8aa542a 100644 --- a/app/src/main/cpp/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/app/src/main/cpp/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -5518,6 +5518,10 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo const bool alpha_eq_one = alpha_c0_eq_one || alpha_c2_eq_one; const bool alpha_high_one = alpha_c0_high_min_one || alpha_c2_high_one; const bool alpha_eq_less_one = alpha_c0_eq_less_max_one || alpha_c2_eq_less_one; + const bool alpha_mali_custom_set = alpha_eq_less_one || alpha_c0_high_max_one; + const bool alpha_mali_custom_set_c0 = alpha_c0_eq_zero || alpha_c0_eq_one || alpha_c0_high_min_one || alpha_c0_high_max_one || alpha_c0_eq_less_max_one; + const bool alpha_mali_custom_set_c1 = alpha_c1_high_min_one || alpha_c1_high_max_one || alpha_c1_eq_less_max_one || alpha_c1_high_no_rta_correct; + const bool alpha_mali_custom_set_c2 = alpha_c2_eq_zero || alpha_c2_eq_one || alpha_c2_eq_less_one || alpha_c2_high_one; // Optimize blending equations, must be done before index calculation if ((m_conf.ps.blend_a == m_conf.ps.blend_b) || ((m_conf.ps.blend_b == m_conf.ps.blend_d) && alpha_eq_one)) @@ -5679,7 +5683,7 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo // Enable sw blending for barriers. sw_blending |= blend_requires_barrier; // Enable sw blending for free blending. - sw_blending |= free_blend; + sw_blending |= free_blend | alpha_mali_custom_set; // Do not run BLEND MIX if sw blending is already present, it's less accurate. blend_mix &= !sw_blending; sw_blending |= blend_mix; @@ -5720,7 +5724,7 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo // Enable sw blending for reading fb. sw_blending |= prefer_sw_blend; // Enable sw blending for free blending. - sw_blending |= free_blend; + sw_blending |= free_blend | alpha_mali_custom_set; // Do not run BLEND MIX if sw blending is already present, it's less accurate. blend_mix &= !sw_blending; sw_blending |= blend_mix; diff --git a/app/src/main/cpp/pcsx2/GS/Renderers/OpenGL/GLContext.cpp b/app/src/main/cpp/pcsx2/GS/Renderers/OpenGL/GLContext.cpp index 8c9bb99..ea2ec03 100644 --- a/app/src/main/cpp/pcsx2/GS/Renderers/OpenGL/GLContext.cpp +++ b/app/src/main/cpp/pcsx2/GS/Renderers/OpenGL/GLContext.cpp @@ -60,9 +60,10 @@ std::vector GLContext::EnumerateFullscreenModes() std::unique_ptr GLContext::Create(const WindowInfo& wi, const Version* versions_to_try, size_t num_versions_to_try) { - if (ShouldPreferESContext()) + const bool prefer_es = (wi.type == WindowInfo::Type::Android) || ShouldPreferESContext(); + if (prefer_es) { - // move ES versions to the front + // Move ES profiles to the front so EGL negotiates a GLES context first on Android/mobile. Version* new_versions_to_try = static_cast(alloca(sizeof(Version) * num_versions_to_try)); size_t count = 0; for (size_t i = 0; i < num_versions_to_try; i++) @@ -70,11 +71,13 @@ std::unique_ptr GLContext::Create(const WindowInfo& wi, const Version if (versions_to_try[i].profile == Profile::ES) new_versions_to_try[count++] = versions_to_try[i]; } + for (size_t i = 0; i < num_versions_to_try; i++) { if (versions_to_try[i].profile != Profile::ES) new_versions_to_try[count++] = versions_to_try[i]; } + versions_to_try = new_versions_to_try; } @@ -108,6 +111,11 @@ std::unique_ptr GLContext::Create(const WindowInfo& wi, const Version context_being_created = nullptr; + const char* gl_vendor = reinterpret_cast(glGetString(GL_VENDOR)); + const char* gl_renderer = reinterpret_cast(glGetString(GL_RENDERER)); + if (gl_vendor && gl_renderer) + DisableBrokenExtensions(gl_vendor, gl_renderer); + return context; } diff --git a/app/src/main/cpp/pcsx2/GS/Renderers/OpenGL/GLContextEGL.cpp b/app/src/main/cpp/pcsx2/GS/Renderers/OpenGL/GLContextEGL.cpp index 9902eb9..07845c7 100644 --- a/app/src/main/cpp/pcsx2/GS/Renderers/OpenGL/GLContextEGL.cpp +++ b/app/src/main/cpp/pcsx2/GS/Renderers/OpenGL/GLContextEGL.cpp @@ -4,7 +4,7 @@ #include "GS/Renderers/OpenGL/GLContextEGL.h" #include "common/Console.h" -#include +#include "GS/GS.h" #include #include @@ -68,7 +68,12 @@ bool GLContextEGL::Initialize(const Version* versions_to_try, size_t num_version bool GLContextEGL::SetDisplay() { + // On Android, there is no native display handle!รง +#ifdef __ANDROID__ + m_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); +#else m_display = eglGetDisplay(static_cast(m_wi.display_connection)); +#endif if (!m_display) { Console.Error("eglGetDisplay() failed: %d", eglGetError()); @@ -322,7 +327,7 @@ bool GLContextEGL::CreateContext(const Version& version, EGLContext share_contex config = configs.front(); } - int attribs[8]; + int attribs[16]; int nattribs = 0; if (version.profile != Profile::NoProfile) { @@ -331,6 +336,16 @@ bool GLContextEGL::CreateContext(const Version& version, EGLContext share_contex attribs[nattribs++] = EGL_CONTEXT_MINOR_VERSION; attribs[nattribs++] = version.minor_version; } + + { + const char* egl_ext = eglQueryString(m_display, EGL_EXTENSIONS); + const bool has_egl_no_error = (egl_ext && std::strstr(egl_ext, "EGL_KHR_create_context_no_error")); + if (has_egl_no_error && !GSConfig.UseDebugDevice) + { + attribs[nattribs++] = EGL_CONTEXT_OPENGL_NO_ERROR_KHR; + attribs[nattribs++] = EGL_TRUE; + } + } attribs[nattribs++] = EGL_NONE; attribs[nattribs++] = 0; diff --git a/app/src/main/cpp/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp b/app/src/main/cpp/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp index fbe3ec0..0ffe832 100644 --- a/app/src/main/cpp/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp +++ b/app/src/main/cpp/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp @@ -18,18 +18,9 @@ #include "IconsFontAwesome5.h" #include -#include #include #include -// Some texture barrier extension macros may not be generated by GLAD for GLES builds. -#ifndef GLAD_GL_EXT_texture_barrier -#define GLAD_GL_EXT_texture_barrier 0 -#endif -#ifndef GLAD_GL_OES_texture_barrier -#define GLAD_GL_OES_texture_barrier 0 -#endif - static constexpr u32 g_vs_cb_index = 1; static constexpr u32 g_ps_cb_index = 0; @@ -213,6 +204,13 @@ bool GSDeviceOGL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) Console.WriteLn("GL: Not using shader cache."); } + // On GLES drivers which support KHR_parallel_shader_compile, allow the driver to use more + // threads for shader compilation to reduce stutter. + if (m_is_gles && GLAD_GL_KHR_parallel_shader_compile) + { + glMaxShaderCompilerThreadsKHR(4); + } + // because of fbo bindings below... GLState::Clear(); @@ -494,8 +492,8 @@ bool GSDeviceOGL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) if (!CompileShadeBoostProgram() || !CompileFXAAProgram()) return false; - // Image load store and GLSL 420pack is core in GL4.2, no need to check. - m_features.cas_sharpening = ((GLAD_GL_VERSION_4_2 && GLAD_GL_ARB_compute_shader) || GLAD_GL_ES_VERSION_3_2) && CreateCASPrograms(); + // Enable CAS when compute is available (GL 4.2+ or GLES 3.1+). + m_features.cas_sharpening = ((GLAD_GL_VERSION_4_2 && GLAD_GL_ARB_compute_shader) || GLAD_GL_ES_VERSION_3_1) && CreateCASPrograms(); // **************************************************************** // rasterization configuration @@ -635,44 +633,39 @@ bool GSDeviceOGL::CheckFeatures(bool& buggy_pbo) //bool vendor_id_amd = false; bool vendor_id_nvidia = false; //bool vendor_id_intel = false; + bool vendor_id_mali = false; + bool vendor_id_adreno = false; const char* vendor = (const char*)glGetString(GL_VENDOR); const char* renderer = (const char*)glGetString(GL_RENDERER); - const std::string vendor_str = vendor ? vendor : ""; - const std::string renderer_str = renderer ? renderer : ""; - const auto to_lower = [](std::string s) { - for (char& c : s) - c = static_cast(std::tolower(static_cast(c))); - return s; - }; - const std::string vendor_lower = to_lower(vendor_str); - const std::string renderer_lower = to_lower(renderer_str); - // Detect Mali GPU - bool vendor_id_arm_mali = (vendor_lower.find("arm") != std::string::npos || - renderer_lower.find("mali") != std::string::npos); - - if (vendor_lower.find("advanced micro devices") != std::string::npos || - vendor_lower.find("ati technologies inc.") != std::string::npos || - vendor_lower.find("ati") != std::string::npos) + if (std::strstr(vendor, "Advanced Micro Devices") || std::strstr(vendor, "ATI Technologies Inc.") || + std::strstr(vendor, "ATI")) { Console.WriteLn(Color_StrongRed, "GL: AMD GPU detected."); //vendor_id_amd = true; } - else if (vendor_lower.find("nvidia corporation") != std::string::npos || vendor_lower.find("nvidia") != std::string::npos) + else if (std::strstr(vendor, "NVIDIA Corporation")) { Console.WriteLn(Color_StrongGreen, "GL: NVIDIA GPU detected."); vendor_id_nvidia = true; } - else if (vendor_lower.find("intel") != std::string::npos) + else if (std::strstr(vendor, "Intel")) { Console.WriteLn(Color_StrongBlue, "GL: Intel GPU detected."); //vendor_id_intel = true; } - else if (vendor_id_arm_mali) + else if (std::strstr(vendor, "ARM") || std::strstr(renderer, "Mali")) { - Console.WriteLn(Color_StrongYellow, "GL: ARM Mali GPU detected - applying workarounds"); + Console.WriteLn(Color_Yellow, "GL: ARM Mali GPU detected."); + vendor_id_mali = true; } + else if (std::strstr(vendor, "Qualcomm") || std::strstr(renderer, "Adreno")) + { + Console.WriteLn(Color_Cyan, "GL: Qualcomm Adreno GPU detected."); + vendor_id_adreno = true; + } + GLint major_gl = 0; GLint minor_gl = 0; glGetIntegerv(GL_MAJOR_VERSION, &major_gl); @@ -736,9 +729,7 @@ bool GSDeviceOGL::CheckFeatures(bool& buggy_pbo) if (!GLAD_GL_ARB_texture_barrier) { glTextureBarrier = ReplaceGL::TextureBarrier; - // OSD message disabled: GL_ARB_texture_barrier warning - // Host::AddOSDMessage( - // "GL_ARB_texture_barrier is not supported, blending will not be accurate.", Host::OSD_ERROR_DURATION); + // Suppressed: frequent warning on mobile drivers where fallback paths are already handled. } if (!GLAD_GL_ARB_direct_state_access) @@ -752,33 +743,14 @@ bool GSDeviceOGL::CheckFeatures(bool& buggy_pbo) if (!m_is_gles) { buggy_pbo = !GLAD_GL_VERSION_4_4 && !GLAD_GL_ARB_buffer_storage && !GLAD_GL_EXT_buffer_storage; } else { - buggy_pbo = !GLAD_GL_EXT_buffer_storage; + buggy_pbo = GLAD_GL_EXT_buffer_storage; } - - // Mali GPU workarounds - force PBO off and disable problematic features - if (vendor_id_arm_mali) - { - Console.WriteLn(Color_StrongYellow, "GL: Applying Mali GPU workarounds:"); - Console.WriteLn(" - Disabling framebuffer fetch"); - Console.WriteLn(" - Enabling texture barriers"); - Console.WriteLn(" - Disabling vertex shader expansion"); - Console.WriteLn(" - Disabling PBO for texture uploads"); - Console.WriteLn(" - Disabling PBO for texture downloads"); - Console.WriteLn(" - Disabling point expand"); - - buggy_pbo = true; - m_disable_download_pbo = true; - - Console.WriteLn("GL: Mali workarounds applied. Textures will use direct upload path."); - } - if (buggy_pbo) Console.Warning("GL: Not using PBOs for texture uploads because buffer_storage is unavailable."); // Give the user the option to disable PBO usage for downloads. // Most drivers seem to be faster with PBO. - if (!vendor_id_arm_mali) - m_disable_download_pbo = Host::GetBoolSettingValue("EmuCore/GS", "DisableGLDownloadPBO", false); + m_disable_download_pbo = Host::GetBoolSettingValue("EmuCore/GS", "DisableGLDownloadPBO", false); if (m_disable_download_pbo) Console.Warning("GL: Not using PBOs for texture downloads, this may reduce performance."); @@ -787,14 +759,6 @@ bool GSDeviceOGL::CheckFeatures(bool& buggy_pbo) m_features.primitive_id = true; m_features.framebuffer_fetch = GLAD_GL_EXT_shader_framebuffer_fetch; - - // Disable framebuffer fetch on Mali - causes 2D graphics issues - if (vendor_id_arm_mali && m_features.framebuffer_fetch) - { - Console.WriteLn("GL: Disabling framebuffer fetch on Mali GPU (causes rendering issues)"); - m_features.framebuffer_fetch = false; - } - if (m_features.framebuffer_fetch && GSConfig.DisableFramebufferFetch) { Host::AddOSDMessage( @@ -802,19 +766,15 @@ bool GSDeviceOGL::CheckFeatures(bool& buggy_pbo) m_features.framebuffer_fetch = false; } - const bool has_texture_barrier = - GLAD_GL_ARB_texture_barrier || GLAD_GL_EXT_texture_barrier || GLAD_GL_OES_texture_barrier; if (GSConfig.OverrideTextureBarriers == 0) m_features.texture_barrier = m_features.framebuffer_fetch; // Force Disabled else if (GSConfig.OverrideTextureBarriers == 1) m_features.texture_barrier = true; // Force Enabled else - m_features.texture_barrier = m_features.framebuffer_fetch || has_texture_barrier; + m_features.texture_barrier = m_features.framebuffer_fetch || GLAD_GL_ARB_texture_barrier; if (!m_features.texture_barrier) { - // OSD message disabled: GL_ARB_texture_barrier warning - // Host::AddOSDMessage( - // "GL_ARB_texture_barrier is not supported, blending will not be accurate.", Host::OSD_ERROR_DURATION); + // Suppressed: frequent warning on mobile drivers where fallback paths are already handled. } m_features.provoking_vertex_last = true; @@ -832,6 +792,36 @@ bool GSDeviceOGL::CheckFeatures(bool& buggy_pbo) if (buggy_vs_expand) Console.Warning("GL: Disabling vertex shader expand due to broken NVIDIA driver."); + // Mali GPU optimizations for tile-based rendering architecture + if (vendor_id_mali) + { + Console.WriteLn(Color_Yellow, "GL: Applying Mali-specific optimizations for tile-based rendering."); + // Enable early-Z and avoid unnecessary discard operations + m_features.prefer_new_textures = true; + m_features.framebuffer_fetch = GLAD_GL_EXT_shader_pixel_local_storage | GLAD_GL_ARM_shader_framebuffer_fetch | GLAD_GL_EXT_shader_framebuffer_fetch; + // Mali benefits from reduced texture barrier usage due to tile memory + if (GSConfig.OverrideTextureBarriers == -1) // If not explicitly set + { + m_features.texture_barrier = m_features.framebuffer_fetch; + Console.WriteLn("GL: Mali optimization - using framebuffer fetch over texture barriers when available."); + } + } + + // Adreno GPU optimizations for Qualcomm's architecture + if (vendor_id_adreno) + { + Console.WriteLn(Color_Cyan, "GL: Applying Adreno-specific optimizations for Qualcomm GPU."); + // Adreno benefits from reduced bandwidth and optimized memory patterns + m_features.prefer_new_textures = true; + // Adreno has efficient compression, enable when available + if (m_features.bptc_textures) + { + Console.WriteLn("GL: Adreno optimization - leveraging BPTC texture compression."); + } + // Reduce unnecessary state changes for Adreno's command processor + Console.WriteLn("GL: Adreno optimization - minimizing state changes for improved performance."); + } + if (GLAD_GL_ARB_shader_storage_buffer_object) { GLint max_vertex_ssbos = 0; @@ -1425,14 +1415,10 @@ std::string GSDeviceOGL::GenGlslHeader(const std::string_view entry, GLenum type else header += "#define HAS_FRAMEBUFFER_FETCH 0\n"; - if (GLAD_GL_ARB_clip_control) - { - header += "#define HAS_CLIP_CONTROL 1\n"; - } - else - { - header += "#define HAS_CLIP_CONTROL 0\n"; - } + if (GLAD_GL_ARB_clip_control) + header += "#define HAS_CLIP_CONTROL 1\n"; + else + header += "#define HAS_CLIP_CONTROL 0\n"; // Allow to puts several shader in 1 files switch (type) @@ -2140,36 +2126,53 @@ void GSDeviceOGL::ClearSamplerCache() bool GSDeviceOGL::CreateCASPrograms() { - std::optional cas_source = ReadShaderSource("shaders/opengl/cas.glsl"); - if (!cas_source.has_value() || !GetCASShaderSource(&cas_source.value())) - { - m_features.cas_sharpening = false; - return false; - } + std::optional cas_source = ReadShaderSource("shaders/opengl/cas.glsl"); + if (!cas_source.has_value() || !GetCASShaderSource(&cas_source.value())) + { + m_features.cas_sharpening = false; + return false; + } - const char* header = - "#version 420\n" - "#extension GL_ARB_compute_shader : require\n"; - const char* sharpen_params[2] = { - "#define CAS_SHARPEN_ONLY false\n", - "#define CAS_SHARPEN_ONLY true\n"}; + // Build an appropriate GLSL header for desktop GL vs GLES. + std::string header; + if (m_is_gles) + { + if (GLAD_GL_ES_VERSION_3_2) + header = "#version 320 es\n"; + else if (GLAD_GL_ES_VERSION_3_1) + header = "#version 310 es\n"; + else + { + m_features.cas_sharpening = false; + return false; + } + // No extension needed for compute on GLES 3.1/3.2. + } + else + { + header = "#version 420\n#extension GL_ARB_compute_shader : require\n"; + } - if (!m_shader_cache.GetComputeProgram(&m_cas.upscale_ps, fmt::format("{}{}{}", header, sharpen_params[0], cas_source.value())) || - !m_shader_cache.GetComputeProgram(&m_cas.sharpen_ps, fmt::format("{}{}{}", header, sharpen_params[1], cas_source.value()))) - { - m_features.cas_sharpening = false; - return false; - } + const char* sharpen_params[2] = { + "#define CAS_SHARPEN_ONLY false\n", + "#define CAS_SHARPEN_ONLY true\n"}; - const auto link_uniforms = [](GLProgram& prog) { - prog.RegisterUniform("const0"); - prog.RegisterUniform("const1"); - prog.RegisterUniform("srcOffset"); - }; - link_uniforms(m_cas.upscale_ps); - link_uniforms(m_cas.sharpen_ps); + if (!m_shader_cache.GetComputeProgram(&m_cas.upscale_ps, fmt::format("{}{}{}", header, sharpen_params[0], cas_source.value())) || + !m_shader_cache.GetComputeProgram(&m_cas.sharpen_ps, fmt::format("{}{}{}", header, sharpen_params[1], cas_source.value()))) + { + m_features.cas_sharpening = false; + return false; + } - return true; + const auto link_uniforms = [](GLProgram& prog) { + prog.RegisterUniform("const0"); + prog.RegisterUniform("const1"); + prog.RegisterUniform("srcOffset"); + }; + link_uniforms(m_cas.upscale_ps); + link_uniforms(m_cas.sharpen_ps); + + return true; } bool GSDeviceOGL::DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array& constants) diff --git a/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index a06f2f2..74e1a37 100644 --- a/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -83,7 +83,6 @@ static std::mutex s_instance_mutex; // Device extensions that are required for PCSX2. static constexpr const char* s_required_device_extensions[] = { - VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, VK_KHR_SHADER_DRAW_PARAMETERS_EXTENSION_NAME, }; @@ -114,17 +113,7 @@ VkInstance GSDeviceVK::CreateVulkanInstance(const WindowInfo& wi, OptionalExtens app_info.pEngineName = "PCSX2"; app_info.engineVersion = VK_MAKE_VERSION( BuildVersion::GitTagHi, BuildVersion::GitTagMid, BuildVersion::GitTagLo); - // Prefer a newer Vulkan API when available, but clamp to loader support. - // This unlocks newer features on capable drivers without breaking older ones. - uint32_t loader_api_version = VK_API_VERSION_1_1; - if (vkEnumerateInstanceVersion) - { - uint32_t ver = 0; - if (vkEnumerateInstanceVersion(&ver) == VK_SUCCESS && ver != 0) - loader_api_version = ver; - } - const uint32_t desired_api_version = VK_API_VERSION_1_3; // headers provide up to 1.4, 1.3 is widely supported - app_info.apiVersion = (loader_api_version < desired_api_version) ? loader_api_version : desired_api_version; + app_info.apiVersion = VK_API_VERSION_1_1; VkInstanceCreateInfo instance_create_info = {}; instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; @@ -434,6 +423,7 @@ bool GSDeviceVK::SelectDeviceExtensions(ExtensionList* extension_list, bool enab SupportsExtension(VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME, false); m_optional_extensions.vk_ext_line_rasterization = SupportsExtension(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME, require_line_rasterization); + m_optional_extensions.vk_khr_push_descriptor = SupportsExtension(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, false); m_optional_extensions.vk_khr_driver_properties = SupportsExtension(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME, false); // glslang generates debug info instructions before phi nodes at the beginning of blocks when non-semantic debug info @@ -764,19 +754,27 @@ bool GSDeviceVK::ProcessDeviceExtensions() Vulkan::AddPointerToChain(&properties2, &m_device_driver_properties); } - VkPhysicalDevicePushDescriptorPropertiesKHR push_descriptor_properties = { - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR}; - Vulkan::AddPointerToChain(&properties2, &push_descriptor_properties); - - // query - vkGetPhysicalDeviceProperties2(m_physical_device, &properties2); - - // confirm we actually support it - if (push_descriptor_properties.maxPushDescriptors < NUM_TFX_TEXTURES) + if (m_optional_extensions.vk_khr_push_descriptor) { - Console.Error("VK: maxPushDescriptors (%u) is below required (%u)", push_descriptor_properties.maxPushDescriptors, - NUM_TFX_TEXTURES); - return false; + VkPhysicalDevicePushDescriptorPropertiesKHR push_descriptor_properties = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR}; + Vulkan::AddPointerToChain(&properties2, &push_descriptor_properties); + + // query + vkGetPhysicalDeviceProperties2(m_physical_device, &properties2); + + // If the device advertises fewer push descriptors than required, disable push-descriptor + // usage and continue with a fallback. + if (push_descriptor_properties.maxPushDescriptors < NUM_TFX_TEXTURES) + { + Console.Warning("VK: push descriptors available but maxPushDescriptors (%u) < required (%u). Disabling push descriptors.", + push_descriptor_properties.maxPushDescriptors, NUM_TFX_TEXTURES); + m_optional_extensions.vk_khr_push_descriptor = false; + } + } + else + { + vkGetPhysicalDeviceProperties2(m_physical_device, &properties2); } if (!line_rasterization_feature.bresenhamLines) @@ -917,7 +915,9 @@ bool GSDeviceVK::CreateCommandBuffers() resources.needs_fence_wait = false; VkCommandPoolCreateInfo pool_info = { - VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, nullptr, 0, m_graphics_queue_family_index}; + VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, nullptr, + VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + m_graphics_queue_family_index}; res = vkCreateCommandPool(m_device, &pool_info, nullptr, &resources.command_pool); if (res != VK_SUCCESS) { @@ -961,13 +961,18 @@ bool GSDeviceVK::CreateCommandBuffers() bool GSDeviceVK::CreateGlobalDescriptorPool() { static constexpr const VkDescriptorPoolSize pool_sizes[] = { - {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 2}, - {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2}, + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, MAX_COMBINED_IMAGE_SAMPLER_DESCRIPTORS_PER_FRAME }, + { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, MAX_SAMPLED_IMAGE_DESCRIPTORS_PER_FRAME }, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, MAX_STORAGE_IMAGE_DESCRIPTORS_PER_FRAME }, + { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, MAX_INPUT_ATTACHMENT_IMAGE_DESCRIPTORS_PER_FRAME }, + { VK_DESCRIPTOR_TYPE_SAMPLER, MAX_COMBINED_IMAGE_SAMPLER_DESCRIPTORS_PER_FRAME }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, MAX_DESCRIPTOR_SETS_PER_FRAME }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, MAX_DESCRIPTOR_SETS_PER_FRAME } }; VkDescriptorPoolCreateInfo pool_create_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, nullptr, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, - 1024, // TODO: tweak this + MAX_DESCRIPTOR_SETS_PER_FRAME, // TODO: tweak this static_cast(std::size(pool_sizes)), pool_sizes}; VkResult res = vkCreateDescriptorPool(m_device, &pool_create_info, nullptr, &m_global_descriptor_pool); @@ -1726,8 +1731,9 @@ bool GSDeviceVK::InitSpinResources() for (SpinResources& resources : m_spin_resources) { u32 index = &resources - &m_spin_resources[0]; - VkCommandPoolCreateInfo pool_info = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; - pool_info.queueFamilyIndex = m_spin_queue_family_index; + VkCommandPoolCreateInfo pool_info = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; + pool_info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + pool_info.queueFamilyIndex = m_spin_queue_family_index; CHECKED_CREATE(vkCreateCommandPool, &pool_info, &resources.command_pool); Vulkan::SetObjectName(m_device, resources.command_pool, "Spin Command Pool %u", index); @@ -1988,7 +1994,7 @@ bool GSDeviceVK::AllocatePreinitializedGPUBuffer(u32 size, VkBuffer* gpu_buffer, } const VkBufferCreateInfo gpu_bci = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, nullptr, 0, size, - VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_SHARING_MODE_EXCLUSIVE}; + (gpu_usage | VK_BUFFER_USAGE_TRANSFER_DST_BIT), VK_SHARING_MODE_EXCLUSIVE}; const VmaAllocationCreateInfo gpu_aci = {0, VMA_MEMORY_USAGE_GPU_ONLY, 0, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT}; VmaAllocationInfo ai; res = vmaCreateBuffer(m_allocator, &gpu_bci, &gpu_aci, gpu_buffer, gpu_allocation, &ai); @@ -2001,7 +2007,11 @@ bool GSDeviceVK::AllocatePreinitializedGPUBuffer(u32 size, VkBuffer* gpu_buffer, const VkBufferCopy buf_copy = {0u, 0u, size}; fill_callback(cpu_ai.pMappedData); - vmaFlushAllocation(m_allocator, cpu_allocation, 0, size); + // Avoid redundant flush if staging memory is HOST_COHERENT + VkMemoryPropertyFlags mem_props = 0; + vmaGetAllocationMemoryProperties(m_allocator, cpu_allocation, &mem_props); + if ((mem_props & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0) + vmaFlushAllocation(m_allocator, cpu_allocation, 0, size); vkCmdCopyBuffer(GetCurrentInitCommandBuffer(), cpu_buffer, *gpu_buffer, 1, &buf_copy); DeferBufferDestruction(cpu_buffer, cpu_allocation); return true; @@ -2624,7 +2634,7 @@ bool GSDeviceVK::CreateDeviceAndSwapChain() bool GSDeviceVK::CheckFeatures() { const VkPhysicalDeviceLimits& limits = m_device_properties.limits; - //const u32 vendorID = m_device_properties.vendorID; + const u32 vendorID = m_device_properties.vendorID; //const bool isAMD = (vendorID == 0x1002 || vendorID == 0x1022); //const bool isNVIDIA = (vendorID == 0x10DE); @@ -2667,6 +2677,15 @@ bool GSDeviceVK::CheckFeatures() m_features.line_expand = (m_device_features.wideLines && limits.lineWidthRange[0] <= f_upscale && limits.lineWidthRange[1] >= f_upscale); + // Mobile GPUs (Adreno/Mali) often emulate wide lines/points expensively; prefer vertex expansion there. + if (vendorID == 0x5143u || vendorID == 0x13B5u) + { + if (m_features.point_expand || m_features.line_expand) + Console.WriteLn("VK: Forcing vertex-based expansion for points/lines on mobile GPU (vendor 0x%X).", vendorID); + m_features.point_expand = false; + m_features.line_expand = false; + } + DevCon.WriteLn("Optional features:%s%s%s%s%s", m_features.primitive_id ? " primitive_id" : "", m_features.texture_barrier ? " texture_barrier" : "", m_features.framebuffer_fetch ? " framebuffer_fetch" : "", m_features.provoking_vertex_last ? " provoking_vertex_last" : "", m_features.vs_expand ? " vs_expand" : ""); @@ -3582,6 +3601,9 @@ VkSampler GSDeviceVK::GetSampler(GSHWDrawConfig::SamplerSelector ss) return it->second; const bool aniso = (ss.aniso && GSConfig.MaxAnisotropy > 1 && m_device_features.samplerAnisotropy); + const float max_supported_aniso = m_device_features.samplerAnisotropy ? + std::max(1.0f, static_cast(m_device_properties.limits.maxSamplerAnisotropy)) : 1.0f; + const float aniso_value = aniso ? std::min(static_cast(GSConfig.MaxAnisotropy), max_supported_aniso) : 1.0f; // See https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkSamplerCreateInfo.html#_description // for the reasoning behind 0.25f here. @@ -3596,8 +3618,8 @@ VkSampler GSDeviceVK::GetSampler(GSHWDrawConfig::SamplerSelector ss) ss.tav ? VK_SAMPLER_ADDRESS_MODE_REPEAT : VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE), // v VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // w 0.0f, // lod bias - static_cast(aniso), // anisotropy enable - aniso ? static_cast(GSConfig.MaxAnisotropy) : 1.0f, // anisotropy + static_cast(aniso), // anisotropy enable + aniso_value, // anisotropy VK_FALSE, // compare enable VK_COMPARE_OP_ALWAYS, // compare op 0.0f, // min lod @@ -3773,7 +3795,8 @@ bool GSDeviceVK::CreatePipelineLayouts() // Convert Pipeline Layout ////////////////////////////////////////////////////////////////////////// - dslb.SetPushFlag(); + if (m_optional_extensions.vk_khr_push_descriptor) + dslb.SetPushFlag(); dslb.AddBinding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, NUM_UTILITY_SAMPLERS, VK_SHADER_STAGE_FRAGMENT_BIT); if ((m_utility_ds_layout = dslb.Create(dev)) == VK_NULL_HANDLE) return false; @@ -3797,7 +3820,8 @@ bool GSDeviceVK::CreatePipelineLayouts() return false; Vulkan::SetObjectName(dev, m_tfx_ubo_ds_layout, "TFX UBO descriptor layout"); - dslb.SetPushFlag(); + if (m_optional_extensions.vk_khr_push_descriptor) + dslb.SetPushFlag(); dslb.AddBinding(TFX_TEXTURE_TEXTURE, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT); dslb.AddBinding(TFX_TEXTURE_PALETTE, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1, VK_SHADER_STAGE_FRAGMENT_BIT); dslb.AddBinding(TFX_TEXTURE_RT, @@ -4356,7 +4380,8 @@ bool GSDeviceVK::CompileCASPipelines() Vulkan::DescriptorSetLayoutBuilder dslb; Vulkan::PipelineLayoutBuilder plb; - dslb.SetPushFlag(); + if (m_optional_extensions.vk_khr_push_descriptor) + dslb.SetPushFlag(); dslb.AddBinding(0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT); dslb.AddBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT); if ((m_cas_ds_layout = dslb.Create(dev)) == VK_NULL_HANDLE) @@ -4563,7 +4588,25 @@ bool GSDeviceVK::DoCAS( Vulkan::DescriptorSetUpdateBuilder dsub; dsub.AddImageDescriptorWrite(VK_NULL_HANDLE, 0, sTexVK->GetView(), sTexVK->GetVkLayout()); dsub.AddStorageImageDescriptorWrite(VK_NULL_HANDLE, 1, dTexVK->GetView(), dTexVK->GetVkLayout()); - dsub.PushUpdate(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, m_cas_pipeline_layout, 0, false); + if (m_optional_extensions.vk_khr_push_descriptor) + { + dsub.PushUpdate(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, m_cas_pipeline_layout, 0, false); + } + else + { + VkDescriptorSet tmp = AllocatePersistentDescriptorSet(m_cas_ds_layout); + if (tmp != VK_NULL_HANDLE) + { + dsub.UpdateToDescriptorSet(m_device, tmp, true); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, m_cas_pipeline_layout, 0, 1, &tmp, 0, nullptr); + FrameResources& fres = m_frame_resources[m_current_frame]; + fres.cleanup_resources.push_back([this, tmp]() { vkFreeDescriptorSets(m_device, m_global_descriptor_pool, 1, &tmp); }); + } + else + { + Console.Error("VK: Failed to allocate fallback descriptor set for CAS push-descriptor emulation."); + } + } // the actual meat and potatoes! only four commands. static const int threadGroupWorkRegionDim = 16; @@ -5447,7 +5490,26 @@ bool GSDeviceVK::ApplyTFXState(bool already_execed) m_tfx_textures[TFX_TEXTURE_PRIMID]->GetView(), m_tfx_textures[TFX_TEXTURE_PRIMID]->GetVkLayout()); } - dsub.PushUpdate(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_tfx_pipeline_layout, TFX_DESCRIPTOR_SET_TEXTURES); + if (m_optional_extensions.vk_khr_push_descriptor) + { + dsub.PushUpdate(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_tfx_pipeline_layout, TFX_DESCRIPTOR_SET_TEXTURES); + } + else + { + VkDescriptorSet tmp = AllocatePersistentDescriptorSet(m_tfx_texture_ds_layout); + if (tmp != VK_NULL_HANDLE) + { + dsub.UpdateToDescriptorSet(m_device, tmp, true); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_tfx_pipeline_layout, + TFX_DESCRIPTOR_SET_TEXTURES, 1, &tmp, 0, nullptr); + FrameResources& fres = m_frame_resources[m_current_frame]; + fres.cleanup_resources.push_back([this, tmp]() { vkFreeDescriptorSets(m_device, m_global_descriptor_pool, 1, &tmp); }); + } + else + { + Console.Error("VK: Failed to allocate fallback descriptor set for TFX push-descriptor emulation."); + } + } } ApplyBaseState(flags, cmdbuf); @@ -5470,7 +5532,25 @@ bool GSDeviceVK::ApplyUtilityState(bool already_execed) Vulkan::DescriptorSetUpdateBuilder dsub; dsub.AddCombinedImageSamplerDescriptorWrite( VK_NULL_HANDLE, 0, m_utility_texture->GetView(), m_utility_sampler, m_utility_texture->GetVkLayout()); - dsub.PushUpdate(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_utility_pipeline_layout, 0, false); + if (m_optional_extensions.vk_khr_push_descriptor) + { + dsub.PushUpdate(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_utility_pipeline_layout, 0, false); + } + else + { + VkDescriptorSet tmp = AllocatePersistentDescriptorSet(m_utility_ds_layout); + if (tmp != VK_NULL_HANDLE) + { + dsub.UpdateToDescriptorSet(m_device, tmp, true); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_utility_pipeline_layout, 0, 1, &tmp, 0, nullptr); + FrameResources& fres = m_frame_resources[m_current_frame]; + fres.cleanup_resources.push_back([this, tmp]() { vkFreeDescriptorSets(m_device, m_global_descriptor_pool, 1, &tmp); }); + } + else + { + Console.Error("VK: Failed to allocate fallback descriptor set for utility push-descriptor emulation."); + } + } } diff --git a/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h b/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h index 975b45f..bdc4f41 100644 --- a/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h +++ b/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h @@ -35,6 +35,7 @@ public: struct OptionalExtensions { + bool vk_khr_push_descriptor : 1; bool vk_ext_provoking_vertex : 1; bool vk_ext_memory_budget : 1; bool vk_ext_calibrated_timestamps : 1; @@ -208,6 +209,7 @@ private: { // [0] - Init (upload) command buffer, [1] - draw command buffer VkCommandPool command_pool = VK_NULL_HANDLE; + VkDescriptorPool descriptor_pool = VK_NULL_HANDLE; std::array command_buffers{VK_NULL_HANDLE, VK_NULL_HANDLE}; VkFence fence = VK_NULL_HANDLE; u64 fence_counter = 0; diff --git a/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/GSTextureVK.cpp b/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/GSTextureVK.cpp index 1ce0bc3..fae03d8 100644 --- a/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/GSTextureVK.cpp +++ b/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/GSTextureVK.cpp @@ -299,7 +299,11 @@ VkBuffer GSTextureVK::AllocateUploadStagingBuffer(const void* data, u32 pitch, u // And write the data. CopyTextureDataForUpload(ai.pMappedData, data, pitch, upload_pitch, height); - vmaFlushAllocation(GSDeviceVK::GetInstance()->GetAllocator(), allocation, 0, size); + // Avoid redundant flush if the staging memory is HOST_COHERENT + VkMemoryPropertyFlags mem_props = 0; + vmaGetAllocationMemoryProperties(GSDeviceVK::GetInstance()->GetAllocator(), allocation, &mem_props); + if ((mem_props & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0) + vmaFlushAllocation(GSDeviceVK::GetInstance()->GetAllocator(), allocation, 0, size); return buffer; } diff --git a/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKBuilders.cpp b/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKBuilders.cpp index 2c78d97..3fc0c01 100644 --- a/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKBuilders.cpp +++ b/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKBuilders.cpp @@ -716,6 +716,19 @@ void Vulkan::DescriptorSetUpdateBuilder::Update(VkDevice device, bool clear /*= Clear(); } +void Vulkan::DescriptorSetUpdateBuilder::UpdateToDescriptorSet(VkDevice device, VkDescriptorSet dst_set, bool clear /*= true*/) +{ + pxAssert(m_num_writes > 0); + + for (u32 i = 0; i < m_num_writes; ++i) + m_writes[i].dstSet = dst_set; + + vkUpdateDescriptorSets(device, m_num_writes, (m_num_writes > 0) ? m_writes.data() : nullptr, 0, nullptr); + + if (clear) + Clear(); +} + void Vulkan::DescriptorSetUpdateBuilder::PushUpdate( VkCommandBuffer cmdbuf, VkPipelineBindPoint bind_point, VkPipelineLayout layout, u32 set, bool clear /*= true*/) { diff --git a/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKBuilders.h b/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKBuilders.h index 8ebde70..45af0b6 100644 --- a/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKBuilders.h +++ b/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKBuilders.h @@ -237,6 +237,7 @@ namespace Vulkan void Clear(); void Update(VkDevice device, bool clear = true); + void UpdateToDescriptorSet(VkDevice device, VkDescriptorSet dst_set, bool clear = true); void PushUpdate(VkCommandBuffer cmdbuf, VkPipelineBindPoint bind_point, VkPipelineLayout layout, u32 set, bool clear = true); diff --git a/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKLoader.cpp b/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKLoader.cpp index 164ca0f..3228446 100644 --- a/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKLoader.cpp +++ b/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKLoader.cpp @@ -7,6 +7,8 @@ #include "common/Console.h" #include "common/DynamicLibrary.h" #include "common/Error.h" +#include "pcsx2/Config.h" +#include "GS/GS.h" #include #include @@ -14,6 +16,13 @@ #include #include +#ifdef __ANDROID__ +#ifdef USE_ADRENOTOOLS +#include +#endif +#include +#endif + extern "C" { #define VULKAN_MODULE_ENTRY_POINT(name, required) PFN_##name name; @@ -47,22 +56,153 @@ bool Vulkan::LoadVulkanLibrary(Error* error) { pxAssertRel(!s_vulkan_library.IsOpen(), "Vulkan module is not loaded."); + // Check for custom driver path from config + std::string custom_driver_path; + if (GSConfig.CustomDriverPath.empty()) + { + char* libvulkan_env = getenv("LIBVULKAN_PATH"); + if (libvulkan_env) + custom_driver_path = libvulkan_env; +#if defined(USE_ADRENOTOOLS) && defined(__ANDROID__) + if (custom_driver_path.empty()) + { + char* adreno_tools_path = getenv("ADRENOTOOLS_LIBVULKAN_PATH"); + if (adreno_tools_path) + custom_driver_path = adreno_tools_path; + } +#endif + } + else + { + custom_driver_path = GSConfig.CustomDriverPath; + } + + // Try to load custom driver if specified + if (!custom_driver_path.empty()) + { +#if defined(__ANDROID__) && defined(USE_ADRENOTOOLS) + std::string custom_driver_dir; + std::string custom_driver_name; + + size_t last_slash = custom_driver_path.find_last_of("/\\"); + if (last_slash != std::string::npos) + { + custom_driver_dir = custom_driver_path.substr(0, last_slash + 1); + custom_driver_name = custom_driver_path.substr(last_slash + 1); + } + else + { + custom_driver_name = custom_driver_path; + } + + const char* hook_lib_dir = getenv("ANDROID_NATIVE_LIB_DIR"); + if (!hook_lib_dir) + { + hook_lib_dir = getenv("ANDROID_DATA_DIR"); + } + + if (hook_lib_dir && !custom_driver_dir.empty() && !custom_driver_name.empty()) + { + Console.WriteLn(Color_StrongGreen, "Vulkan: Using libadrenotools to load custom driver: %s from %s", + custom_driver_name.c_str(), custom_driver_dir.c_str()); + + void* vulkan_handle = adrenotools_open_libvulkan( + RTLD_NOW | RTLD_LOCAL, // dlopenMode + ADRENOTOOLS_DRIVER_CUSTOM, // featureFlags + nullptr, // tmpLibDir (nullptr for API 29+) + hook_lib_dir, // hookLibDir + custom_driver_dir.c_str(), // customDriverDir + custom_driver_name.c_str(), // customDriverName + nullptr, // fileRedirectDir + nullptr // userMappingHandle + ); + + if (vulkan_handle) + { + // Grab the handle from libadrenotools + s_vulkan_library.Adopt(vulkan_handle); + Console.WriteLn(Color_StrongGreen, "Vulkan: Successfully loaded custom driver via libadrenotools"); + } + else + { + Console.Warning("Vulkan: libadrenotools failed to load custom driver, falling back to direct loading"); + // Fall through to direct loading + if (s_vulkan_library.Open(custom_driver_path.c_str(), error)) + { + Console.WriteLn(Color_StrongGreen, "Vulkan: Successfully loaded custom driver directly"); + } + else + { + Console.Warning("Vulkan: Failed to load custom driver from '%s', falling back to system driver", custom_driver_path.c_str()); + } + } + } + else + { + Console.Warning("Vulkan: libadrenotools requires ANDROID_NATIVE_LIB_DIR and valid custom driver path, falling back to direct loading"); + if (s_vulkan_library.Open(custom_driver_path.c_str(), error)) + { + Console.WriteLn(Color_StrongGreen, "Vulkan: Successfully loaded custom driver directly"); + } + else + { + Console.Warning("Vulkan: Failed to load custom driver from '%s', falling back to system driver", custom_driver_path.c_str()); + } + } +#else + // Loading without libadrenotools + Console.WriteLn(Color_StrongGreen, "Vulkan: Attempting to load custom driver from: %s", custom_driver_path.c_str()); + if (s_vulkan_library.Open(custom_driver_path.c_str(), error)) + { + Console.WriteLn(Color_StrongGreen, "Vulkan: Successfully loaded custom driver"); + } + else + { + Console.Warning("Vulkan: Failed to load custom driver from '%s', falling back to system driver", custom_driver_path.c_str()); + } +#endif + } + #ifdef __APPLE__ - // Check if a path to a specific Vulkan library has been specified. - char* libvulkan_env = getenv("LIBVULKAN_PATH"); - if (libvulkan_env) - s_vulkan_library.Open(libvulkan_env, error); + // On macOS, try MoltenVK if custom driver failed or wasn't specified if (!s_vulkan_library.IsOpen() && !s_vulkan_library.Open(DynamicLibrary::GetVersionedFilename("MoltenVK").c_str(), error)) { return false; } #else - // try versioned first, then unversioned. - if (!s_vulkan_library.Open(DynamicLibrary::GetVersionedFilename("vulkan", 1).c_str(), error) && - !s_vulkan_library.Open(DynamicLibrary::GetVersionedFilename("vulkan").c_str(), error)) + // On other platforms, try versioned first, then unversioned system libraries + if (!s_vulkan_library.IsOpen()) { - return false; +#if defined(__ANDROID__) + const char* android_native_lib_dir = getenv("ANDROID_NATIVE_LIB_DIR"); + if (android_native_lib_dir) + { + std::string custom_lib_path = std::string(android_native_lib_dir) + "/libvulkan.so"; + if (s_vulkan_library.Open(custom_lib_path.c_str(), error)) + { + Console.WriteLn(Color_StrongGreen, "Vulkan: Loaded custom driver from app directory"); + } + } + + if (!s_vulkan_library.IsOpen()) + { + if (!s_vulkan_library.Open("libvulkan.so", error)) + { + if (!s_vulkan_library.Open(DynamicLibrary::GetVersionedFilename("vulkan", 1).c_str(), error) && + !s_vulkan_library.Open(DynamicLibrary::GetVersionedFilename("vulkan").c_str(), error)) + { + return false; + } + } + } +#else + if (!s_vulkan_library.Open(DynamicLibrary::GetVersionedFilename("vulkan", 1).c_str(), error) && + !s_vulkan_library.Open(DynamicLibrary::GetVersionedFilename("vulkan").c_str(), error)) + { + return false; + } +#endif } #endif diff --git a/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKStreamBuffer.cpp b/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKStreamBuffer.cpp index 8f6323d..b5dc355 100644 --- a/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKStreamBuffer.cpp +++ b/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKStreamBuffer.cpp @@ -59,6 +59,10 @@ bool VKStreamBuffer::Create(VkBufferUsageFlags usage, u32 size) VmaAllocationCreateInfo aci = {}; aci.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; +#ifdef VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT + // Hint the allocator that we write sequentially from CPU to GPU + aci.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; +#endif aci.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; aci.preferredFlags = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; @@ -84,6 +88,10 @@ bool VKStreamBuffer::Create(VkBufferUsageFlags usage, u32 size) m_allocation = new_allocation; m_buffer = new_buffer; m_host_pointer = static_cast(ai.pMappedData); + // Cache coherency so we can avoid redundant flushes on HOST_COHERENT memory + VkMemoryPropertyFlags mem_props = 0; + vmaGetAllocationMemoryProperties(GSDeviceVK::GetInstance()->GetAllocator(), m_allocation, &mem_props); + m_allocation_is_coherent = (mem_props & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0; return true; } @@ -181,7 +189,8 @@ void VKStreamBuffer::CommitMemory(u32 final_num_bytes) pxAssert(final_num_bytes <= m_current_space); // For non-coherent mappings, flush the memory range - vmaFlushAllocation(GSDeviceVK::GetInstance()->GetAllocator(), m_allocation, m_current_offset, final_num_bytes); + if (!m_allocation_is_coherent) + vmaFlushAllocation(GSDeviceVK::GetInstance()->GetAllocator(), m_allocation, m_current_offset, final_num_bytes); m_current_offset += final_num_bytes; m_current_space -= final_num_bytes; diff --git a/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKStreamBuffer.h b/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKStreamBuffer.h index e0839a8..4cef1cf 100644 --- a/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKStreamBuffer.h +++ b/app/src/main/cpp/pcsx2/GS/Renderers/Vulkan/VKStreamBuffer.h @@ -52,6 +52,7 @@ private: VmaAllocation m_allocation = VK_NULL_HANDLE; VkBuffer m_buffer = VK_NULL_HANDLE; u8* m_host_pointer = nullptr; + bool m_allocation_is_coherent = false; // List of fences and the corresponding positions in the buffer std::deque> m_tracked_fences; diff --git a/app/src/main/cpp/pcsx2/Pcsx2Config.cpp b/app/src/main/cpp/pcsx2/Pcsx2Config.cpp index f6f436d..9724d14 100644 --- a/app/src/main/cpp/pcsx2/Pcsx2Config.cpp +++ b/app/src/main/cpp/pcsx2/Pcsx2Config.cpp @@ -720,7 +720,7 @@ Pcsx2Config::GSOptions::GSOptions() OsdShowFPS = true; OsdShowVPS = true; OsdShowCPU = true; - OsdShowGPU = true; + OsdShowGPU = false; OsdShowResolution = true; OsdShowGSStats = false; OsdShowIndicators = true; @@ -867,6 +867,7 @@ bool Pcsx2Config::GSOptions::OptionsAreEqual(const GSOptions& right) const OpEqu(AudioCaptureBitrate) && OpEqu(Adapter) && + OpEqu(CustomDriverPath) && OpEqu(HWDumpDirectory) && OpEqu(SWDumpDirectory)); @@ -881,6 +882,7 @@ bool Pcsx2Config::GSOptions::RestartOptionsAreEqual(const GSOptions& right) cons { return OpEqu(Renderer) && OpEqu(Adapter) && + OpEqu(CustomDriverPath) && OpEqu(UseDebugDevice) && OpEqu(UseBlitSwapChain) && OpEqu(DisableShaderCache) && @@ -1054,6 +1056,7 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap) SettingsWrapBitfieldEx(AudioCaptureBitrate, "AudioCaptureBitrate"); SettingsWrapEntry(Adapter); + SettingsWrapEntry(CustomDriverPath); SettingsWrapEntry(HWDumpDirectory); if (!HWDumpDirectory.empty() && !Path::IsAbsolute(HWDumpDirectory)) HWDumpDirectory = Path::Combine(EmuFolders::DataRoot, HWDumpDirectory);