1 Commits
8 changed files with 90 additions and 1100 deletions
+1 -6
View File
@@ -28,8 +28,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Android")
app/src/main/cpp/AndroidOut.cpp
app/src/main/cpp/DebugLog.h
app/src/main/cpp/DebugLog.cpp
app/src/main/cpp/CrashHandler.h
app/src/main/cpp/CrashHandler.cpp
vulkan/AppBase.h
vulkan/AppBase.cpp
vulkan/Application.h
@@ -53,10 +51,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Android")
game-activity::game-activity_static
Vulkan::Vulkan
android
log
# dl: needed by CrashHandler::dumpFrame -> dladdr() to map a
# PC back to "module + symbol + offset" in the crash log.
dl)
log)
target_compile_definitions(face_sdk PRIVATE
VMA_STATIC_VULKAN_FUNCTIONS=0
-512
View File
@@ -1,512 +0,0 @@
// CrashHandler.cpp
//
// Async-signal-safe crash dumper for the face_sdk native library. The goal
// is that the next time the app dies (e.g. another FORTIFY: pthread_mutex_lock
// called on a destroyed mutex from inside the Vulkan driver) we don't only
// have logcat — we also have a self-contained dump on the device's app data
// directory that the user can pull off and send back, even after a reboot.
//
// Why we need this:
// - DebugLog only writes "happy path" events. The Vulkan crash happens
// synchronously inside vkQueueSubmit / vkFreeCommandBuffers — there is
// no DebugLog call near the crash site, so the file log just stops.
// - logcat survives across the crash (debuggerd dumps the backtrace there)
// but logcat is volatile: a couple of reboots, a logcat -c, or a long
// idle period and it's gone. We want a persistent file.
// - tombstones in /data/tombstones/ are root-only on consumer devices.
//
// Implementation notes:
// - Inside a signal handler we MUST stick to async-signal-safe APIs
// (man 7 signal-safety). That rules out fprintf / snprintf / malloc.
// We use write(), our own integer-to-string conversion, and a single
// pre-opened fd. dladdr() is technically not on the POSIX safe list but
// bionic's implementation only takes one rwlock and is widely used in
// other crash dumpers (breakpad, crashpad, libunwindstack). We accept
// that risk because the alternative is no symbol at all.
// - We use <unwind.h> (_Unwind_Backtrace) instead of execinfo.h because
// bionic doesn't ship execinfo.h on all NDK levels, and _Unwind_Backtrace
// is the same primitive Android's own tombstoned uses.
// - We re-raise the original signal with the default handler at the end so
// the OS still produces a tombstone / ANR record for vendors that
// read /data/tombstones/.
#include "CrashHandler.h"
#ifndef _WIN32
#include <android/log.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdint.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <unwind.h>
#include <atomic>
namespace {
// ---------- Globals (touched from the handler -> only POD/atomics) ---------
constexpr size_t kCrashStackSize = 64 * 1024; // sigaltstack
constexpr size_t kMaxFrames = 64;
constexpr size_t kNoteCapacity = 256;
uint8_t g_sigStack[kCrashStackSize];
int g_crashFd = -1; // crash log fd (append, sync)
int g_debugFd = -1; // optional: also dup write to debug log
std::atomic<bool> g_installed{false};
std::atomic<bool> g_handlingCrash{false};
// Single writer of g_note: setNote() (uses memcpy under a tiny lock, but the
// handler reads byte-by-byte so a torn read at most produces a truncated
// note, never a deref of bad memory).
char g_note[kNoteCapacity] = {0};
pthread_mutex_t g_noteMtx = PTHREAD_MUTEX_INITIALIZER;
const int g_signals[] = { SIGSEGV, SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSYS };
constexpr size_t kNumSignals = sizeof(g_signals) / sizeof(g_signals[0]);
// ----------------------- Async-signal-safe writers -------------------------
// Write a NUL-terminated string. Drops the trailing NUL.
void sigWrite(int fd, const char* s) {
if (fd < 0 || !s) return;
size_t n = 0;
while (s[n]) ++n;
if (n == 0) return;
// Loop until everything is written or we error out. Async-safe.
while (n > 0) {
ssize_t w = write(fd, s, n);
if (w <= 0) {
if (w < 0 && errno == EINTR) continue;
return;
}
s += w;
n -= (size_t)w;
}
}
// Write n bytes from buf. Used for the note (may contain anything).
void sigWriteN(int fd, const char* buf, size_t n) {
if (fd < 0 || !buf) return;
while (n > 0) {
ssize_t w = write(fd, buf, n);
if (w <= 0) {
if (w < 0 && errno == EINTR) continue;
return;
}
buf += w;
n -= (size_t)w;
}
}
// Write the same string to both the crash log and the debug log (if any).
void sigDump(const char* s) {
sigWrite(g_crashFd, s);
sigWrite(g_debugFd, s);
}
// Convert an unsigned integer to its decimal representation. Returns the
// number of characters written into buf (without a NUL).
size_t u64ToDec(uint64_t v, char* buf, size_t cap) {
if (cap == 0) return 0;
char tmp[32];
size_t i = 0;
if (v == 0) {
tmp[i++] = '0';
} else {
while (v && i < sizeof(tmp)) {
tmp[i++] = (char)('0' + (v % 10));
v /= 10;
}
}
size_t out = (i < cap) ? i : cap;
for (size_t k = 0; k < out; ++k) {
buf[k] = tmp[i - 1 - k];
}
return out;
}
// Convert an unsigned 64-bit value to a fixed-width 16-digit hex string.
// Useful for PC values.
size_t u64ToHex16(uint64_t v, char* buf, size_t cap) {
static const char digits[] = "0123456789abcdef";
if (cap < 16) return 0;
for (int i = 15; i >= 0; --i) {
buf[i] = digits[v & 0xF];
v >>= 4;
}
return 16;
}
// Write "<key>=<u64>\n".
void sigWriteKV_u64(int fd, const char* key, uint64_t v) {
sigWrite(fd, key);
sigWrite(fd, "=");
char num[24];
size_t n = u64ToDec(v, num, sizeof(num));
sigWriteN(fd, num, n);
sigWrite(fd, "\n");
}
// Write "<key>=0x<hex>\n".
void sigWriteKV_ptr(int fd, const char* key, uint64_t v) {
sigWrite(fd, key);
sigWrite(fd, "=0x");
char hx[16];
u64ToHex16(v, hx, sizeof(hx));
sigWriteN(fd, hx, 16);
sigWrite(fd, "\n");
}
// ------------------------- Signal name lookup ------------------------------
const char* signalName(int signo) {
switch (signo) {
case SIGSEGV: return "SIGSEGV";
case SIGABRT: return "SIGABRT";
case SIGBUS: return "SIGBUS";
case SIGFPE: return "SIGFPE";
case SIGILL: return "SIGILL";
case SIGSYS: return "SIGSYS";
case SIGTRAP: return "SIGTRAP";
default: return "SIG?";
}
}
// si_code to short string. Only the most common ones; everything else falls
// back to the numeric value via sigWriteKV_u64.
const char* siCodeName(int signo, int code) {
switch (signo) {
case SIGSEGV:
if (code == SEGV_MAPERR) return "SEGV_MAPERR";
if (code == SEGV_ACCERR) return "SEGV_ACCERR";
break;
case SIGBUS:
if (code == BUS_ADRALN) return "BUS_ADRALN";
if (code == BUS_ADRERR) return "BUS_ADRERR";
if (code == BUS_OBJERR) return "BUS_OBJERR";
break;
case SIGFPE:
if (code == FPE_INTDIV) return "FPE_INTDIV";
if (code == FPE_INTOVF) return "FPE_INTOVF";
if (code == FPE_FLTDIV) return "FPE_FLTDIV";
break;
case SIGILL:
if (code == ILL_ILLOPC) return "ILL_ILLOPC";
if (code == ILL_ILLOPN) return "ILL_ILLOPN";
break;
case SIGABRT:
if (code == SI_TKILL) return "SI_TKILL";
if (code == SI_USER) return "SI_USER";
break;
}
return "?";
}
// ------------------------- Backtrace via _Unwind_Backtrace -----------------
struct UnwindCtx {
uintptr_t* frames;
size_t count;
size_t cap;
};
_Unwind_Reason_Code unwindCallback(_Unwind_Context* ctx, void* arg) {
UnwindCtx* uc = static_cast<UnwindCtx*>(arg);
if (uc->count >= uc->cap) return _URC_END_OF_STACK;
uintptr_t pc = _Unwind_GetIP(ctx);
if (pc) {
// Trim Thumb bit on 32-bit ARM. No-op on aarch64/x86_64.
pc &= ~(uintptr_t)1;
uc->frames[uc->count++] = pc;
}
return _URC_NO_REASON;
}
size_t captureBacktrace(uintptr_t* out, size_t cap) {
UnwindCtx uc{out, 0, cap};
_Unwind_Backtrace(&unwindCallback, &uc);
return uc.count;
}
// Dump one frame: " #02 pc 000000000000abcd /path/lib.so (Symbol+0x10)"
void dumpFrame(int fd, size_t idx, uintptr_t pc) {
sigWrite(fd, " #");
char num[8];
if (idx < 10) {
num[0] = '0';
num[1] = (char)('0' + idx);
sigWriteN(fd, num, 2);
} else {
size_t n = u64ToDec(idx, num, sizeof(num));
sigWriteN(fd, num, n);
}
sigWrite(fd, " pc ");
char hx[16];
u64ToHex16((uint64_t)pc, hx, sizeof(hx));
sigWriteN(fd, hx, 16);
Dl_info info;
memset(&info, 0, sizeof(info));
if (dladdr(reinterpret_cast<void*>(pc), &info) && info.dli_fname) {
sigWrite(fd, " ");
sigWrite(fd, info.dli_fname);
if (info.dli_sname) {
uintptr_t sym = reinterpret_cast<uintptr_t>(info.dli_saddr);
uintptr_t off = (sym && pc >= sym) ? (pc - sym) : 0;
sigWrite(fd, " (");
sigWrite(fd, info.dli_sname);
sigWrite(fd, "+0x");
char ohx[16];
u64ToHex16((uint64_t)off, ohx, sizeof(ohx));
sigWriteN(fd, ohx, 16);
sigWrite(fd, ")");
} else if (info.dli_fbase) {
uintptr_t base = reinterpret_cast<uintptr_t>(info.dli_fbase);
uintptr_t off = (pc >= base) ? (pc - base) : 0;
sigWrite(fd, " (offset 0x");
char ohx[16];
u64ToHex16((uint64_t)off, ohx, sizeof(ohx));
sigWriteN(fd, ohx, 16);
sigWrite(fd, ")");
}
}
sigWrite(fd, "\n");
}
// ------------------------- Time + tid helpers ------------------------------
// Builds "YYYY-MM-DD HH:MM:SS.mmm UTC" into the given buffer (no NUL).
// Returns the number of bytes written. Async-signal-safe (no stdio, no
// localtime_r tz lookups).
size_t formatTimestamp(char* b, size_t cap) {
timespec ts{};
clock_gettime(CLOCK_REALTIME, &ts);
struct tm tm_info{};
time_t s = ts.tv_sec;
gmtime_r(&s, &tm_info);
size_t i = 0;
auto putUInt = [&](unsigned v, int width) {
char tmp[8];
size_t n = u64ToDec(v, tmp, sizeof(tmp));
while ((int)n < (size_t)width) {
if (i < cap) b[i++] = '0';
++n;
}
for (size_t k = 0; k < n; ++k) {
if (i < cap) b[i++] = tmp[k];
}
};
putUInt((unsigned)(tm_info.tm_year + 1900), 4); if (i < cap) b[i++] = '-';
putUInt((unsigned)(tm_info.tm_mon + 1), 2); if (i < cap) b[i++] = '-';
putUInt((unsigned)(tm_info.tm_mday), 2); if (i < cap) b[i++] = ' ';
putUInt((unsigned)(tm_info.tm_hour), 2); if (i < cap) b[i++] = ':';
putUInt((unsigned)(tm_info.tm_min), 2); if (i < cap) b[i++] = ':';
putUInt((unsigned)(tm_info.tm_sec), 2); if (i < cap) b[i++] = '.';
putUInt((unsigned)(ts.tv_nsec / 1000000), 3);
static const char kSuffix[] = " UTC";
for (size_t k = 0; k < sizeof(kSuffix) - 1; ++k) {
if (i < cap) b[i++] = kSuffix[k];
}
return i;
}
pid_t currentTid() {
return static_cast<pid_t>(syscall(SYS_gettid));
}
// ------------------------- Signal handler ----------------------------------
void crashHandler(int signo, siginfo_t* info, void* ucontext) {
(void)ucontext;
// Re-entry guard: if a second signal fires while we're dumping (e.g. our
// own dladdr trips a SIGSEGV) just chain to the default handler.
bool expected = false;
if (!g_handlingCrash.compare_exchange_strong(expected, true,
std::memory_order_acq_rel)) {
// Already in handler -> default + bail.
signal(signo, SIG_DFL);
raise(signo);
return;
}
sigDump("\n========== FACE_SDK CRASH ==========\n");
sigDump("time=");
{
char tsbuf[48];
size_t tn = formatTimestamp(tsbuf, sizeof(tsbuf));
sigWriteN(g_crashFd, tsbuf, tn);
sigWriteN(g_debugFd, tsbuf, tn);
}
sigDump("\n");
sigDump("signal=");
sigDump(signalName(signo));
sigDump(" code=");
sigDump(siCodeName(signo, info ? info->si_code : 0));
sigDump("\n");
if (info) {
sigWriteKV_u64(g_crashFd, "si_signo", (uint64_t)info->si_signo);
sigWriteKV_u64(g_debugFd, "si_signo", (uint64_t)info->si_signo);
sigWriteKV_u64(g_crashFd, "si_code", (uint64_t)info->si_code);
sigWriteKV_u64(g_debugFd, "si_code", (uint64_t)info->si_code);
sigWriteKV_ptr(g_crashFd, "si_addr", (uint64_t)(uintptr_t)info->si_addr);
sigWriteKV_ptr(g_debugFd, "si_addr", (uint64_t)(uintptr_t)info->si_addr);
}
sigWriteKV_u64(g_crashFd, "pid", (uint64_t)getpid());
sigWriteKV_u64(g_debugFd, "pid", (uint64_t)getpid());
sigWriteKV_u64(g_crashFd, "tid", (uint64_t)currentTid());
sigWriteKV_u64(g_debugFd, "tid", (uint64_t)currentTid());
// Note (current frame index, current motion, ...).
sigDump("note=");
sigWriteN(g_crashFd, g_note, strnlen(g_note, kNoteCapacity));
sigWriteN(g_debugFd, g_note, strnlen(g_note, kNoteCapacity));
sigDump("\n");
sigDump("backtrace:\n");
uintptr_t frames[kMaxFrames];
size_t nf = captureBacktrace(frames, kMaxFrames);
for (size_t i = 0; i < nf; ++i) {
dumpFrame(g_crashFd, i, frames[i]);
dumpFrame(g_debugFd, i, frames[i]);
}
sigDump("==================================\n");
// Make sure everything reaches disk before we self-destruct.
if (g_crashFd >= 0) fsync(g_crashFd);
if (g_debugFd >= 0) fsync(g_debugFd);
// Mirror to logcat too so a quick `adb logcat -d` after a reboot still
// shows the SIGNAL line (helps cross-checking the file).
__android_log_print(ANDROID_LOG_FATAL, "FACE_DBG_CRASH",
"fatal %s @ tid=%d, see face_sdk_crash.log",
signalName(signo), currentTid());
// Restore default handler and re-raise. This produces /data/tombstones/*
// on rooted devices and tells debuggerd to print the official Android
// backtrace into logcat (`crash_dump64 ... DEBUG`).
struct sigaction dfl{};
dfl.sa_handler = SIG_DFL;
sigemptyset(&dfl.sa_mask);
sigaction(signo, &dfl, nullptr);
raise(signo);
}
} // namespace
namespace CrashHandler {
void install(const std::string& internalDataPath,
const std::string& debugLogPath) {
bool expected = false;
if (!g_installed.compare_exchange_strong(expected, true,
std::memory_order_acq_rel)) {
return; // already installed
}
// Open the persistent crash log file in append mode. We keep it open
// forever so the signal handler doesn't have to call open() (which is
// safe but slow).
{
std::string path = internalDataPath.empty()
? std::string("/data/local/tmp/face_sdk_crash.log")
: internalDataPath + "/face_sdk_crash.log";
g_crashFd = open(path.c_str(),
O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC,
0644);
if (g_crashFd >= 0) {
// Header for the new run.
sigWrite(g_crashFd, "\n=== CrashHandler installed pid=");
char pidbuf[16];
size_t n = u64ToDec((uint64_t)getpid(), pidbuf, sizeof(pidbuf));
sigWriteN(g_crashFd, pidbuf, n);
sigWrite(g_crashFd, " ===\n");
fsync(g_crashFd);
__android_log_print(ANDROID_LOG_INFO, "FACE_DBG",
"CrashHandler log file: %s", path.c_str());
} else {
__android_log_print(ANDROID_LOG_WARN, "FACE_DBG",
"CrashHandler failed to open %s: %s",
path.c_str(), strerror(errno));
}
}
// Also keep a writable fd to the DebugLog file (if any) so dumps land
// next to the regular tail of the log. We don't touch g_fp inside
// DebugLog because that would need its mutex — not safe in handler.
if (!debugLogPath.empty()) {
g_debugFd = open(debugLogPath.c_str(),
O_WRONLY | O_APPEND | O_CLOEXEC,
0644);
if (g_debugFd < 0) {
__android_log_print(ANDROID_LOG_WARN, "FACE_DBG",
"CrashHandler: cannot open debug log %s: %s",
debugLogPath.c_str(), strerror(errno));
}
}
// Set up an alternate stack so we still have stack space if the original
// thread ran out (very common for Vulkan crashes inside deep driver
// call chains).
stack_t ss{};
ss.ss_sp = g_sigStack;
ss.ss_size = sizeof(g_sigStack);
ss.ss_flags = 0;
if (sigaltstack(&ss, nullptr) != 0) {
__android_log_print(ANDROID_LOG_WARN, "FACE_DBG",
"CrashHandler: sigaltstack failed: %s",
strerror(errno));
}
struct sigaction sa{};
sa.sa_sigaction = &crashHandler;
sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
sigemptyset(&sa.sa_mask);
for (size_t i = 0; i < kNumSignals; ++i) {
if (sigaction(g_signals[i], &sa, nullptr) != 0) {
__android_log_print(ANDROID_LOG_WARN, "FACE_DBG",
"CrashHandler: sigaction(%d) failed: %s",
g_signals[i], strerror(errno));
}
}
__android_log_print(ANDROID_LOG_INFO, "FACE_DBG",
"CrashHandler installed for SIGSEGV/SIGABRT/SIGBUS/SIGFPE/SIGILL/SIGSYS");
}
void setNote(const char* note) {
if (!note) note = "";
pthread_mutex_lock(&g_noteMtx);
size_t n = strnlen(note, kNoteCapacity - 1);
memcpy(g_note, note, n);
g_note[n] = '\0';
pthread_mutex_unlock(&g_noteMtx);
}
} // namespace CrashHandler
#else // _WIN32 — no-op on host build
namespace CrashHandler {
void install(const std::string&, const std::string&) {}
void setNote(const char*) {}
} // namespace CrashHandler
#endif
-33
View File
@@ -1,33 +0,0 @@
#ifndef FACE_SDK_CRASH_HANDLER_H
#define FACE_SDK_CRASH_HANDLER_H
#include <string>
namespace CrashHandler {
// Install signal handlers for SIGSEGV / SIGABRT / SIGBUS / SIGFPE / SIGILL.
//
// On a fatal signal we:
// 1) Switch to a pre-allocated 64KB sigaltstack so we still have stack
// space even if the original thread's stack was the cause.
// 2) Write a self-contained crash record to <internalDataPath>/face_sdk_crash.log
// (and also try to append it to the DebugLog file passed in).
// The record contains: signal name, si_code, si_addr, pid, tid,
// a synchronous unwound backtrace (PC + module + offset for each frame),
// and the value of any extra context the caller registered via setNote().
// 3) Re-raise the original signal with the default handler so that the
// Android tombstone pipeline still produces /data/tombstones/* files.
//
// Safe to call once. Subsequent calls are no-ops. Must be called AFTER
// DebugLog::init() so we know the log directory.
void install(const std::string& internalDataPath,
const std::string& debugLogPath = "");
// Optional short note (<= 256 chars) appended to every crash dump from now on.
// Useful to record "current frame index", "current motion", etc. so we know
// what was happening at the moment of the crash. Async-signal-safe to read.
void setNote(const char* note);
} // namespace CrashHandler
#endif // FACE_SDK_CRASH_HANDLER_H
+1 -56
View File
@@ -4,14 +4,12 @@
#include <game-activity/GameActivity.h>
#include "AndroidOut.h"
#include "DebugLog.h"
#include "CrashHandler.h"
#include "FaceApp.h"
#include <android/asset_manager.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <atomic>
#include <chrono>
#include <cstdio>
#include <thread>
using namespace std;
@@ -56,13 +54,9 @@ void handle_cmd(android_app *pApp, int32_t cmd) {
case APP_CMD_INIT_WINDOW:
aout << "APP_CMD_INIT_WINDOW" << std::endl;
DebugLog::log("handle_cmd APP_CMD_INIT_WINDOW: calling onWindowInit()");
if (g_Application != nullptr) {
g_Application->onWindowInit();
DebugLog::log("handle_cmd APP_CMD_INIT_WINDOW: onWindowInit() returned, isInited=%d",
(int)g_Application->isInited());
} else {
DebugLog::log("handle_cmd APP_CMD_INIT_WINDOW: g_Application is null, skipping");
}
break;
case APP_CMD_TERM_WINDOW:
aout << "APP_CMD_TERM_WINDOW" << std::endl;
@@ -83,15 +77,6 @@ char g_InitArgString[ArgLen] = {0};
void android_main(struct android_app *pApp) {
DebugLog::init(pApp->activity->internalDataPath);
// 安装信号处理器越早越好:之前的 FORTIFY: pthread_mutex_lock 崩溃
// 是裸的 SIGABRT,没有任何 signal handler,所以 DebugLog 文件停在最后
// 一条业务日志、看不到任何崩溃栈。装上之后任何 fatal signal 都会把:
// * 信号名 / si_code / si_addr / pid / tid / 当前 note
// * 完整 backtracePC + 模块 + 符号 + 偏移)
// 同步落到 internalDataPath/face_sdk_crash.log(以及 DebugLog 文件尾),
// 然后再走默认 handler 让 debuggerd 产生 tombstone。
CrashHandler::install(pApp->activity->internalDataPath,
DebugLog::getLogPath());
DebugLog::log("android_main start, pid=%d tid=%d, logPath=%s",
getpid(), dbg_tid(), DebugLog::getLogPath().c_str());
aout << "Welcome to android_main" << std::endl;
@@ -152,16 +137,6 @@ void android_main(struct android_app *pApp) {
(unsigned long long)s_frameIdx,
(unsigned long long)s_excCount);
}
// 每 60 帧刷新一次崩溃 note,崩溃时 dump 里能看到「最后一次活着」
// 的帧号和异常数,定位是不是在某一个固定帧附近卡死。
if (s_frameIdx % 60 == 0) {
char note[128];
std::snprintf(note, sizeof(note),
"drawFrame frame=%llu exc=%llu",
(unsigned long long)s_frameIdx,
(unsigned long long)s_excCount);
CrashHandler::setNote(note);
}
try {
g_Application->drawFrame(frameTime);
@@ -191,38 +166,8 @@ void android_main(struct android_app *pApp) {
}
} while (!pApp->destroyRequested);
DebugLog::log("android_main: destroyRequested, running cleanup");
// ⚠️ 不要在这里调 cleanupSecondInit()。
//
// 在 Android 上,Activity 销毁(APP_CMD_DESTROY) 之前已经先收到了
// APP_CMD_TERM_WINDOWhandle_cmd 里走的是 onWindowLost()
// → Application::cleanupForWindowLost():销毁 swapChain / surface /
// framebuffers / imageViews / commandBuffers / 同步对象,但故意保留
// renderPass / pipelineLayout / graphicsPipeline,以便下一次进入
// Activity 时 reinitForNewWindow() 在 extent/format 不变的情况下
// 直接复用,省下重建 RenderPass / 重新编译 Pipeline 的开销。
//
// 而 cleanupSecondInit() 是 Windows 路径
// (mainLoop → cleanupSecondInit → initVulkan → mainLoop) 的对偶——
// 它会把 renderPass / pipelineLayout / graphicsPipeline 全部销毁。
// 又因为 g_Application 是一个跨 Activity 存活的全局 FaceApp 实例
// (android_main 入口处只在 g_Application == nullptr 时才 new)
// _applicationInited 也不会被复位;
// 下一次同一进程内进入另一个继承自 FaceActivity 的 Activity 时,
// onWindowInit() 看到 _applicationInited=1 走恢复分支调用
// reinitForNewWindow()extent/format 与上次相同(480x480 / 同 format)
// 时**不会**重建 renderPass / pipelineLayout / graphicsPipeline——
// 用着已经被销毁的 renderPass 句柄继续 createFramebuffers() →
// recordCommandBuffer() → vkCmdBeginRenderPass()
// Mali 驱动 (libGLES_mali) 在校验层后解引用已释放的 RenderPass 内部
// 数据,触发 SIGSEGV / si_addr=0x4
// (recordCommandBuffer + 0xBC,正好是 vkCmdBeginRenderPass)。
//
// 解决办法:android_main 退出时只信任 cleanupForWindowLost 已经做的
// 那部分清理,不再额外调 cleanupSecondInit(),让 renderPass /
// pipelineLayout / graphicsPipeline / FaceApp 的 m_graphicsPipeline*
// 都能存活到下一次 android_main 重新进来被复用。
//application.cleanup();
//g_Application->cleanupSecondInit();
g_Application->cleanupSecondInit();
g_Application->clearnSecondFaceApp();
DebugLog::log("android_main: exit");
}
+16 -307
View File
@@ -110,19 +110,11 @@ void Application::initVulkan()
createCommandPool(); // 创建命令池
createCommandBuffer(); // 创建命令缓冲区
createSyncObjects(); // 创建同步对象
// 复用式 single-time-command 资源:从 commandPool_ex 预分配
// kTransferSlotCount 个 cmdbuf + 同数 signaled fence
// 之后所有 copyBuffer / updateTexture 走 runTransferCommand
// 不再每次 vkAllocate/vkFree。
createTransferResources();
_lastDrawFrameTime = getCurrentTimeMillis();
_applicationInited = true;
// The first branch above already built all of the window-dependent
// objects. Mark _sceondInited so we don't fall into the recovery
// branch below and leak a second copy of surface/swapChain/etc.
_sceondInited = true;
}
else if (!_sceondInited) {
if (!_sceondInited) {
createSurface();
createSwapChain();
createImageViews();
@@ -136,55 +128,14 @@ void Application::initVulkan()
void Application::cleanupSecondInit()
{
// ★ 幂等:销毁后立刻把句柄置 VK_NULL_HANDLE / 清空 vector。
//
// 这个函数过去在 Android 路径 (android_main 退出) 上被错误地调用过;
// 由于销毁后没有清零句柄,紧接着重新进入的 reinitForNewWindow() 在
// extent/format 不变时会跳过重建,让 framebuffer / drawFrame 在
// 已经销毁的 renderPass 上继续工作,最终在
// vkCmdBeginRenderPass 内部 (libGLES_mali) 触发 SIGSEGV。
//
// 现在 android_main 退出路径已经不再调本函数;本函数只剩 Windows
// 路径 (mainLoop → cleanupSecondInit → initVulkan 的"二次 session"
// 复用) 在用。把它写成幂等的可以兜住将来任何对它的误用。
FACE_DBG_LOG("cleanupSecondInit: begin (swapChain=%s surface=%s renderPass=%s pipelineLayout=%s graphicsPipeline=%s imageViews=%zu framebuffers=%zu)",
swapChain == VK_NULL_HANDLE ? "null" : "live",
surface == VK_NULL_HANDLE ? "null" : "live",
renderPass == VK_NULL_HANDLE ? "null" : "live",
pipelineLayout == VK_NULL_HANDLE ? "null" : "live",
graphicsPipeline == VK_NULL_HANDLE ? "null" : "live",
swapChainImageViews.size(), swapChainFramebuffers.size());
if (device != VK_NULL_HANDLE) {
vkDeviceWaitIdle(device);
}
if (swapChain != VK_NULL_HANDLE) {
vkDestroySwapchainKHR(device, swapChain, nullptr);
swapChain = VK_NULL_HANDLE;
}
if (surface != VK_NULL_HANDLE) {
vkDestroySurfaceKHR(instance, surface, nullptr);
surface = VK_NULL_HANDLE;
}
if (graphicsPipeline != VK_NULL_HANDLE) {
vkDestroyPipeline(device, graphicsPipeline, nullptr);
graphicsPipeline = VK_NULL_HANDLE;
}
if (pipelineLayout != VK_NULL_HANDLE) {
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
pipelineLayout = VK_NULL_HANDLE;
}
if (renderPass != VK_NULL_HANDLE) {
vkDestroyRenderPass(device, renderPass, nullptr);
renderPass = VK_NULL_HANDLE;
}
for (auto imageView : swapChainImageViews) {
if (imageView != VK_NULL_HANDLE) {
vkDestroyImageView(device, imageView, nullptr);
}
}
swapChainImageViews.clear();
for (auto framebuffer : swapChainFramebuffers)
{
if (framebuffer != VK_NULL_HANDLE)
@@ -193,7 +144,6 @@ void Application::cleanupSecondInit()
}
}
swapChainFramebuffers.clear();
swapChainImages.clear();
_sceondInited = false;
}
@@ -205,14 +155,8 @@ void Application::cleanupForWindowLost()
(int)_applicationInited, (int)_sceondInited);
return;
}
FACE_DBG_LOG("cleanupForWindowLost: begin (imageCount=%zu MAX_FRAMES_IN_FLIGHT=%d extent=%ux%u format=%d)",
swapChainImages.size(), MAX_FRAMES_IN_FLIGHT,
swapChainExtent.width, swapChainExtent.height, (int)swapChainImageFormat);
// Snapshot before we destroy the swapchain so reinitForNewWindow() can
// decide whether the new swapchain needs renderPass / pipelines rebuilt.
_prevSwapChainExtent = swapChainExtent;
_prevSwapChainImageFormat = swapChainImageFormat;
FACE_DBG_LOG("cleanupForWindowLost: begin (imageCount=%zu MAX_FRAMES_IN_FLIGHT=%d)",
swapChainImages.size(), MAX_FRAMES_IN_FLIGHT);
// Block until GPU is no longer using any of the resources we are about
// to destroy. Anything weaker than this risks hitting VK_ERROR_DEVICE_LOST
@@ -278,73 +222,29 @@ void Application::cleanupForWindowLost()
FACE_DBG_LOG("cleanupForWindowLost: done");
}
bool Application::reinitForNewWindow()
void Application::reinitForNewWindow()
{
if (!_applicationInited) {
FACE_DBG_LOG("reinitForNewWindow: skip, _applicationInited=0 (must go through initVulkan first)");
return false;
return;
}
if (_sceondInited) {
FACE_DBG_LOG("reinitForNewWindow: skip, already _sceondInited=1");
return false;
return;
}
FACE_DBG_LOG("reinitForNewWindow: begin (prev extent=%ux%u format=%d)",
_prevSwapChainExtent.width, _prevSwapChainExtent.height,
(int)_prevSwapChainImageFormat);
FACE_DBG_LOG("reinitForNewWindow: begin");
createSurface();
createSwapChain();
const bool extentChanged = (swapChainExtent.width != _prevSwapChainExtent.width) ||
(swapChainExtent.height != _prevSwapChainExtent.height);
const bool formatChanged = (swapChainImageFormat != _prevSwapChainImageFormat);
const bool swapchainIncompatible = extentChanged || formatChanged;
if (formatChanged) {
// renderPass bakes in swapChainImageFormat, so it must be rebuilt
// when the surface chose a different format. The old Application
// graphicsPipeline is tied to the old renderPass, so destroy it
// first to make the handle invalidation explicit.
FACE_DBG_LOG("reinitForNewWindow: format changed (%d -> %d), rebuilding renderPass",
(int)_prevSwapChainImageFormat, (int)swapChainImageFormat);
if (graphicsPipeline != VK_NULL_HANDLE) {
vkDestroyPipeline(device, graphicsPipeline, nullptr);
graphicsPipeline = VK_NULL_HANDLE;
}
if (pipelineLayout != VK_NULL_HANDLE) {
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
pipelineLayout = VK_NULL_HANDLE;
}
if (renderPass != VK_NULL_HANDLE) {
vkDestroyRenderPass(device, renderPass, nullptr);
renderPass = VK_NULL_HANDLE;
}
createRenderPass();
createPipelineLayout();
createGraphicsPipeline();
} else if (extentChanged) {
// renderPass is still fine, but the Application pipeline has a static
// viewport baked to the old extent and must be rebuilt.
FACE_DBG_LOG("reinitForNewWindow: extent changed (%ux%u -> %ux%u), rebuilding graphicsPipeline",
_prevSwapChainExtent.width, _prevSwapChainExtent.height,
swapChainExtent.width, swapChainExtent.height);
if (graphicsPipeline != VK_NULL_HANDLE) {
vkDestroyPipeline(device, graphicsPipeline, nullptr);
graphicsPipeline = VK_NULL_HANDLE;
}
createGraphicsPipeline();
}
createImageViews();
createFramebuffers();
createCommandBuffer();
createSyncObjects();
_sceondInited = true;
FACE_DBG_LOG("reinitForNewWindow: done (imageCount=%zu extent=%ux%u format=%d MAX_FRAMES_IN_FLIGHT=%d swapchainIncompatible=%d)",
FACE_DBG_LOG("reinitForNewWindow: done (imageCount=%zu extent=%ux%u MAX_FRAMES_IN_FLIGHT=%d)",
swapChainImages.size(), swapChainExtent.width, swapChainExtent.height,
(int)swapChainImageFormat, MAX_FRAMES_IN_FLIGHT, (int)swapchainIncompatible);
return swapchainIncompatible;
MAX_FRAMES_IN_FLIGHT);
}
@@ -837,14 +737,7 @@ void Application::drawFrame(long long frameTime)
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
// 串行化 graphicsQueue/presentQueue 的 submit 与 present
// 确保与 copyBuffer / updateTexture 等其它线程的队列提交互斥,
// 避免驱动内部 pthread_mutex 在长时间并发下被破坏。
VkResult submitRes;
{
std::lock_guard<std::mutex> poolLock(poolQueueMtx);
submitRes = vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]);
}
VkResult submitRes = vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]);
if (submitRes != VK_SUCCESS) {
#ifndef _WIN32
DebugLog::log_throttled("drawFrame.submit",
@@ -864,10 +757,7 @@ void Application::drawFrame(long long frameTime)
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;
{
std::lock_guard<std::mutex> poolLock(poolQueueMtx);
result = vkQueuePresentKHR(presentQueue, &presentInfo);
}
if (result != VK_SUCCESS) {
#ifndef _WIN32
@@ -1178,184 +1068,6 @@ void Application::endSingleTimeCommands(VkDevice device, VkCommandPool commandPo
vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
}
void Application::createTransferResources()
{
if (m_xferInited) {
#ifndef _WIN32
DebugLog::log("createTransferResources: already inited, skip");
#endif
return;
}
if (commandPool_ex == VK_NULL_HANDLE) {
#ifndef _WIN32
DebugLog::log("createTransferResources: commandPool_ex is null, abort");
#endif
throw std::runtime_error("createTransferResources: commandPool_ex not created yet");
}
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = commandPool_ex;
allocInfo.commandBufferCount = kTransferSlotCount;
if (vkAllocateCommandBuffers(device, &allocInfo, m_xferCmd) != VK_SUCCESS) {
throw std::runtime_error("createTransferResources: vkAllocateCommandBuffers failed");
}
// fence 创建为 SIGNALED:第一次 runTransferCommand 的 vkWaitForFences
// 会立刻返回,避免冷启动卡顿。
VkFenceCreateInfo fenceInfo{};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
for (uint32_t i = 0; i < kTransferSlotCount; ++i) {
if (vkCreateFence(device, &fenceInfo, nullptr, &m_xferFence[i]) != VK_SUCCESS) {
for (uint32_t j = 0; j < i; ++j) {
vkDestroyFence(device, m_xferFence[j], nullptr);
m_xferFence[j] = VK_NULL_HANDLE;
}
vkFreeCommandBuffers(device, commandPool_ex, kTransferSlotCount, m_xferCmd);
for (uint32_t j = 0; j < kTransferSlotCount; ++j) m_xferCmd[j] = VK_NULL_HANDLE;
throw std::runtime_error("createTransferResources: vkCreateFence failed");
}
}
m_xferIdx = 0;
m_xferInited = true;
#ifndef _WIN32
DebugLog::log("createTransferResources: done, slots=%u pool=commandPool_ex",
(unsigned)kTransferSlotCount);
#endif
}
void Application::destroyTransferResources()
{
if (!m_xferInited) {
return;
}
// 调用方必须保证 GPU 已 idle 且没有线程正在 runTransferCommand。
// 这里再加一道 m_xferMtx,串行化潜在的最后一次 transfer。
std::lock_guard<std::mutex> xferLock(m_xferMtx);
for (uint32_t i = 0; i < kTransferSlotCount; ++i) {
if (m_xferFence[i] != VK_NULL_HANDLE) {
vkDestroyFence(device, m_xferFence[i], nullptr);
m_xferFence[i] = VK_NULL_HANDLE;
}
}
if (m_xferCmd[0] != VK_NULL_HANDLE && commandPool_ex != VK_NULL_HANDLE) {
vkFreeCommandBuffers(device, commandPool_ex, kTransferSlotCount, m_xferCmd);
}
for (uint32_t i = 0; i < kTransferSlotCount; ++i) {
m_xferCmd[i] = VK_NULL_HANDLE;
}
m_xferIdx = 0;
m_xferInited = false;
#ifndef _WIN32
DebugLog::log("destroyTransferResources: done");
#endif
}
void Application::runTransferCommand(const std::function<void(VkCommandBuffer)>& record)
{
if (!m_xferInited) {
#ifndef _WIN32
DebugLog::log_throttled("runTransferCommand.notInited",
"runTransferCommand: not inited, skip");
#endif
return;
}
// 串行化所有 transfer 调用:
// - 保证 m_xferIdx 推进 / m_xferCmd[idx] 录制 / m_xferFence[idx] 等待
// 形成一组原子动作;
// - 同时也保证 commandPool_ex 的「外部同步」语义(同一时刻只允许一个
// 线程对它做 record/reset)。
std::lock_guard<std::mutex> xferLock(m_xferMtx);
const uint32_t idx = m_xferIdx;
VkFence fence = m_xferFence[idx];
VkCommandBuffer cmd = m_xferCmd[idx];
// 等上一次该 slot 的提交真正完成。fence 是独立同步对象,不需要持
// poolQueueMtx 就可以等待,drawFrame 的 submit 不会被阻塞。
VkResult wr = vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
if (wr != VK_SUCCESS) {
#ifndef _WIN32
DebugLog::log_throttled("runTransferCommand.wait",
"runTransferCommand: vkWaitForFences slot=%u -> %d",
idx, (int)wr);
#endif
}
vkResetFences(device, 1, &fence);
vkResetCommandBuffer(cmd, 0);
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
if (vkBeginCommandBuffer(cmd, &beginInfo) != VK_SUCCESS) {
#ifndef _WIN32
DebugLog::log_throttled("runTransferCommand.begin",
"runTransferCommand: vkBeginCommandBuffer slot=%u failed", idx);
#endif
return;
}
// 调用方在这里 record 命令:vkCmdCopyBuffer / vkCmdCopyBufferToImage /
// transitionImageLayout 等。这些是纯 record 操作,在 m_xferMtx 持有
// 且 cmd 独占的前提下不需要再额外加锁。
record(cmd);
if (vkEndCommandBuffer(cmd) != VK_SUCCESS) {
#ifndef _WIN32
DebugLog::log_throttled("runTransferCommand.end",
"runTransferCommand: vkEndCommandBuffer slot=%u failed", idx);
#endif
return;
}
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &cmd;
// vkQueueSubmit 必须和 drawFrame 的 vkQueueSubmit / vkQueuePresentKHR
// 互斥(队列要求外部同步)。其它步骤只占 m_xferMtx 即可。
{
std::lock_guard<std::mutex> poolLock(poolQueueMtx);
VkResult sr = vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence);
if (sr != VK_SUCCESS) {
#ifndef _WIN32
DebugLog::log_throttled("runTransferCommand.submit",
"runTransferCommand: vkQueueSubmit slot=%u -> %d",
idx, (int)sr);
#endif
}
}
// ★ 关键:调用方代码(FaceApp::uploadVertexData / Application::updateTexture
// 在 runTransferCommand 之后会立刻 vkMapMemory + memcpy 覆写共享的
// staging buffer,去做下一次拷贝。所以本接口必须像旧的 vkQueueWaitIdle
// 一样保证 GPU **已读完** staging 才能返回,否则覆写会 race 上 GPU
// 还在执行的 vkCmdCopyBuffer / vkCmdCopyBufferToImage,导致顶点 / 纹理
// 数据被错位拼接(外观就是模型畸形 / 纹理花屏)。
//
// 这里只等自己这一次的 fence,不像旧实现 vkQueueWaitIdle 那样等整个
// graphicsQueue(包括 drawFrame 的提交),所以不会拖慢渲染主路径。
//
// 不持 poolQueueMtxfence 是独立同步对象,等它不需要外部互斥。
VkResult er = vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
if (er != VK_SUCCESS) {
#ifndef _WIN32
DebugLog::log_throttled("runTransferCommand.endWait",
"runTransferCommand: end vkWaitForFences slot=%u -> %d",
idx, (int)er);
#endif
}
m_xferIdx = (idx + 1) % kTransferSlotCount;
}
void Application::updateTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VkCommandPool commandPool, VkQueue queue,
@@ -1388,13 +1100,8 @@ void Application::updateTexture(VkDevice device, VkPhysicalDevice physicalDevice
vkUnmapMemory(device, texture.stagingBufferMemory);
// 走 runTransferCommand:复用 commandPool_ex 上预分配的 cmdbuf + fence
// 不再每帧 vkAllocate/vkFree,也不再 vkQueueWaitIdle。
// 注意:传入的 commandPool / queue 参数保留只是为了兼容旧接口,
// 实际命令池一律换成 commandPool_ex(与渲染主管线物理隔离)。
(void)commandPool;
(void)queue;
runTransferCommand([&](VkCommandBuffer commandBuffer) {
VkCommandBuffer commandBuffer = beginSingleTimeCommands(device, commandPool);
transitionImageLayout(commandBuffer, texture.image,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
@@ -1417,7 +1124,9 @@ void Application::updateTexture(VkDevice device, VkPhysicalDevice physicalDevice
transitionImageLayout(commandBuffer, texture.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
});
endSingleTimeCommands(device, commandPool, queue, commandBuffer);
texture.image_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
}
+1 -89
View File
@@ -5,7 +5,6 @@
#include "AppBase.h"
#include <thread>
#include <mutex>
#include <functional>
struct Texture
{
@@ -49,68 +48,8 @@ public:
virtual void render(VkCommandBuffer commandBuffer, long long frameTime);
virtual bool isInited() { return _applicationInited; }
std::mutex createTextureMtx;
// 全局 GPU 提交互斥锁:任何对 graphicsQueue / presentQueue 的提交
// vkQueueSubmit / vkQueuePresentKHR / vkQueueWaitIdle)以及共享
// commandPool 的 vkAllocateCommandBuffers / vkFreeCommandBuffers
// 都必须在持有此锁的期间执行,否则驱动内部维护命令池与队列的
// pthread_mutex 在长时间多线程并发下会被破坏(FORTIFY: pthread_mutex_lock
// called on a destroyed mutex)。
// 需要覆盖的 3 条并发线路:
// 1) 渲染线程 drawFrame
// 2) processImageNative → updateTexture (single-time commands)
// 3) passDataToNative → update_face_vertex_buffer → copyBuffer
std::mutex poolQueueMtx;
void processWithVulkan(uint8_t* data, int width, int height, int rowStride, size_t dataSize, Texture& texture, bool srgb, VkCommandPool pool, std::string tex_path);
// ---------------------------------------------------------------------
// 复用式 single-time-command 接口(替代每帧 allocate+submit+waitIdle+free
//
// ★ 同步语义(重要):
// 本接口 **同步**,返回时 GPU 已经读完 record 里访问的源数据。
// 行为上等价于旧的 vkQueueSubmit + vkQueueWaitIdle,但只等自己这次
// 提交的 fence,不阻塞渲染线程在 graphicsQueue 上的其它提交。
//
// 为什么必须同步:调用方 (FaceApp::uploadVertexData /
// Application::updateTexture) 紧接着会复用同一个 staging buffer
//
// vkMapMemory(staging); memcpy(staging, A); vkUnmapMemory;
// runTransferCommand([&](cmd){ vkCmdCopyBuffer(cmd, staging, dstA); });
// vkMapMemory(staging); memcpy(staging, B); ← 必须等 dstA 拷完!
// vkUnmapMemory;
// runTransferCommand([&](cmd){ vkCmdCopyBuffer(cmd, staging, dstB); });
//
// 如果 runTransferCommand 异步返回,第二次 memcpy 会在 GPU 还没读完
// staging 里的 A 时就把它覆盖成 B —— 顶点 / 纹理立刻畸形。
//
// 行为:
// - 共 kTransferSlotCount 个 VkCommandBuffer + 同数 VkFence
// 从 commandPool_ex 一次性分配,在 cleanup 时一次性释放。
// - 每次 runTransferCommand:
// 1) m_xferMtx.lock() (串行所有 transfer 提交)
// 2) 当前 slot 上 vkWaitForFences (等上一次该 slot 的提交完成)
// 3) vkResetFences + vkResetCommandBuffer + vkBegin
// 4) 调用 record(cmd) 录制命令 (vkCmdCopy* 等)
// 5) vkEndCommandBuffer
// 6) poolQueueMtx 内 vkQueueSubmit(graphicsQueue, fence)
// 7) vkWaitForFences(fence) (★ 等本次 GPU 完成才返回)
// 8) m_xferIdx 推进到下一个 slot
//
// 为什么这样设计:
// - 完全消除每帧 vkAllocateCommandBuffers / vkFreeCommandBuffers
// 根除驱动 per-pool mutex 在长时间高频压力下被破坏导致的
// FORTIFY: pthread_mutex_lock called on a destroyed mutex 崩溃。
// - 用 fence 替代 vkQueueWaitIdle,等待粒度只到自己这一次提交,
// 不会 stall 整条 graphicsQueue(包括渲染主路径的提交)。
// - 固定使用 commandPool_ex(与渲染主用的 commandPool 物理隔离),
// 即使驱动还有内部 contention,也不会波及渲染主管线。
//
// 调用约束:
// - 调用方 **绝不能** 提前持有 poolQueueMtx;本接口内部按需短暂获取。
// - record 函数体内只允许 vkCmd*(命令录制)这类操作,不要在里面调
// queue / pool 级别的 APIsubmit / present / pool reset 等)。
static constexpr uint32_t kTransferSlotCount = 3;
void runTransferCommand(const std::function<void(VkCommandBuffer)>& record);
protected:
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; // 物理设备
@@ -151,34 +90,7 @@ public:
// Rebuild the window-dependent Vulkan objects torn down above.
// Safe to call only after cleanupForWindowLost().
// Returns true when the new swapchain's extent or format differs from the
// one in use before cleanupForWindowLost(). When true, any pipeline baked
// with a static viewport/scissor from swapChainExtent -- including the
// FaceApp pipelines -- must be destroyed and recreated against the new
// renderPass / extent. Application's own renderPass + graphicsPipeline are
// already handled internally.
bool reinitForNewWindow();
// Extent / format captured by the most recent cleanupForWindowLost().
// Used by reinitForNewWindow() to decide whether renderPass / pipelines
// must be rebuilt against the new swapchain.
VkExtent2D _prevSwapChainExtent = {0, 0};
VkFormat _prevSwapChainImageFormat = VK_FORMAT_UNDEFINED;
protected:
// Transfer command resources(详见 runTransferCommand 上方注释)。
// 命令缓冲来自 commandPool_ex,与渲染主用的 commandPool 物理隔离。
VkCommandBuffer m_xferCmd[kTransferSlotCount] = { VK_NULL_HANDLE, VK_NULL_HANDLE, VK_NULL_HANDLE };
VkFence m_xferFence[kTransferSlotCount] = { VK_NULL_HANDLE, VK_NULL_HANDLE, VK_NULL_HANDLE };
uint32_t m_xferIdx = 0;
bool m_xferInited = false;
std::mutex m_xferMtx;
// 创建/销毁 transfer 资源。createTransferResources 必须在 createCommandPool
// 之后调用;destroyTransferResources 必须在销毁 commandPool_ex 之前调用,
// 且 GPU 已 idlevkDeviceWaitIdle 或确认所有 fence 已 signaled)。
void createTransferResources();
void destroyTransferResources();
void reinitForNewWindow();
protected:
void loadTexture(std::string path, Texture& tex, bool srgb, VkCommandPool pool);
+35 -54
View File
@@ -9,10 +9,8 @@
#ifndef _WIN32
#include "../app/src/main/cpp/DebugLog.h"
#define FACE_DBG_LOG(...) DebugLog::log(__VA_ARGS__)
#define FACE_DBG_LOG_THROTTLED(key, ...) DebugLog::log_throttled(key, __VA_ARGS__)
#else
#define FACE_DBG_LOG(...) ((void)0)
#define FACE_DBG_LOG_THROTTLED(key, ...) ((void)0)
#endif
@@ -889,12 +887,6 @@ void FaceApp::onWindowLost()
std::unique_lock<std::mutex> lk_point(mtx_point);
std::unique_lock<std::mutex> lk_motion(changeMotionMtx);
std::unique_lock<std::mutex> lk_tex(createTextureMtx);
// 同时阻塞所有并发的 GPU 提交(drawFrame / copyBuffer / updateTexture)。
// cleanupForWindowLost() 会执行 vkDeviceWaitIdle 并销毁 swapchain / surface /
// semaphores / fences 等窗口相关资源,如果此时有其它线程正在 vkQueueSubmit
// 或 vkQueuePresentKHR,会触发 FORTIFY: pthread_mutex_lock called on a
// destroyed mutex。这里持锁确保销毁与提交是互斥的。
std::unique_lock<std::mutex> lk_pool(poolQueueMtx);
Application::cleanupForWindowLost();
_secondfaceAppInited = false;
@@ -914,18 +906,12 @@ void FaceApp::onWindowInit()
} else {
// Recovery path after an earlier onWindowLost. Rebuild only the
// window-dependent Vulkan objects; keep renderPass / pipelines /
// FaceApp GPU resources intact unless the new swapchain is
// incompatible (e.g. rotation changed extent).
// FaceApp GPU resources intact.
std::unique_lock<std::mutex> lk_point(mtx_point);
std::unique_lock<std::mutex> lk_motion(changeMotionMtx);
std::unique_lock<std::mutex> lk_tex(createTextureMtx);
bool swapchainIncompatible = Application::reinitForNewWindow();
if (swapchainIncompatible && _faceAppInited) {
FACE_DBG_LOG("FaceApp::onWindowInit: swapchain incompatible, rebuilding FaceApp pipelines");
recreatePipelinesForSwapchain();
}
Application::reinitForNewWindow();
if (!_secondfaceAppInited) {
_secondfaceAppInited = true;
@@ -939,29 +925,6 @@ void FaceApp::onWindowInit()
(int)_secondfaceAppInited, (int)_running);
}
void FaceApp::recreatePipelinesForSwapchain()
{
// Destroy the two FaceApp pipelines. Layouts / descriptor set layouts are
// swapchain-independent and kept as-is so existing descriptor sets keep
// pointing at the same texture/uniform resources.
if (m_graphicsPipeline != VK_NULL_HANDLE) {
vkDestroyPipeline(device, m_graphicsPipeline, nullptr);
m_graphicsPipeline = VK_NULL_HANDLE;
}
if (m_graphicsPipeline_bg != VK_NULL_HANDLE) {
vkDestroyPipeline(device, m_graphicsPipeline_bg, nullptr);
m_graphicsPipeline_bg = VK_NULL_HANDLE;
}
// Recreate against the fresh renderPass (if format changed) and the new
// swapChainExtent (viewport/scissor are baked statically into these).
create_face_pipelines();
create_pipelines_bg();
FACE_DBG_LOG("FaceApp::recreatePipelinesForSwapchain: rebuilt for extent=%ux%u",
swapChainExtent.width, swapChainExtent.height);
}
void FaceApp::update_uniform_buffers()
{
uint32_t width = 480;
@@ -1059,20 +1022,41 @@ void FaceApp::update_face_vertex_buffer(float* pos, int pointCount)
void FaceApp::copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size)
{
// 改造说明:
// 旧实现是 vkAllocate(commandPool) + vkBegin + vkCmdCopyBuffer + vkEnd +
// vkQueueSubmit(graphicsQueue) + vkQueueWaitIdle + vkFree(commandPool)
// 每帧 update_face_vertex_buffer 会调两次 copyBuffer(顶点 + 索引),
// 长期高频 allocate/free 会把驱动 per-pool mutex 玩坏,触发
// FORTIFY: pthread_mutex_lock called on a destroyed mutex。
//
// 现在改走基类的 runTransferCommand,复用 commandPool_ex 上预分配的
// 3 个 cmdbuf + fence,并发安全由 m_xferMtx + poolQueueMtx 联合保证。
runTransferCommand([&](VkCommandBuffer commandBuffer) {
VkCommandBuffer commandBuffer = beginSingleTimeCommands();
VkBufferCopy copyRegion = {};
copyRegion.size = size;
vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);
});
endSingleTimeCommands(commandBuffer);
}
VkCommandBuffer FaceApp::beginSingleTimeCommands() {
VkCommandBufferAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = commandPool;
allocInfo.commandBufferCount = 1;
VkCommandBuffer commandBuffer;
vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);
VkCommandBufferBeginInfo beginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(commandBuffer, &beginInfo);
return commandBuffer;
}
void FaceApp::endSingleTimeCommands(VkCommandBuffer commandBuffer) {
vkEndCommandBuffer(commandBuffer);
VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
vkQueueWaitIdle(graphicsQueue);
vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
}
void FaceApp::createUniformBuffer()
@@ -1443,9 +1427,6 @@ void FaceApp::cleanup()
//}
destroyTexture(device, tex_bg);
// 必须在销毁 commandPool_ex 之前释放从它分配的 transfer cmdbuf + fence。
// vkDeviceWaitIdle 已在本函数开头调过,提交不会再有 in-flight。
destroyTransferResources();
vkDestroyCommandPool(device, commandPool, nullptr);
vkDestroyCommandPool(device, commandPool_ex, nullptr);
Application::cleanup();
@@ -1656,7 +1637,7 @@ Motion FaceApp::getMotionByName(string name)
void FaceApp::changeMotionList(vector<string> motions, AnimationFinishedCallback callback, bool loop)
{
FACE_DBG_LOG_THROTTLED("FaceApp.changeMotionList",
DebugLog::log_throttled("FaceApp.changeMotionList",
"FaceApp::changeMotionList called count=%zu loop=%d _isLoadMotion=%d",
motions.size(), (int)loop, (int)_isLoadMotion);
if (_isLoadMotion)
+3 -10
View File
@@ -112,6 +112,8 @@ private:
const bool kThick = false;
void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size);
VkCommandBuffer beginSingleTimeCommands();
void endSingleTimeCommands(VkCommandBuffer commandBuffer);
// 顶点缓冲区相关
VkBuffer m_vertexBuffer = VK_NULL_HANDLE;
@@ -189,17 +191,8 @@ public:
// Called from main thread in response to APP_CMD_INIT_WINDOW.
// Does the full first-time initVulkan() on the very first call, and a
// lightweight swapchain/surface rebuild on subsequent calls. When the
// new swapchain's extent or format differs from the previous one, the
// FaceApp pipelines (baked with static viewport / old renderPass) are
// also destroyed and recreated via recreatePipelinesForSwapchain().
// lightweight swapchain/surface rebuild on subsequent calls.
void onWindowInit();
// Destroy and recreate m_graphicsPipeline / m_graphicsPipeline_bg so they
// match the current renderPass and swapChainExtent. Descriptor set layouts,
// pipeline layouts, vertex/index buffers, uniform buffers and textures are
// all kept. Must be called with device idle and the FaceApp mutexes held.
void recreatePipelinesForSwapchain();
void loadMotionThread();
std::thread worker_;
bool _isLoadMotion = false;