mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
Android: bring the platform up on the unified monorepo core
Wire platforms/android to build against the shared core and reconcile the pieces the Android native lib needs, so the Android target compiles and links on this repo (produces libemucore_4k.so). Per-platform builds stay independent; the PC and iOS targets are unchanged. Build/CMake: - Android-aware cmake modules under platforms/android/.../cmake with CMAKE_MODULE_PATH repointed at them (root desktop modules untouched) - ryml::ryml alias, TOP_CMAKE_WAS_SOURCED, drop the fatal 3rdparty add_subdirectory; pcsx2 PCAP guard elseif(NOT ANDROID) - git-sync-deps step for shaderc third_party (fetch-on-demand, not submodules); android .gitignore for keystores/artifacts/shaderc deps - dual-core rotation-signed build scripts (release APK + Play AAB) Core reconciliation (APIs the native lib expects): - GS present-cap/frameskip, Adreno framebuffer-fetch GSOption round-trip - SPU2 channel swap + output-pause suppression - Achievements JSON, VMManager autosave slot, WindowInfo::Android - FileSystem SAF hooks, custom Vulkan driver path, BIOS-from-fd - Oboe audio stream, EGL native-window, Linux/Android source guards refresh-experimental fixes layered onto the shared recompiler/GS: - IOP direct block chaining (aR3000A, gated s_iopBlockLinkEnabled, default off so behaviour is byte-identical until enabled) - COP2 CO-bit macro-mode gate (aVU_Macro) - EE mixed-operand constant folding - Adreno GL coherent depth feedback via gl_LastFragDepthARM, gated to the Adreno GPU profile (inert on every other GPU) - Android GPU gates: GPU-profile shader-header keystone, FIFO_RELAXED present mode, Mali coherent readback, Adreno-650 boot-crash gates, ROAA VK_ARM alias
This commit is contained in:
@@ -73,6 +73,11 @@ jobs:
|
||||
- name: Install NDK
|
||||
run: yes | sdkmanager "ndk;27.2.12479018" "cmake;3.22.1" >/dev/null || true
|
||||
|
||||
# shaderc's SPIRV-Tools/glslang/etc. are fetched on demand (not vendored,
|
||||
# not submodules), so `submodules: recursive` above does not provide them.
|
||||
- name: Fetch shaderc third-party deps
|
||||
run: python3 app/src/main/cpp/3rdparty/shaderc/utils/git-sync-deps
|
||||
|
||||
- name: Build release APK
|
||||
run: ./gradlew :app:assembleRelease --stacktrace
|
||||
|
||||
|
||||
@@ -123,6 +123,17 @@ namespace FileSystem
|
||||
|
||||
int OpenFDFile(const char* filename, int flags, int mode, Error* error = nullptr);
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
/// Open a content:// URI (passed through from the Java SAF layer) and return
|
||||
/// its raw fd. Defined in the Android JNI layer (native-lib.cpp).
|
||||
int OpenFDFileContent(const char* filename);
|
||||
/// Create a directory tree via the Java File API (NativeApp.createDirectoryPath).
|
||||
/// Fallback for when libc mkdir() is denied on FUSE-emulated external storage
|
||||
/// (user-picked custom data folders) despite all-files access. Returns true if
|
||||
/// the directory exists afterwards.
|
||||
bool CreateDirectoryViaJava(const char* path);
|
||||
#endif
|
||||
|
||||
/// Sharing modes for OpenSharedCFile().
|
||||
enum class FileShareMode
|
||||
{
|
||||
|
||||
@@ -22,6 +22,16 @@
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include <sys/syscall.h>
|
||||
// bionic lacks shm_open until API 30; memfd_create is a libc wrapper only from
|
||||
// API 30 too, but the raw syscall works from API 26 (our minSdk).
|
||||
static int memfd_create_wrapper(const char* name, unsigned int flags)
|
||||
{
|
||||
return static_cast<int>(syscall(__NR_memfd_create, name, flags));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__FreeBSD__)
|
||||
#include "cpuinfo.h"
|
||||
#endif
|
||||
@@ -64,6 +74,15 @@ std::string HostSys::GetFileMappingName(const char* prefix)
|
||||
|
||||
void* HostSys::CreateSharedMemory(const char* name, size_t size)
|
||||
{
|
||||
#if defined(__ANDROID__)
|
||||
// Android: memfd_create available since API 26 (our minSdk), no shm_open until API 30.
|
||||
const int fd = memfd_create_wrapper(name, 0);
|
||||
if (fd < 0)
|
||||
{
|
||||
std::fprintf(stderr, "memfd_create failed: %d\n", errno);
|
||||
return nullptr;
|
||||
}
|
||||
#else
|
||||
const int fd = shm_open(name, O_CREAT | O_EXCL | O_RDWR, 0600);
|
||||
if (fd < 0)
|
||||
{
|
||||
@@ -73,6 +92,7 @@ void* HostSys::CreateSharedMemory(const char* name, size_t size)
|
||||
|
||||
// we're not going to be opening this mapping in other processes, so remove the file
|
||||
shm_unlink(name);
|
||||
#endif
|
||||
|
||||
// ensure it's the correct size
|
||||
if (ftruncate(fd, static_cast<off_t>(size)) < 0)
|
||||
|
||||
@@ -13,14 +13,18 @@
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
#if !defined(__ANDROID__)
|
||||
#include <dbus/dbus.h>
|
||||
#endif
|
||||
#include <spawn.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#if !defined(__ANDROID__)
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/extensions/XInput2.h>
|
||||
#endif
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -140,6 +144,7 @@ std::string GetOSVersionString()
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(__ANDROID__)
|
||||
static bool SetScreensaverInhibitDBus(const bool inhibit_requested, const char* program_name, const char* reason)
|
||||
{
|
||||
static dbus_uint32_t s_cookie;
|
||||
@@ -329,6 +334,32 @@ void Common::DetachMousePositionCb()
|
||||
}
|
||||
}
|
||||
|
||||
#else // __ANDROID__
|
||||
|
||||
// Android has no X11 or D-Bus screensaver/session service; these are no-ops.
|
||||
bool Common::InhibitScreensaver(bool inhibit)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void Common::SetMousePosition(int x, int y)
|
||||
{
|
||||
}
|
||||
|
||||
bool Common::AttachMousePositionCb(std::function<void(int, int)> cb)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void Common::DetachMousePositionCb()
|
||||
{
|
||||
}
|
||||
|
||||
#endif // !__ANDROID__
|
||||
|
||||
// Desktop-Linux only: the aplay/gstreamer approach uses posix_spawnp (bionic API 30+)
|
||||
// and external audio tools. Android provides its own Common::PlaySoundAsync (native-lib).
|
||||
#ifndef __ANDROID__
|
||||
bool Common::PlaySoundAsync(const char* path)
|
||||
{
|
||||
#ifdef __linux__
|
||||
@@ -365,6 +396,7 @@ bool Common::PlaySoundAsync(const char* path)
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#endif // !__ANDROID__ (PlaySoundAsync — Android impl lives in native-lib.cpp)
|
||||
|
||||
void Threading::Sleep(int ms)
|
||||
{
|
||||
|
||||
+2
-1
@@ -15,7 +15,8 @@ struct WindowInfo
|
||||
Win32,
|
||||
X11,
|
||||
Wayland,
|
||||
MacOS
|
||||
MacOS,
|
||||
Android
|
||||
};
|
||||
|
||||
/// The type of the surface. Surfaceless indicates it will not be displayed on screen at all.
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
@@ -401,6 +402,215 @@ bool Achievements::HasAchievements()
|
||||
return s_has_achievements;
|
||||
}
|
||||
|
||||
std::string Achievements::GetAchievementsAsJSON()
|
||||
{
|
||||
// JSON-quote a string into the buffer, escaping the bare minimum so
|
||||
// org.json on the Java side can parse the payload. We don't expect
|
||||
// achievement titles/descriptions to contain control chars beyond \n,
|
||||
// but escape them defensively. Characters > 0x7F are passed through —
|
||||
// the JSON spec allows raw UTF-8 in string contents.
|
||||
auto append_json_string = [](std::string& dst, const char* src) {
|
||||
dst += '"';
|
||||
if (src)
|
||||
{
|
||||
for (const char* p = src; *p; ++p)
|
||||
{
|
||||
const unsigned char c = static_cast<unsigned char>(*p);
|
||||
switch (c)
|
||||
{
|
||||
case '"': dst += "\\\""; break;
|
||||
case '\\': dst += "\\\\"; break;
|
||||
case '\n': dst += "\\n"; break;
|
||||
case '\r': dst += "\\r"; break;
|
||||
case '\t': dst += "\\t"; break;
|
||||
default:
|
||||
if (c < 0x20)
|
||||
{
|
||||
char esc[8];
|
||||
std::snprintf(esc, sizeof(esc), "\\u%04x", c);
|
||||
dst += esc;
|
||||
}
|
||||
else
|
||||
{
|
||||
dst += static_cast<char>(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
dst += '"';
|
||||
};
|
||||
|
||||
std::string out;
|
||||
out.reserve(4096);
|
||||
out += '{';
|
||||
|
||||
auto lock = GetLock();
|
||||
|
||||
const bool active = HasActiveGame() && s_has_achievements;
|
||||
|
||||
// `s_client` is the persistent rc_client created by Achievements::Initialize,
|
||||
// which only runs when a VM is initialized AND EmuConfig.Achievements.Enabled.
|
||||
// On Android the user typically logs in BEFORE loading a game, via the
|
||||
// overlay's right-side panel — that path uses a TEMPORARY client which is
|
||||
// destroyed when Login returns. So `rc_client_get_user_info(s_client)` reports
|
||||
// "not logged in" even though the auth token was just written to the secrets
|
||||
// layer. Fall back to the persisted Username / Token to detect post-login
|
||||
// state. Token lives in the LAYER_SECRETS in-memory store; Username in BASE.
|
||||
const rc_client_user_t* user = s_client ? rc_client_get_user_info(s_client) : nullptr;
|
||||
std::string display_name;
|
||||
bool logged_in = false;
|
||||
if (user && user->display_name)
|
||||
{
|
||||
display_name = user->display_name;
|
||||
logged_in = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string saved_user = Host::GetBaseStringSettingValue("Achievements", "Username", "");
|
||||
const std::string saved_token = Host::GetStringSettingValue("Achievements", "Token");
|
||||
if (!saved_user.empty() && !saved_token.empty())
|
||||
{
|
||||
display_name = saved_user;
|
||||
logged_in = true;
|
||||
}
|
||||
}
|
||||
|
||||
out += "\"active\":";
|
||||
out += active ? "true" : "false";
|
||||
out += ",\"loggedIn\":";
|
||||
out += logged_in ? "true" : "false";
|
||||
out += ",\"hardcore\":";
|
||||
out += s_hardcore_mode ? "true" : "false";
|
||||
out += ",\"userName\":";
|
||||
append_json_string(out, display_name.c_str());
|
||||
|
||||
// Player score. Only available from the persistent client (a game with
|
||||
// achievements is loaded); the post-login temporary client is destroyed,
|
||||
// so report -1 ("unknown") when we can't read it. The panel hides the
|
||||
// points chip on -1 rather than showing a misleading 0.
|
||||
out += ",\"score\":";
|
||||
out += std::to_string(user ? static_cast<long long>(user->score) : -1LL);
|
||||
out += ",\"softcoreScore\":";
|
||||
out += std::to_string(user ? static_cast<long long>(user->score_softcore) : -1LL);
|
||||
|
||||
// RA presentation options (global [Achievements] settings) so the panel
|
||||
// can show + toggle them without a second JNI poll. Defaults mirror
|
||||
// Pcsx2Config::AchievementsOptions() (all on).
|
||||
out += ",\"notifications\":";
|
||||
out += Host::GetBaseBoolSettingValue("Achievements", "Notifications", true) ? "true" : "false";
|
||||
out += ",\"leaderboardNotifications\":";
|
||||
out += Host::GetBaseBoolSettingValue("Achievements", "LeaderboardNotifications", true) ? "true" : "false";
|
||||
out += ",\"overlays\":";
|
||||
out += Host::GetBaseBoolSettingValue("Achievements", "Overlays", true) ? "true" : "false";
|
||||
out += ",\"lbOverlays\":";
|
||||
out += Host::GetBaseBoolSettingValue("Achievements", "LBOverlays", true) ? "true" : "false";
|
||||
out += ",\"soundEffects\":";
|
||||
out += Host::GetBaseBoolSettingValue("Achievements", "SoundEffects", true) ? "true" : "false";
|
||||
|
||||
out += ",\"items\":[";
|
||||
|
||||
if (active && s_client)
|
||||
{
|
||||
// Build a fresh list — don't reuse s_achievement_list, that one's
|
||||
// owned by the ImGui pause-menu Achievements window and we don't
|
||||
// want to step on its lifecycle.
|
||||
rc_client_achievement_list_t* list = rc_client_create_achievement_list(
|
||||
s_client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL,
|
||||
RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS);
|
||||
|
||||
if (list)
|
||||
{
|
||||
// Same display order the ImGui window uses.
|
||||
static constexpr u32 bucket_order[] = {
|
||||
RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE,
|
||||
RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED,
|
||||
RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED,
|
||||
RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE,
|
||||
RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED,
|
||||
RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL,
|
||||
RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED,
|
||||
};
|
||||
|
||||
bool first = true;
|
||||
for (u32 bucket_type : bucket_order)
|
||||
{
|
||||
for (u32 b = 0; b < list->num_buckets; b++)
|
||||
{
|
||||
const rc_client_achievement_bucket_t& bucket = list->buckets[b];
|
||||
if (bucket.bucket_type != bucket_type)
|
||||
continue;
|
||||
for (u32 a = 0; a < bucket.num_achievements; a++)
|
||||
{
|
||||
const rc_client_achievement_t* ach = bucket.achievements[a];
|
||||
if (!ach)
|
||||
continue;
|
||||
if (!first)
|
||||
out += ',';
|
||||
first = false;
|
||||
out += "{\"id\":";
|
||||
out += std::to_string(ach->id);
|
||||
out += ",\"title\":";
|
||||
append_json_string(out, ach->title);
|
||||
out += ",\"description\":";
|
||||
append_json_string(out, ach->description);
|
||||
out += ",\"points\":";
|
||||
out += std::to_string(ach->points);
|
||||
out += ",\"unlocked\":";
|
||||
out += (ach->state == RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED) ? "true" : "false";
|
||||
out += ",\"bucket\":";
|
||||
out += std::to_string(static_cast<int>(bucket.bucket_type));
|
||||
out += ",\"rarity\":";
|
||||
{
|
||||
char buf[32];
|
||||
std::snprintf(buf, sizeof(buf), "%.1f", ach->rarity);
|
||||
out += buf;
|
||||
}
|
||||
out += ",\"measuredProgress\":";
|
||||
append_json_string(out, ach->measured_progress);
|
||||
out += ",\"measuredPercent\":";
|
||||
{
|
||||
char buf[32];
|
||||
std::snprintf(buf, sizeof(buf), "%.1f", ach->measured_percent);
|
||||
out += buf;
|
||||
}
|
||||
// Type lets the UI tag MISSABLE / PROGRESSION / WIN
|
||||
// achievements (STANDARD = 0 → no tag). Unlocked is
|
||||
// the SOFTCORE/HARDCORE bitmask — distinguishes how
|
||||
// the user earned it for the HC indicator. unlockTime
|
||||
// is unix-seconds (0 if locked) so the panel can
|
||||
// render a relative timestamp.
|
||||
out += ",\"type\":";
|
||||
out += std::to_string(static_cast<int>(ach->type));
|
||||
out += ",\"unlockedMask\":";
|
||||
out += std::to_string(static_cast<int>(ach->unlocked));
|
||||
out += ",\"unlockTime\":";
|
||||
out += std::to_string(static_cast<long long>(ach->unlock_time));
|
||||
// Badge image URL for the achievement's current state
|
||||
// (unlocked variant vs `_lock` greyscale). Java side
|
||||
// fetches via Coil into the persistent cover_cache —
|
||||
// RA badge URLs are immutable per badge_name so they
|
||||
// cache forever. Empty string on failure → panel
|
||||
// falls back to the glyph placeholder.
|
||||
{
|
||||
char url_buf[256];
|
||||
const int rc = rc_client_achievement_get_image_url(
|
||||
ach, ach->state, url_buf, std::size(url_buf));
|
||||
out += ",\"iconUrl\":";
|
||||
append_json_string(out, rc == RC_OK ? url_buf : "");
|
||||
}
|
||||
out += '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
rc_client_destroy_achievement_list(list);
|
||||
}
|
||||
}
|
||||
|
||||
out += "]}";
|
||||
return out;
|
||||
}
|
||||
|
||||
bool Achievements::HasLeaderboards()
|
||||
{
|
||||
return s_has_leaderboards;
|
||||
|
||||
@@ -105,6 +105,24 @@ namespace Achievements
|
||||
/// Returns true if the current game has any achievements.
|
||||
bool HasAchievements();
|
||||
|
||||
/// Snapshot the current game's achievements as JSON for the in-game
|
||||
/// overlay's right-side panel. Walks rc_client buckets in display
|
||||
/// order (active challenge → recently unlocked → unlocked → almost
|
||||
/// there → locked → unofficial → unsupported). Empty array when no
|
||||
/// active game / not logged in. Format:
|
||||
/// {
|
||||
/// "active": bool, // game has any achievements
|
||||
/// "loggedIn": bool, // a user is logged in to RA
|
||||
/// "userName": "string", // display name when loggedIn
|
||||
/// "items": [
|
||||
/// { "id": int, "title": "...", "description": "...",
|
||||
/// "points": int, "unlocked": bool, "bucket": int,
|
||||
/// "rarity": float, "measuredProgress": "..." }
|
||||
/// ]
|
||||
/// }
|
||||
/// Self-contained — no rcheevos headers needed by the caller.
|
||||
std::string GetAchievementsAsJSON();
|
||||
|
||||
/// Returns true if the current game has any leaderboards.
|
||||
bool HasLeaderboards();
|
||||
|
||||
|
||||
+15
-1
@@ -650,6 +650,10 @@ if(USE_VULKAN)
|
||||
)
|
||||
target_link_libraries(PCSX2_FLAGS INTERFACE vulkan-headers)
|
||||
target_include_directories(PCSX2_FLAGS INTERFACE ${SHADERC_INCLUDE_DIR})
|
||||
if(ANDROID AND CMAKE_ANDROID_ARCH_ABI STREQUAL "arm64-v8a")
|
||||
# VKLoader.cpp's adrenotools_open_libvulkan splice needs adrenotools/driver.h.
|
||||
target_link_libraries(PCSX2_FLAGS INTERFACE adrenotools)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(pcsx2GSMetalShaders
|
||||
@@ -1127,6 +1131,13 @@ if(ANDROID)
|
||||
AndroidEEOpHist.h
|
||||
AndroidPerfBuckets.h
|
||||
PS1DrvTrace.h)
|
||||
# Oboe is the Android audio backend consumed by Host/OboeAudioStream.cpp.
|
||||
# The `oboe` target (created in SearchForStuff.cmake) exports its `include/`
|
||||
# dir as PUBLIC, so linking it into PCSX2_FLAGS propagates <oboe/Oboe.h> to
|
||||
# the PCSX2 translation units. (android/log libs are already linked on the
|
||||
# emucore target in the Android entry CMakeLists, so only oboe is needed here.)
|
||||
target_link_libraries(PCSX2_FLAGS INTERFACE
|
||||
oboe)
|
||||
endif()
|
||||
|
||||
if(ARMSX2_IOS)
|
||||
@@ -1243,6 +1254,7 @@ target_link_libraries(PCSX2_FLAGS INTERFACE
|
||||
ZLIB::ZLIB
|
||||
LZ4::LZ4
|
||||
SoundTouch::SoundTouch
|
||||
JPEG::JPEG
|
||||
PNG::PNG
|
||||
LZMA::LZMA
|
||||
Zstd::Zstd
|
||||
@@ -1276,7 +1288,9 @@ if(WIN32)
|
||||
dwmapi.lib
|
||||
OneCore.lib
|
||||
)
|
||||
else()
|
||||
elseif(NOT ANDROID)
|
||||
# Android has no system libpcap; DEV9 uses Android/AndroidPcapStubs.cpp instead,
|
||||
# so it must NOT link PCAP::PCAP (a bare else() here caught Android and broke it).
|
||||
target_link_libraries(PCSX2_FLAGS INTERFACE
|
||||
PCAP::PCAP
|
||||
)
|
||||
|
||||
@@ -771,6 +771,7 @@ struct Pcsx2Config
|
||||
UseBlitSwapChain : 1,
|
||||
DisableShaderCache : 1,
|
||||
DisableFramebufferFetch : 1,
|
||||
EnableAdrenoFramebufferFetch : 1,
|
||||
DisableVertexShaderExpand : 1,
|
||||
SkipDuplicateFrames : 1,
|
||||
OsdShowSpeed : 1,
|
||||
@@ -938,6 +939,7 @@ struct Pcsx2Config
|
||||
int AudioCaptureBitrate = DEFAULT_AUDIO_CAPTURE_BITRATE;
|
||||
|
||||
std::string Adapter;
|
||||
std::string AndroidGpuProfileOverride = "auto";
|
||||
std::string HWDumpDirectory;
|
||||
std::string SWDumpDirectory;
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <fstream>
|
||||
|
||||
Pcsx2Config::GSOptions GSConfig;
|
||||
@@ -436,6 +437,55 @@ void GSgifTransfer3(u8* mem, u32 size)
|
||||
g_gs_renderer->Transfer<2>(const_cast<u8*>(mem), size);
|
||||
}
|
||||
|
||||
// Manual frameskip target (Android). Set from the UI thread via the JNI
|
||||
// setFrameSkip, read on the GS thread in GSRenderer::VSync. Relaxed atomic — a
|
||||
// stale read at most mis-skips a single frame, which is harmless.
|
||||
static std::atomic<u32> s_manual_frameskip{0};
|
||||
void GSSetManualFrameSkip(u32 frames)
|
||||
{
|
||||
s_manual_frameskip.store(frames, std::memory_order_relaxed);
|
||||
}
|
||||
u32 GSGetManualFrameSkip()
|
||||
{
|
||||
return s_manual_frameskip.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Max presented-FPS cap (Android). Caps the DISPLAY frame rate without slowing
|
||||
// emulation — read on the GS thread in GSRenderer::VSync, which drops a present
|
||||
// only when ahead of the target interval (adaptive, no over-skip). 0 = off.
|
||||
// s_max_present_fps is the cap value (for the OSD label); s_max_present_interval
|
||||
// is the vsync-aligned minimum present spacing in CPU ticks, computed in
|
||||
// native-lib setFpsCap where the native refresh is known, so display rates snap
|
||||
// to whole vsync multiples (60/30/20/15…) and hold steady at the boundary.
|
||||
static std::atomic<u32> s_max_present_fps{0};
|
||||
static std::atomic<u64> s_max_present_interval{0};
|
||||
// Fast-forward (Turbo) bypasses the present cap so the speed-up is visible. Set
|
||||
// from the limiter-mode JNI (Turbo → true, anything else → false) and read on
|
||||
// the GS thread in GSRenderer::VSync. Unlimited (frame-limit-off steady state)
|
||||
// deliberately does NOT set this — there the present cap is still wanted.
|
||||
static std::atomic<bool> s_present_cap_suspended{false};
|
||||
void GSSetMaxPresentFps(u32 fps, u64 present_interval)
|
||||
{
|
||||
s_max_present_fps.store(fps, std::memory_order_relaxed);
|
||||
s_max_present_interval.store(present_interval, std::memory_order_relaxed);
|
||||
}
|
||||
u32 GSGetMaxPresentFps()
|
||||
{
|
||||
return s_max_present_fps.load(std::memory_order_relaxed);
|
||||
}
|
||||
u64 GSGetMaxPresentInterval()
|
||||
{
|
||||
return s_max_present_interval.load(std::memory_order_relaxed);
|
||||
}
|
||||
void GSSetPresentCapSuspended(bool suspended)
|
||||
{
|
||||
s_present_cap_suspended.store(suspended, std::memory_order_relaxed);
|
||||
}
|
||||
bool GSGetPresentCapSuspended()
|
||||
{
|
||||
return s_present_cap_suspended.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void GSvsync(u32 field, bool registers_written)
|
||||
{
|
||||
// Update this here because we need to check if the pending draw affects the current frame, so our regs need to be updated.
|
||||
@@ -994,6 +1044,10 @@ void GSFreeWrappedMemory(void* ptr, size_t size, size_t repeat)
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#if defined(__ANDROID__)
|
||||
#include <sys/syscall.h>
|
||||
#include <android/sharedmem.h>
|
||||
#endif
|
||||
|
||||
static int s_shm_fd = -1;
|
||||
|
||||
@@ -1002,6 +1056,14 @@ void* GSAllocateWrappedMemory(size_t size, size_t repeat)
|
||||
pxAssert(s_shm_fd == -1);
|
||||
|
||||
const char* file_name = "/GS.mem";
|
||||
#if defined(__ANDROID__)
|
||||
s_shm_fd = static_cast<int>(syscall(__NR_memfd_create, "GS.mem", 0));
|
||||
if (s_shm_fd == -1)
|
||||
{
|
||||
fprintf(stderr, "Failed to create memfd due to %s\n", strerror(errno));
|
||||
return nullptr;
|
||||
}
|
||||
#else
|
||||
s_shm_fd = shm_open(file_name, O_RDWR | O_CREAT | O_EXCL, 0600);
|
||||
if (s_shm_fd != -1)
|
||||
{
|
||||
@@ -1012,6 +1074,7 @@ void* GSAllocateWrappedMemory(size_t size, size_t repeat)
|
||||
fprintf(stderr, "Failed to open %s due to %s\n", file_name, strerror(errno));
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ftruncate(s_shm_fd, repeat * size) < 0)
|
||||
fprintf(stderr, "Failed to reserve memory due to %s\n", strerror(errno));
|
||||
|
||||
@@ -71,6 +71,20 @@ void GSgifTransfer1(u8* mem, u32 addr);
|
||||
void GSgifTransfer2(u8* mem, u32 size);
|
||||
void GSgifTransfer3(u8* mem, u32 size);
|
||||
void GSvsync(u32 field, bool registers_written);
|
||||
// Manual frameskip (Android low-end devices): present 1 of every (frames+1)
|
||||
// VSyncs, skipping presentation of the rest. 0 disables. See GSRenderer::VSync.
|
||||
void GSSetManualFrameSkip(u32 frames);
|
||||
u32 GSGetManualFrameSkip();
|
||||
// Max presented-FPS cap (Android). Caps the DISPLAY frame rate without touching
|
||||
// emulation speed — dropped on the GS thread in GSRenderer::VSync. 0 disables.
|
||||
void GSSetMaxPresentFps(u32 fps, u64 present_interval);
|
||||
u32 GSGetMaxPresentFps();
|
||||
u64 GSGetMaxPresentInterval();
|
||||
// While true (set when the limiter enters Turbo / fast-forward), the present cap
|
||||
// above is bypassed so the speed-up is actually visible. The cap resumes — with
|
||||
// a clean re-prime, no catch-up burst — as soon as fast-forward ends.
|
||||
void GSSetPresentCapSuspended(bool suspended);
|
||||
bool GSGetPresentCapSuspended();
|
||||
int GSfreeze(FreezeAction mode, freezeData* data);
|
||||
std::string GSGetBaseSnapshotFilename();
|
||||
std::string GSGetBaseVideoFilename();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "common/WindowInfo.h"
|
||||
#include "GS/GS.h"
|
||||
#include "GS/Renderers/Common/GSFastList.h"
|
||||
#include "GS/Renderers/Common/GSGPUProfile.h"
|
||||
#include "GS/Renderers/Common/GSShaderEnums.h"
|
||||
#include "GS/Renderers/Common/GSTexture.h"
|
||||
#include "GS/Renderers/Common/GSVertex.h"
|
||||
@@ -1444,6 +1445,7 @@ protected:
|
||||
std::string m_name = "Unknown";
|
||||
FeatureSupport m_features;
|
||||
u32 m_max_texture_size = 0;
|
||||
RuntimeGpuProfile m_runtime_gpu_profile = RuntimeGpuProfile::Adreno;
|
||||
|
||||
struct
|
||||
{
|
||||
@@ -1562,6 +1564,11 @@ public:
|
||||
|
||||
__fi FeatureSupport Features() const { return m_features; }
|
||||
__fi u32 GetMaxTextureSize() const { return m_max_texture_size; }
|
||||
__fi void SetRuntimeGPUProfile(RuntimeGpuProfile p) { m_runtime_gpu_profile = p; }
|
||||
__fi RuntimeGpuProfile GetRuntimeGPUProfile() const { return m_runtime_gpu_profile; }
|
||||
__fi bool IsMaliGPUProfile() const { return (m_runtime_gpu_profile == RuntimeGpuProfile::Mali); }
|
||||
__fi bool IsAdrenoGPUProfile() const { return (m_runtime_gpu_profile == RuntimeGpuProfile::Adreno); }
|
||||
__fi bool IsPowerVRGPUProfile() const { return (m_runtime_gpu_profile == RuntimeGpuProfile::PowerVR); }
|
||||
|
||||
__fi const WindowInfo& GetWindowInfo() const { return m_window_info; }
|
||||
__fi s32 GetWindowWidth() const { return static_cast<s32>(m_window_info.surface_width); }
|
||||
|
||||
@@ -125,6 +125,11 @@ bool GLContextEGL::Initialize(std::span<const Version> versions_to_try, Error* e
|
||||
return false;
|
||||
}
|
||||
|
||||
EGLNativeWindowType GLContextEGL::GetNativeWindow(EGLConfig config)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
EGLDisplay GLContextEGL::GetPlatformDisplay(Error* error)
|
||||
{
|
||||
EGLDisplay dpy = TryGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA, "EGL_MESA_platform_surfaceless");
|
||||
|
||||
@@ -31,6 +31,10 @@ public:
|
||||
protected:
|
||||
virtual EGLDisplay GetPlatformDisplay(Error* error);
|
||||
virtual EGLSurface CreatePlatformSurface(EGLConfig config, void* win, Error* error);
|
||||
// Overridden by GLContextEGLAndroid to return the ANativeWindow; base returns
|
||||
// none. (Surface creation on this core sources the window directly; the Android
|
||||
// subclass keeps this override for parity with the known-good EGL path.)
|
||||
virtual EGLNativeWindowType GetNativeWindow(EGLConfig config);
|
||||
|
||||
EGLDisplay TryGetPlatformDisplay(EGLenum platform, const char* platform_ext);
|
||||
EGLSurface TryCreatePlatformSurface(EGLConfig config, void* window, Error* error);
|
||||
|
||||
@@ -312,8 +312,19 @@ namespace
|
||||
|
||||
std::unique_ptr<GLStreamBuffer> GLStreamBuffer::Create(GLenum target, u32 size)
|
||||
{
|
||||
// Adreno 650 (Snapdragon 855/865-era driver) tears out a persistent buffer
|
||||
// mapping on an Android task-switch, so the GS thread's next write SIGSEGVs.
|
||||
// Route ONLY that GPU to the orphan-per-draw glBufferData path below. Every other
|
||||
// GPU -- including newer Adreno 7xx/8xx -- keeps the fast persistent path; a broad
|
||||
// version of this gate over ALL Qualcomm/Adreno was a big GS perf regression, so
|
||||
// this is deliberately narrowed to the single reported model.
|
||||
const char* renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
|
||||
const bool adreno650_no_persistent = renderer &&
|
||||
std::strstr(renderer, "Adreno") && std::strstr(renderer, "650");
|
||||
|
||||
std::unique_ptr<GLStreamBuffer> buf;
|
||||
if (GLAD_GL_VERSION_4_4 || GLAD_GL_ARB_buffer_storage || GLAD_GL_EXT_buffer_storage)
|
||||
if (!adreno650_no_persistent &&
|
||||
(GLAD_GL_VERSION_4_4 || GLAD_GL_ARB_buffer_storage || GLAD_GL_EXT_buffer_storage))
|
||||
{
|
||||
buf = BufferStorageStreamBuffer::Create(target, size);
|
||||
if (buf)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "GS/Renderers/OpenGL/GLContext.h"
|
||||
#include "GS/Renderers/OpenGL/GSDeviceOGL.h"
|
||||
#include "GS/Renderers/OpenGL/GLState.h"
|
||||
#include "GS/Renderers/Common/GSGPUProfile.h"
|
||||
#include "GS/GSState.h"
|
||||
#include "GS/GSGL.h"
|
||||
#include "GS/GSPerfMon.h"
|
||||
@@ -578,7 +579,15 @@ bool GSDeviceOGL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
|
||||
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();
|
||||
// NOTE: CAS uses a desktop-GL compute shader (cas.glsl is "#version 420" +
|
||||
// "#extension GL_ARB_compute_shader") -- that source is invalid GLSL ES. On
|
||||
// devices whose driver only gives a GL ES 3.2 context (e.g. Adreno 650 on the
|
||||
// Retroid Pocket Mini, where the desktop 4.2 context request fails and we fall
|
||||
// back to ES), feeding the ES compiler that shader hard-crashes the driver
|
||||
// during GS init. So gate CAS to a real desktop-GL 4.2 context only; ES devices
|
||||
// just go without the sharpening filter (TV/CRT present shaders are unaffected --
|
||||
// they use the ES-aware "#version 320 es" header and compile fine).
|
||||
m_features.cas_sharpening = (GLAD_GL_VERSION_4_2 && GLAD_GL_ARB_compute_shader) && CreateCASPrograms();
|
||||
|
||||
// ****************************************************************
|
||||
// rasterization configuration
|
||||
@@ -742,6 +751,31 @@ bool GSDeviceOGL::CheckFeatures()
|
||||
Console.WriteLn(Color_StrongBlue, "GL: Intel GPU detected.");
|
||||
//vendor_id_intel = true;
|
||||
}
|
||||
// ARMSX2: resolve the runtime GPU profile (Mali/Adreno/PowerVR) so the tfx GL
|
||||
// shaders can select their tile-based-GPU arms via GPU_PROFILE_* (emitted in
|
||||
// GenGlslHeader below). The monorepo GS device base is a desktop PCSX2 base and
|
||||
// has no Mali/Adreno vendor branch above, so we detect from the raw vendor/renderer
|
||||
// strings here. Guard null pointers (glGetString may return null).
|
||||
{
|
||||
const char* renderer = (const char*)glGetString(GL_RENDERER);
|
||||
const char* vendor_safe = vendor ? vendor : "";
|
||||
const char* renderer_safe = renderer ? renderer : "";
|
||||
#if defined(__ANDROID__)
|
||||
const GpuProfileSelection profile_selection =
|
||||
GpuProfileDetector::Resolve(GSConfig.AndroidGpuProfileOverride, vendor_safe, renderer_safe);
|
||||
SetRuntimeGPUProfile(profile_selection.runtime_profile);
|
||||
Console.WriteLn("GL: GPU profile override='%s' resolved='%s'.",
|
||||
GpuProfileDetector::OverrideToConfigString(profile_selection.override_mode),
|
||||
GpuProfileDetector::RuntimeProfileToString(profile_selection.runtime_profile));
|
||||
DevCon.WriteLn("GL: GPU profile hints: %s", profile_selection.hints.c_str());
|
||||
#else
|
||||
// Desktop/non-Android GL: no tile-based-GPU profile arms are used, keep the
|
||||
// device's profile initialized to a benign default.
|
||||
SetRuntimeGPUProfile(RuntimeGpuProfile::Adreno);
|
||||
(void)vendor_safe;
|
||||
(void)renderer_safe;
|
||||
#endif
|
||||
}
|
||||
|
||||
GLint major_gl = 0;
|
||||
GLint minor_gl = 0;
|
||||
@@ -880,6 +914,28 @@ bool GSDeviceOGL::CheckFeatures()
|
||||
m_features.depth_feedback |= GSConfig.DepthFeedbackMode == GSDepthFeedbackMode::Auto;
|
||||
}
|
||||
|
||||
// ARMSX2 (Adreno GLES only; inert on every other GPU/profile). Adreno's driver
|
||||
// rejects a fragment shader declaring TWO framebuffer-fetch `inout` outputs (o_col0
|
||||
// colour + o_col1 depth), which the depth-as-colour SW-Z path emits for accurate-
|
||||
// alpha-test draws -> link failure -> garbage (Everybody's Golf 4 / Minna no Golf 4).
|
||||
// Route depth feedback through the depth path (a single fetch output) so it links, and
|
||||
// read prior depth via the coherent ARM depth-stencil fetch (gl_LastFragDepthARM) when
|
||||
// available -- the mode-1 depth sampler read is incoherent on GLES (no barrier on a
|
||||
// sampled depth attachment) and makes occluded triangles poke through as white shards.
|
||||
// Only overrides Auto; an explicit DepthFeedbackMode choice is honoured. The GPU
|
||||
// profile is already resolved above (SetRuntimeGPUProfile), so IsAdrenoGPUProfile()
|
||||
// is valid here.
|
||||
if (m_features.framebuffer_fetch && IsAdrenoGPUProfile() &&
|
||||
GSConfig.DepthFeedbackMode == GSDepthFeedbackMode::Auto)
|
||||
{
|
||||
m_features.depth_feedback = true;
|
||||
m_arm_depth_fetch = GLAD_GL_ARM_shader_framebuffer_fetch_depth_stencil;
|
||||
Console.WriteLn(m_arm_depth_fetch
|
||||
? "GL: Adreno - depth feedback via coherent ARM depth-stencil fetch (gl_LastFragDepthARM)."
|
||||
: "GL: Adreno - routing depth feedback through the depth sampler "
|
||||
"(avoids the dual framebuffer-fetch output link failure).");
|
||||
}
|
||||
|
||||
if (GLAD_GL_ARB_shader_storage_buffer_object)
|
||||
{
|
||||
GLint max_vertex_ssbos = 0;
|
||||
@@ -925,7 +981,13 @@ void GSDeviceOGL::SetSwapInterval()
|
||||
m_vsync_mode = (m_vsync_mode == GSVSyncMode::Mailbox) ? GSVSyncMode::FIFO : m_vsync_mode;
|
||||
|
||||
// Window framebuffer has to be bound to call SetSwapInterval.
|
||||
const s32 interval = static_cast<s32>(m_vsync_mode == GSVSyncMode::FIFO);
|
||||
s32 interval = static_cast<s32>(m_vsync_mode == GSVSyncMode::FIFO);
|
||||
// ARM Mali GLES breaks with eglSwapInterval(0): after a handful of swaps the surface
|
||||
// stops presenting entirely (frozen screen). Never pass 0 on Mali — force interval 1.
|
||||
// A forced-on vsync beats a frozen display, and the FIFO present pacer still lets
|
||||
// fast-forward exceed the panel rate via dropped presents. (cf. Dolphin BUG_BROKEN_VSYNC)
|
||||
if (interval == 0 && IsMaliGPUProfile())
|
||||
interval = 1;
|
||||
GLint current_fbo = 0;
|
||||
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
@@ -1570,10 +1632,22 @@ std::string GSDeviceOGL::GenGlslHeader(const std::string_view entry, GLenum type
|
||||
if (m_features.framebuffer_fetch && GLAD_GL_EXT_shader_framebuffer_fetch)
|
||||
header += "#extension GL_EXT_shader_framebuffer_fetch : require\n";
|
||||
|
||||
// ARMSX2 (Adreno): coherent prior-depth read for SW-Z feedback (gl_LastFragDepthARM).
|
||||
if (m_arm_depth_fetch)
|
||||
header += "#extension GL_ARM_shader_framebuffer_fetch_depth_stencil : require\n";
|
||||
|
||||
if (m_features.framebuffer_fetch)
|
||||
header += "#define HAS_FRAMEBUFFER_FETCH 1\n";
|
||||
else
|
||||
header += "#define HAS_FRAMEBUFFER_FETCH 0\n";
|
||||
// ARMSX2: emit the runtime GPU-profile selectors so the tfx GL shaders can pick
|
||||
// their tile-based-GPU (Mali/Adreno/PowerVR) arms. GenGlslHeader() is prepended to
|
||||
// every GL shader (GetShaderSource / GetTfxVertexShader / GetTfxFragmentShader),
|
||||
// so this is the header string the tfx_fs.glsl `#if GPU_PROFILE_MALI` guards read.
|
||||
header += fmt::format("#define GPU_PROFILE_MALI {}\n", IsMaliGPUProfile() ? 1 : 0);
|
||||
header += fmt::format("#define GPU_PROFILE_ADRENO {}\n", IsAdrenoGPUProfile() ? 1 : 0);
|
||||
header += fmt::format("#define GPU_PROFILE_POWERVR {}\n", IsPowerVRGPUProfile() ? 1 : 0);
|
||||
header += fmt::format("#define HAS_ARM_DEPTH_FETCH {}\n", m_arm_depth_fetch ? 1 : 0);
|
||||
|
||||
if (GLAD_GL_ARB_conservative_depth)
|
||||
{
|
||||
@@ -2975,7 +3049,10 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
if (m_features.texture_barrier && (config.require_one_barrier || config.require_full_barrier))
|
||||
PSSetShaderResource(TEXTURE_RT, colclip_rt ? colclip_rt : config.rt);
|
||||
if (m_features.texture_barrier && (config.require_one_barrier || config.require_full_barrier) && config.ps.IsFeedbackLoopDepth())
|
||||
PSSetShaderResource(TEXTURE_DEPTH, m_features.depth_feedback ? config.ds : m_ds_as_rt);
|
||||
// ARMSX2 (Adreno): with ARM depth-stencil fetch the shader reads gl_LastFragDepthARM,
|
||||
// not a sampler, so don't bind the live depth attachment as a texture (avoids a
|
||||
// feedback-loop bind the driver may flag).
|
||||
PSSetShaderResource(TEXTURE_DEPTH, (m_features.depth_feedback && !m_arm_depth_fetch) ? config.ds : m_ds_as_rt);
|
||||
|
||||
SetupSampler(config.sampler);
|
||||
|
||||
|
||||
@@ -158,6 +158,10 @@ private:
|
||||
} m_bugs;
|
||||
|
||||
bool m_disable_download_pbo = false;
|
||||
// ARMSX2 (Adreno GLES): read prior depth for SW-Z feedback via the coherent ARM
|
||||
// depth-stencil fetch (gl_LastFragDepthARM) instead of the incoherent depth sampler,
|
||||
// when GL_ARM_shader_framebuffer_fetch_depth_stencil is present. False everywhere else.
|
||||
bool m_arm_depth_fetch = false;
|
||||
|
||||
GLuint m_fbo = 0; // frame buffer container
|
||||
GLuint m_fbo_read = 0; // frame buffer container only for reading
|
||||
|
||||
@@ -411,8 +411,15 @@ bool GSDeviceVK::SelectDeviceExtensions(ExtensionList* extension_list, bool enab
|
||||
m_optional_extensions.vk_ext_memory_budget = SupportsExtension(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME, false);
|
||||
m_optional_extensions.vk_ext_calibrated_timestamps =
|
||||
SupportsExtension(VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME, false);
|
||||
// ROAA is DUAL-NAMED: ARM shipped VK_ARM_rasterization_order_attachment_access (Mali
|
||||
// driver r36p0), and the promoted VK_EXT_ alias only landed at r40p0. The structs/enums
|
||||
// are identical (alias), so accept EITHER - otherwise Mali on r36-r39 blobs (a big chunk
|
||||
// of mid-tier, incl. Tensor G2/G3 on old blobs) exposes only the ARM name and gets
|
||||
// silently demoted to the per-primitive-barrier slideshow. SupportsExtension enables
|
||||
// whichever name it finds (EXT preferred via short-circuit). Matches upstream 5da4b7e.
|
||||
m_optional_extensions.vk_ext_rasterization_order_attachment_access =
|
||||
SupportsExtension(VK_EXT_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME, false);
|
||||
SupportsExtension(VK_EXT_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME, false) ||
|
||||
SupportsExtension(VK_ARM_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME, false);
|
||||
m_optional_extensions.vk_ext_attachment_feedback_loop_layout =
|
||||
SupportsExtension(VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME, false);
|
||||
m_optional_extensions.vk_ext_line_rasterization = SupportsExtension(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME, false);
|
||||
@@ -2757,7 +2764,40 @@ bool GSDeviceVK::CheckFeatures()
|
||||
//const bool isAMD = (vendorID == 0x1002 || vendorID == 0x1022);
|
||||
//const bool isNVIDIA = (vendorID == 0x10DE);
|
||||
|
||||
m_features.framebuffer_fetch =
|
||||
// framebuffer_fetch: the tiler-native ordered Cd read (ROAA / subpassLoad in tile memory).
|
||||
// It lets DetermineBarriers() drop every per-primitive barrier and makes ROV auto-disable --
|
||||
// the fast, correct path for blend-heavy games on a TBDR.
|
||||
//
|
||||
// MALI (0x13B5): ENABLED by default when ROAA is present -- without it the per-PRIMITIVE
|
||||
// texture-barrier path tanks blend-heavy games (GT4 = 10-20fps slideshow). No-op on any Mali
|
||||
// lacking the extension.
|
||||
//
|
||||
// ADRENO / other non-Mali: OPT-IN only (GSConfig.EnableAdrenoFramebufferFetch, default off).
|
||||
// The proprietary Adreno driver returned STALE ROAA reads above Basic blending (alpha cutouts /
|
||||
// invisible floors, A/B 2026-06-10); gated behind a toggle to ship dark and be A/B-verified per
|
||||
// device+driver. Gated on ROAA presence, so a no-op on any device lacking the extension.
|
||||
// Keep the runtime GPU profile consistent on Vulkan too. The OpenGL device sets this from the
|
||||
// GpuProfileDetector so the shared HW blend path (GSRendererHW.cpp alpha_mali_custom_set) can route
|
||||
// Mali's broken HW dual-source blends through the in-shader SW-blend path; on Vulkan we reach Cd via
|
||||
// texture-barriers instead of GL_ARM_shader_framebuffer_fetch. The GL-shader GPU_PROFILE_* defines are
|
||||
// unused by the VK path, so this only affects that one cross-platform blend workaround.
|
||||
if (IsDeviceMali())
|
||||
SetRuntimeGPUProfile(RuntimeGpuProfile::Mali);
|
||||
else if (IsDevicePowerVR())
|
||||
SetRuntimeGPUProfile(RuntimeGpuProfile::PowerVR);
|
||||
else if (IsDeviceAdreno())
|
||||
SetRuntimeGPUProfile(RuntimeGpuProfile::Adreno);
|
||||
|
||||
const bool is_mali_vk = IsDeviceMali();
|
||||
// Turnip/Mesa is the open Adreno driver and does NOT exhibit the proprietary blob's stale-ROAA
|
||||
// reads (the reason Adreno fbfetch shipped opt-in), so default it ON there.
|
||||
const bool is_turnip = (m_device_driver_properties.driverID == VK_DRIVER_ID_MESA_TURNIP);
|
||||
// Samsung Xclipse (Exynos AMD-RDNA2) has no working ROAA-based framebuffer fetch -- force it off
|
||||
// there. Inert if the 0x144D vendorID guess is wrong.
|
||||
const bool is_xclipse_vk = IsDeviceXclipse();
|
||||
const bool vendor_allows_fbfetch =
|
||||
(is_mali_vk || is_turnip || GSConfig.EnableAdrenoFramebufferFetch) && !is_xclipse_vk;
|
||||
m_features.framebuffer_fetch = vendor_allows_fbfetch &&
|
||||
m_optional_extensions.vk_ext_rasterization_order_attachment_access && !GSConfig.DisableFramebufferFetch;
|
||||
m_features.texture_barrier = GSConfig.OverrideTextureBarriers != 0;
|
||||
m_features.multidraw_fb_copy = false;
|
||||
@@ -2789,6 +2829,18 @@ bool GSDeviceVK::CheckFeatures()
|
||||
|
||||
// Use D32F depth instead of D32S8 when we have framebuffer fetch.
|
||||
m_features.stencil_buffer &= !m_features.framebuffer_fetch;
|
||||
// @@MALI_TELEMETRY@@ One-line device/driver banner so Mali (and Adreno) field reports are
|
||||
// actionable: which GPU/driver, and - critically - which accurate-blend path was resolved:
|
||||
// in-tile framebuffer_fetch (cheap) vs the per-primitive barrier fallback (the tile-flush
|
||||
// slideshow). ROAA=yes but fbfetch=NO means the barrier path is active.
|
||||
Console.WriteLn("VK: GPU '%s' vendor=0x%04X driver='%s' (%s) | ROAA=%s fbfetch=%s texbarrier=%s",
|
||||
m_device_properties.deviceName,
|
||||
m_device_properties.vendorID,
|
||||
m_device_driver_properties.driverName,
|
||||
m_device_driver_properties.driverInfo,
|
||||
m_optional_extensions.vk_ext_rasterization_order_attachment_access ? "yes" : "NO",
|
||||
m_features.framebuffer_fetch ? "yes(in-tile)" : "NO(barrier-fallback)",
|
||||
m_features.texture_barrier ? "on" : "off");
|
||||
|
||||
// whether we can do point/line expand depends on the range of the device
|
||||
const float f_upscale = static_cast<float>(GSConfig.UpscaleMultiplier);
|
||||
|
||||
@@ -81,6 +81,19 @@ public:
|
||||
|
||||
/// Returns true if running on an AMD GPU.
|
||||
__fi bool IsDeviceAMD() const { return (m_device_properties.vendorID == 0x1002); }
|
||||
/// Returns true if running on an ARM Mali GPU (vendorID 0x13B5).
|
||||
__fi bool IsDeviceMali() const { return (m_device_properties.vendorID == 0x13B5u); }
|
||||
|
||||
/// Returns true if running on a Qualcomm Adreno GPU (vendorID 0x5143).
|
||||
__fi bool IsDeviceAdreno() const { return (m_device_properties.vendorID == 0x5143u); }
|
||||
|
||||
/// Returns true if running on an Imagination PowerVR GPU (vendorID 0x1010).
|
||||
__fi bool IsDevicePowerVR() const { return (m_device_properties.vendorID == 0x1010u); }
|
||||
|
||||
/// Returns true if running on a Samsung Xclipse (Exynos AMD-RDNA2) GPU.
|
||||
/// NOTE: 0x144D (Samsung) is unverified across driver revisions -- a real Xclipse tester
|
||||
/// must confirm this fires; if it reports a different vendorID the gate is simply inert.
|
||||
__fi bool IsDeviceXclipse() const { return (m_device_properties.vendorID == 0x144Du); }
|
||||
|
||||
// Creates a simple render pass.
|
||||
VkRenderPass GetRenderPass(VkFormat color_format, VkFormat depth_format,
|
||||
|
||||
@@ -855,7 +855,14 @@ std::unique_ptr<GSDownloadTextureVK> GSDownloadTextureVK::Create(u32 width, u32
|
||||
VmaAllocationCreateInfo aci = {};
|
||||
aci.usage = VMA_MEMORY_USAGE_GPU_TO_CPU;
|
||||
aci.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
||||
aci.preferredFlags = VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
|
||||
// Cached host memory is normally the fastest to read back from on the CPU. On the ARM Mali
|
||||
// Vulkan driver, however, cached readbacks are much slower than coherent memory: mapping a
|
||||
// cached readback buffer spends most of its time inside the kernel cache-invalidation routine
|
||||
// (__pi___inval_cache_range), pegging a CPU core. Prefer coherent memory on Mali so texture
|
||||
// readbacks (GT4, Tales, any hardware-download game) skip that invalidation cost. Every other
|
||||
// vendor keeps the cached preference. (Ports Dolphin BUG_SLOW_CACHED_READBACK_MEMORY.)
|
||||
aci.preferredFlags = GSDeviceVK::GetInstance()->IsDeviceMali() ? VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
|
||||
: VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
|
||||
|
||||
VmaAllocationInfo ai = {};
|
||||
VmaAllocation allocation;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user