Mali Mediatek fixes

This commit is contained in:
izzy2lost
2026-02-23 06:38:01 -05:00
parent 9c8af01b0e
commit cac5891d91
19 changed files with 497 additions and 204 deletions
+16 -4
View File
@@ -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'
}
@@ -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
@@ -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
+2 -1
View File
@@ -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;
+15 -7
View File
@@ -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<int>(GSRendererType::Auto));
const GSRendererType base_renderer = static_cast<GSRendererType>(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)
{
+17 -29
View File
@@ -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 <array>
@@ -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
}
@@ -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;
@@ -60,9 +60,10 @@ std::vector<GLContext::FullscreenModeInfo> GLContext::EnumerateFullscreenModes()
std::unique_ptr<GLContext> 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<Version*>(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> 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> GLContext::Create(const WindowInfo& wi, const Version
context_being_created = nullptr;
const char* gl_vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
const char* gl_renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
if (gl_vendor && gl_renderer)
DisableBrokenExtensions(gl_vendor, gl_renderer);
return context;
}
@@ -4,7 +4,7 @@
#include "GS/Renderers/OpenGL/GLContextEGL.h"
#include "common/Console.h"
#include <EGL/eglext.h>
#include "GS/GS.h"
#include <algorithm>
#include <cstring>
@@ -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<EGLNativeDisplayType>(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;
@@ -18,18 +18,9 @@
#include "IconsFontAwesome5.h"
#include <cinttypes>
#include <cctype>
#include <fstream>
#include <sstream>
// 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<char>(std::tolower(static_cast<unsigned char>(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<std::string> 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<std::string> 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<u32, NUM_CAS_CONSTANTS>& constants)
@@ -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<u32>(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<float>(m_device_properties.limits.maxSamplerAnisotropy)) : 1.0f;
const float aniso_value = aniso ? std::min(static_cast<float>(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<VkBool32>(aniso), // anisotropy enable
aniso ? static_cast<float>(GSConfig.MaxAnisotropy) : 1.0f, // anisotropy
static_cast<VkBool32>(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.");
}
}
}
@@ -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<VkCommandBuffer, 2> command_buffers{VK_NULL_HANDLE, VK_NULL_HANDLE};
VkFence fence = VK_NULL_HANDLE;
u64 fence_counter = 0;
@@ -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;
}
@@ -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*/)
{
@@ -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);
@@ -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 <cstdarg>
#include <cstdio>
@@ -14,6 +16,13 @@
#include <cstring>
#include <string>
#ifdef __ANDROID__
#ifdef USE_ADRENOTOOLS
#include <adrenotools/driver.h>
#endif
#include <dlfcn.h>
#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
@@ -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<u8*>(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;
@@ -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<std::pair<u64, u32>> m_tracked_fences;
+4 -1
View File
@@ -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);