fix(kernel): orderly shutdown when closing the display window
This commit is contained in:
parent
143aec54c2
commit
39fd90b050
|
|
@ -11,6 +11,7 @@ void Submit(const Packet* packet, std::uint32_t queue);
|
|||
}
|
||||
|
||||
extern "C" void AgcDriverWaitIdle_nid_postfix();
|
||||
extern "C" void AgcDriverShutdown_nid_postfix();
|
||||
extern "C" void AgcDriverSuspendPoint_nid_postfix();
|
||||
extern "C" void AgcDriverRegisterShader_nid_postfix(const Shader* shader);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace AgcDriver {
|
|||
|
||||
// Starts sampling the calling thread when APS5_SAMPLE_WORKER names an output file.
|
||||
void StartWorkerSampler();
|
||||
void StopWorkerSampler();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp"
|
||||
#include "prx/libc/include/Shutdown.hpp"
|
||||
#include "prx/libc/include/CpuTopology.hpp"
|
||||
#include "ControlFlow/RequestSerializer.hpp"
|
||||
|
|
@ -460,24 +461,34 @@ public:
|
|||
|
||||
void Shutdown() {
|
||||
stop();
|
||||
CheckFailure();
|
||||
std::lock_guard lock(mutex);
|
||||
rethrowFailure();
|
||||
}
|
||||
|
||||
private:
|
||||
void stop() {
|
||||
require(!OnWorkerThread(), "worker cannot stop itself");
|
||||
std::lock_guard shutdownLock(shutdownMutex);
|
||||
if (stopped) return;
|
||||
LibcRequestShutdown_nid_postfix();
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
stopping = true;
|
||||
for (auto& [queue, worker] : workers) {
|
||||
worker.pending.clear();
|
||||
worker.queued.store(0, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
changed.notify_all();
|
||||
// No worker is added once `stopping` is set, so the map is stable while the threads finish.
|
||||
for (auto& [queue, worker] : workers) {
|
||||
if (worker.thread.joinable()) worker.thread.join();
|
||||
}
|
||||
StopWorkerSampler();
|
||||
std::lock_guard gpuLock(GuestMemory::GpuMutex());
|
||||
device.reset();
|
||||
replacedDevices.clear();
|
||||
Graphics::ShutdownGuestBufferWorkers();
|
||||
stopped = true;
|
||||
}
|
||||
|
||||
public:
|
||||
|
|
@ -506,7 +517,7 @@ public:
|
|||
{
|
||||
std::lock_guard lock(mutex);
|
||||
rethrowFailure();
|
||||
require(!stopping, "submission during shutdown");
|
||||
checkStopping();
|
||||
require(accepted != std::numeric_limits<std::uint64_t>::max(), "submission serial overflow");
|
||||
for (std::size_t cursor = 0; cursor < submission.commands.size();) {
|
||||
const auto* words = submission.commands.data() + cursor;
|
||||
|
|
@ -550,16 +561,17 @@ public:
|
|||
std::unique_lock lock(mutex);
|
||||
const auto target = accepted;
|
||||
++idleWaiters;
|
||||
changed.wait(lock, [&] { return failure != nullptr || completed >= target; });
|
||||
changed.wait(lock, [&] { return failure != nullptr || stopping || completed >= target; });
|
||||
--idleWaiters;
|
||||
rethrowFailure();
|
||||
checkStopping();
|
||||
}
|
||||
|
||||
void SuspendPoint() {
|
||||
require(!OnWorkerThread(), "worker cannot suspend itself");
|
||||
std::unique_lock lock(mutex);
|
||||
rethrowFailure();
|
||||
require(!stopping, "suspend during shutdown");
|
||||
checkStopping();
|
||||
require(accepted != std::numeric_limits<std::uint64_t>::max(), "submission serial overflow");
|
||||
Submission boundary{};
|
||||
boundary.serial = accepted + 1;
|
||||
|
|
@ -578,7 +590,7 @@ public:
|
|||
require(output != nullptr, "null video output");
|
||||
std::lock_guard lock(mutex);
|
||||
rethrowFailure();
|
||||
require(!stopping, "video output registration during shutdown");
|
||||
checkStopping();
|
||||
require(outputs.emplace(handle, output).second, "video output already registered");
|
||||
}
|
||||
|
||||
|
|
@ -592,9 +604,11 @@ public:
|
|||
// Called per packet and per poll of a wait: the flag keeps the driver mutex out of those loops
|
||||
// until a failure exists, and `failure` itself is still read under the mutex.
|
||||
void CheckFailure() {
|
||||
if (!failed.load(std::memory_order_acquire)) return;
|
||||
std::lock_guard lock(mutex);
|
||||
rethrowFailure();
|
||||
if (failed.load(std::memory_order_acquire)) {
|
||||
std::lock_guard lock(mutex);
|
||||
rethrowFailure();
|
||||
}
|
||||
checkStopping();
|
||||
}
|
||||
|
||||
void ReportFailure(std::exception_ptr error) {
|
||||
|
|
@ -654,7 +668,6 @@ public:
|
|||
if (device == nullptr || device->Window() == nullptr) {
|
||||
if (device) {
|
||||
device->PrepareForReplacement();
|
||||
static std::vector<std::shared_ptr<VulkanDevice>> replacedDevices;
|
||||
replacedDevices.push_back(device);
|
||||
}
|
||||
device = std::make_shared<VulkanDevice>(&window);
|
||||
|
|
@ -722,6 +735,8 @@ public:
|
|||
timing.Mark("release_and_callback");
|
||||
if (profile) reportPresents(waitedMs, inFlight);
|
||||
CheckFailure();
|
||||
} catch (const ProcessShutdown&) {
|
||||
throw;
|
||||
} catch (...) {
|
||||
ReportFailure(std::current_exception());
|
||||
throw;
|
||||
|
|
@ -1450,13 +1465,16 @@ private:
|
|||
std::map<std::uint32_t, QueueState> queues;
|
||||
std::map<std::uint32_t, std::shared_ptr<IVideoOutput>> outputs;
|
||||
DevicePointer device;
|
||||
std::vector<std::shared_ptr<VulkanDevice>> replacedDevices;
|
||||
std::stop_token shutdownToken = LibcShutdownToken_nid_postfix();
|
||||
std::uint64_t accepted = 0;
|
||||
std::uint64_t completed = 0;
|
||||
std::set<std::uint64_t> completedOutOfOrder;
|
||||
std::exception_ptr failure;
|
||||
// Set once `failure` is, so hot loops can check for one without the mutex.
|
||||
std::atomic<bool> failed{false};
|
||||
bool stopping = false;
|
||||
std::atomic<bool> stopping{false};
|
||||
bool stopped = false;
|
||||
bool resetGraphics = false;
|
||||
// Threads in WaitIdle (under `mutex`): a worker's completion notifies only while one waits.
|
||||
std::uint32_t idleWaiters = 0;
|
||||
|
|
@ -1491,6 +1509,10 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
void checkStopping() const {
|
||||
if (stopping.load(std::memory_order_acquire) || shutdownToken.stop_requested()) throw ProcessShutdown{};
|
||||
}
|
||||
|
||||
// Summarizes a rejected submission (packet names with counts and the first rejection reason per name).
|
||||
static void dumpPackets(std::span<const std::uint32_t> commands, const std::uint32_t* guest = nullptr) {
|
||||
std::map<std::string, std::pair<std::size_t, std::string>> summary;
|
||||
|
|
@ -6116,7 +6138,7 @@ private:
|
|||
lock.lock();
|
||||
}
|
||||
rethrowFailure();
|
||||
if (pending.empty()) {
|
||||
if (stopping || shutdownToken.stop_requested() || pending.empty()) {
|
||||
break;
|
||||
}
|
||||
submission = std::move(pending.front());
|
||||
|
|
@ -6140,6 +6162,8 @@ private:
|
|||
else ++costs.notifiesSkipped;
|
||||
if (profile) costs.completeNs += static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - completeStart).count());
|
||||
}
|
||||
} catch (const ProcessShutdown&) {
|
||||
submission = Submission{};
|
||||
} catch (...) {
|
||||
const auto error = std::current_exception();
|
||||
try {
|
||||
|
|
@ -6157,14 +6181,20 @@ private:
|
|||
|
||||
}
|
||||
|
||||
void Submit(const Packet* packet, std::uint32_t queue) {
|
||||
void Submit(const Packet* packet, std::uint32_t queue) try {
|
||||
Driver::Get().Submit(packet, queue);
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
void WaitIdle() {
|
||||
Driver::Get().WaitIdle();
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
Driver::Get().Shutdown();
|
||||
}
|
||||
|
||||
void RegisterShader(const Shader* shader) {
|
||||
Driver::Get().RegisterShader(shader);
|
||||
}
|
||||
|
|
@ -6199,16 +6229,26 @@ void ReportFailure(std::exception_ptr error) {
|
|||
|
||||
}
|
||||
|
||||
extern "C" void AgcDriverWaitIdle_nid_postfix() {
|
||||
extern "C" void AgcDriverWaitIdle_nid_postfix() try {
|
||||
AgcDriver::WaitIdle();
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
extern "C" void AgcDriverRegisterShader_nid_postfix(const Shader* shader) {
|
||||
extern "C" void AgcDriverShutdown_nid_postfix() {
|
||||
AgcDriver::Shutdown();
|
||||
}
|
||||
|
||||
extern "C" void AgcDriverRegisterShader_nid_postfix(const Shader* shader) try {
|
||||
AgcDriver::RegisterShader(shader);
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
extern "C" void AgcDriverSuspendPoint_nid_postfix() {
|
||||
extern "C" void AgcDriverSuspendPoint_nid_postfix() try {
|
||||
AgcDriver::SuspendPoint();
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
extern "C" void AgcDriverRegisterVideoOutput_nid_postfix(std::uint32_t handle, const std::shared_ptr<AgcDriver::IVideoOutput>& output) {
|
||||
|
|
|
|||
|
|
@ -9,8 +9,11 @@
|
|||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <stop_token>
|
||||
#include <system_error>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#endif
|
||||
|
|
@ -20,6 +23,9 @@ namespace AgcDriver {
|
|||
#ifdef _WIN32
|
||||
namespace {
|
||||
|
||||
std::jthread processSamplerThread;
|
||||
std::jthread workerSamplerThread;
|
||||
|
||||
// Debug aid: APS5_SAMPLE_WORKER=<file> samples the calling thread every millisecond and, every 20 s,
|
||||
// writes "module+offset self inclusive" lines to <file> for offline symbolization with nm.
|
||||
struct Sampler {
|
||||
|
|
@ -29,6 +35,10 @@ struct Sampler {
|
|||
std::map<std::uint64_t, std::pair<std::uint64_t, std::uint64_t>> counts;
|
||||
std::uint64_t samples = 0;
|
||||
|
||||
~Sampler() noexcept(false) {
|
||||
if (target != nullptr && !CloseHandle(target)) throw std::system_error(GetLastError(), std::system_category(), "Closing sampler thread handle");
|
||||
}
|
||||
|
||||
void sample() {
|
||||
CONTEXT context{};
|
||||
context.ContextFlags = CONTEXT_FULL;
|
||||
|
|
@ -137,12 +147,12 @@ struct ProcessSampler {
|
|||
void StartProcessSampler() {
|
||||
const char* path = std::getenv("APS5_SAMPLE_THREADS");
|
||||
if (path == nullptr) return;
|
||||
std::thread([path = std::string(path)] {
|
||||
auto* sampler = new ProcessSampler();
|
||||
processSamplerThread = std::jthread([path = std::string(path)](std::stop_token token) {
|
||||
auto sampler = std::make_unique<ProcessSampler>();
|
||||
sampler->path = path;
|
||||
sampler->self = GetCurrentThreadId();
|
||||
auto flushed = std::chrono::steady_clock::now();
|
||||
for (;;) {
|
||||
while (!token.stop_requested()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||
sampler->sample();
|
||||
if (std::chrono::steady_clock::now() - flushed > std::chrono::seconds(20)) {
|
||||
|
|
@ -150,7 +160,8 @@ void StartProcessSampler() {
|
|||
flushed = std::chrono::steady_clock::now();
|
||||
}
|
||||
}
|
||||
}).detach();
|
||||
sampler->write();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -159,12 +170,12 @@ void StartWorkerSampler() {
|
|||
StartProcessSampler();
|
||||
const char* path = std::getenv("APS5_SAMPLE_WORKER");
|
||||
if (path == nullptr) return;
|
||||
auto* sampler = new Sampler();
|
||||
auto sampler = std::make_unique<Sampler>();
|
||||
sampler->path = path;
|
||||
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &sampler->target, THREAD_ALL_ACCESS, FALSE, 0);
|
||||
std::thread([sampler] {
|
||||
if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &sampler->target, THREAD_ALL_ACCESS, FALSE, 0)) throw std::system_error(GetLastError(), std::system_category(), "Duplicating sampler thread handle");
|
||||
workerSamplerThread = std::jthread([sampler = std::move(sampler)](std::stop_token token) {
|
||||
auto flushed = std::chrono::steady_clock::now();
|
||||
for (;;) {
|
||||
while (!token.stop_requested()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
sampler->sample();
|
||||
if (std::chrono::steady_clock::now() - flushed > std::chrono::seconds(20)) {
|
||||
|
|
@ -172,10 +183,19 @@ void StartWorkerSampler() {
|
|||
flushed = std::chrono::steady_clock::now();
|
||||
}
|
||||
}
|
||||
}).detach();
|
||||
sampler->write();
|
||||
});
|
||||
}
|
||||
|
||||
void StopWorkerSampler() {
|
||||
processSamplerThread.request_stop();
|
||||
workerSamplerThread.request_stop();
|
||||
if (processSamplerThread.joinable()) processSamplerThread.join();
|
||||
if (workerSamplerThread.joinable()) workerSamplerThread.join();
|
||||
}
|
||||
#else
|
||||
void StartWorkerSampler() {}
|
||||
void StopWorkerSampler() {}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
void ShutdownGuestBufferWorkers();
|
||||
|
||||
struct GuestMemorySnapshot {
|
||||
std::uint64_t address;
|
||||
std::span<const std::byte> bytes;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include <mutex>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <stop_token>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <chrono>
|
||||
|
|
@ -382,19 +383,15 @@ bool compareBlock(const RefreshBlock& block) {
|
|||
return true;
|
||||
}
|
||||
|
||||
// Helper threads for the block compares. An address-based build refreshes every writable image range
|
||||
// (~15 MiB in ~300 blocks) under the device lock, and that memcmp was most of its cost; the blocks
|
||||
// are handed out by an atomic cursor to the helpers and the calling thread alike, and Run returns
|
||||
// once every block was taken and finished. The threads are started on first use and never joined
|
||||
// (they only wait on the condition variable). APS5_MIRROR_REFRESH_THREADS sets the helper count
|
||||
// (default 3); APS5_NO_PARALLEL_MIRROR_REFRESH=1 compares on the calling thread only.
|
||||
class RefreshPool {
|
||||
public:
|
||||
static RefreshPool& Get() {
|
||||
// Never destroyed (as WorkerSampler's): the helpers block on its condition variable, which a
|
||||
// static destructor at std::exit would tear down under them.
|
||||
static RefreshPool* pool = new RefreshPool;
|
||||
return *pool;
|
||||
if (instance == nullptr) instance = new RefreshPool;
|
||||
return *instance;
|
||||
}
|
||||
|
||||
static void Shutdown() {
|
||||
delete std::exchange(instance, nullptr);
|
||||
}
|
||||
|
||||
// Compares `blocks` and returns how many were copied.
|
||||
|
|
@ -429,19 +426,12 @@ private:
|
|||
const char* text = std::getenv("APS5_MIRROR_REFRESH_THREADS");
|
||||
const auto requested = text != nullptr ? std::atoi(text) : 3;
|
||||
const auto wanted = disabled ? 0u : static_cast<unsigned>(std::clamp(requested, 0, 16));
|
||||
// Only started threads are counted: Run waits for `helpers` finishes, and a thread that could
|
||||
// not be started would leave it waiting forever.
|
||||
for (unsigned i = 0; i < wanted; ++i) {
|
||||
try {
|
||||
std::thread([this] {
|
||||
CpuTopology::PinHelperThread("mirror refresh");
|
||||
helper();
|
||||
}).detach();
|
||||
++helpers;
|
||||
} catch (const std::system_error& error) {
|
||||
std::fprintf(stderr, "[gpu] mirror refresh helper %u not started: %s\n", i, error.what());
|
||||
break;
|
||||
}
|
||||
threads.emplace_back([this](std::stop_token token) {
|
||||
CpuTopology::PinHelperThread("mirror refresh");
|
||||
helper(token);
|
||||
});
|
||||
++helpers;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -452,13 +442,13 @@ private:
|
|||
copiedBlocks.fetch_add(copied, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void helper() {
|
||||
void helper(std::stop_token token) {
|
||||
std::uint64_t seen = 0;
|
||||
for (;;) {
|
||||
std::span<const RefreshBlock> blocks;
|
||||
{
|
||||
std::unique_lock lock(mutex);
|
||||
wake.wait(lock, [&] { return serial != seen; });
|
||||
if (!wake.wait(lock, token, [&] { return serial != seen; })) return;
|
||||
seen = serial;
|
||||
blocks = job;
|
||||
}
|
||||
|
|
@ -471,16 +461,18 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
inline static RefreshPool* instance = nullptr;
|
||||
unsigned helpers = 0;
|
||||
std::mutex runMutex;
|
||||
std::mutex mutex;
|
||||
std::condition_variable wake;
|
||||
std::condition_variable_any wake;
|
||||
std::condition_variable done;
|
||||
std::span<const RefreshBlock> job;
|
||||
std::uint64_t serial = 0;
|
||||
unsigned finished = 0;
|
||||
std::atomic<std::size_t> next{0};
|
||||
std::atomic<std::uint64_t> copiedBlocks{0};
|
||||
std::vector<std::jthread> threads;
|
||||
};
|
||||
|
||||
// The device-lock work before [address, address + bytes) of a writable mirror can be compared with
|
||||
|
|
@ -1357,6 +1349,10 @@ void reportStaging() {
|
|||
|
||||
}
|
||||
|
||||
void ShutdownGuestBufferWorkers() {
|
||||
RefreshPool::Shutdown();
|
||||
}
|
||||
|
||||
GuestBufferMemory::~GuestBufferMemory() {
|
||||
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
if (!profile) return;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp"
|
||||
#include "prx/libSceVideoOut/include/DisplayWindow.hpp"
|
||||
#include "prx/libSceVideoOut/include/BufferReuseTracker.hpp"
|
||||
#include "prx/libc/include/Shutdown.hpp"
|
||||
|
||||
#include "SDL.h"
|
||||
#include "SceTypes.hpp"
|
||||
|
|
@ -100,6 +101,7 @@ struct VideoOutConfig {
|
|||
bool opened = false;
|
||||
bool closing = false;
|
||||
std::exception_ptr failure;
|
||||
std::stop_token shutdownToken = LibcShutdownToken_nid_postfix();
|
||||
int flipRate = 0;
|
||||
uint64_t lastFlipVblank = 0;
|
||||
std::chrono::steady_clock::time_point lastTimingFlip{};
|
||||
|
|
@ -117,6 +119,7 @@ struct VideoOutConfig {
|
|||
|
||||
void Check() const {
|
||||
if (failure) std::rethrow_exception(failure);
|
||||
if (shutdownToken.stop_requested()) throw ProcessShutdown{};
|
||||
if (!opened || closing) throw std::runtime_error("VideoOut: port is closed");
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ void APS5_VABI sceVideoOutSetBufferAttribute2(VideoOutBufferAttribute2* attribut
|
|||
attribute->dcc_control = dccControl;
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutRegisterBuffers2(int handle, int setIndex, int bufferIndexStart, const VideoOutBuffers* buffers, int bufferNum, const VideoOutBufferAttribute2* attribute, int category, void* option) {
|
||||
int APS5_VABI sceVideoOutRegisterBuffers2(int handle, int setIndex, int bufferIndexStart, const VideoOutBuffers* buffers, int bufferNum, const VideoOutBufferAttribute2* attribute, int category, void* option) try {
|
||||
auto cfg = VideoOutDriver::Get().GetConfig(handle);
|
||||
if (cfg == nullptr) {
|
||||
throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE");
|
||||
|
|
@ -88,9 +88,11 @@ int APS5_VABI sceVideoOutRegisterBuffers2(int handle, int setIndex, int bufferIn
|
|||
cfg->buffers[bufferIndexStart + i] = registered[i];
|
||||
}
|
||||
return 0;
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutSubmitChangeBufferAttribute2(int handle, int setIndex, const VideoOutBufferAttribute2* attribute, void* option) {
|
||||
int APS5_VABI sceVideoOutSubmitChangeBufferAttribute2(int handle, int setIndex, const VideoOutBufferAttribute2* attribute, void* option) try {
|
||||
auto cfg = VideoOutDriver::Get().GetConfig(handle);
|
||||
if (cfg == nullptr) {
|
||||
throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE");
|
||||
|
|
@ -120,9 +122,11 @@ int APS5_VABI sceVideoOutSubmitChangeBufferAttribute2(int handle, int setIndex,
|
|||
}
|
||||
cfg->groups[setIndex] = group;
|
||||
return 0;
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutUnregisterBuffers(int handle, int setIndex) {
|
||||
int APS5_VABI sceVideoOutUnregisterBuffers(int handle, int setIndex) try {
|
||||
auto cfg = VideoOutDriver::Get().GetConfig(handle);
|
||||
if (cfg == nullptr) {
|
||||
throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE");
|
||||
|
|
@ -148,9 +152,11 @@ int APS5_VABI sceVideoOutUnregisterBuffers(int handle, int setIndex) {
|
|||
}
|
||||
}
|
||||
return 0;
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutSubmitFlip(int handle, int index, int flipMode, int64_t flipArg) {
|
||||
int APS5_VABI sceVideoOutSubmitFlip(int handle, int index, int flipMode, int64_t flipArg) try {
|
||||
auto cfg = VideoOutDriver::Get().GetConfig(handle);
|
||||
if (cfg == nullptr) {
|
||||
throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE");
|
||||
|
|
@ -173,6 +179,8 @@ int APS5_VABI sceVideoOutSubmitFlip(int handle, int index, int flipMode, int64_t
|
|||
}
|
||||
lock.unlock();
|
||||
return VideoOutDriver::Get().SubmitFlip(handle, index, flipMode, flipArg);
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,35 +107,49 @@ static int deleteVideoOutEvent(int handle, KernelEqueue eq, int16_t eventKind) {
|
|||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceVideoOutAddFlipEvent(KernelEqueue eq, int handle, void* udata) {
|
||||
int APS5_VABI sceVideoOutAddFlipEvent(KernelEqueue eq, int handle, void* udata) try {
|
||||
return registerVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_FLIP, udata);
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutAddVblankEvent(KernelEqueue eq, int handle, void* udata) {
|
||||
int APS5_VABI sceVideoOutAddVblankEvent(KernelEqueue eq, int handle, void* udata) try {
|
||||
return registerVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_VBLANK, udata);
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutAddPreVblankStartEvent(KernelEqueue eq, int handle, void* udata) {
|
||||
int APS5_VABI sceVideoOutAddPreVblankStartEvent(KernelEqueue eq, int handle, void* udata) try {
|
||||
static_cast<void>(eq);
|
||||
static_cast<void>(udata);
|
||||
VideoOutDriver::Get().GetConfig(handle);
|
||||
throw std::runtime_error("VideoOut: physical pre-vblank timing is not implemented");
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutAddOutputModeEvent(KernelEqueue eq, int handle, void* udata) {
|
||||
int APS5_VABI sceVideoOutAddOutputModeEvent(KernelEqueue eq, int handle, void* udata) try {
|
||||
return registerVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_SET_MODE, udata);
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutDeleteFlipEvent(KernelEqueue eq, int handle) {
|
||||
int APS5_VABI sceVideoOutDeleteFlipEvent(KernelEqueue eq, int handle) try {
|
||||
return deleteVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_FLIP);
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutDeleteVblankEvent(KernelEqueue eq, int handle) {
|
||||
int APS5_VABI sceVideoOutDeleteVblankEvent(KernelEqueue eq, int handle) try {
|
||||
return deleteVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_VBLANK);
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutDeletePreVblankStartEvent(KernelEqueue eq, int handle) {
|
||||
int APS5_VABI sceVideoOutDeletePreVblankStartEvent(KernelEqueue eq, int handle) try {
|
||||
return deleteVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_PRE_VBLANK_START);
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutGetEventId(const KernelEvent* ev) {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ static int validateOutputConfig(int handle, uint64_t mode, const VideoOutOutputO
|
|||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceVideoOutOpen(int userId, int busType, int index, const void* param) {
|
||||
int APS5_VABI sceVideoOutOpen(int userId, int busType, int index, const void* param) try {
|
||||
if (param != nullptr) {
|
||||
throw std::runtime_error(std::string(__func__) + ": param not implemented");
|
||||
}
|
||||
|
|
@ -47,13 +47,17 @@ int APS5_VABI sceVideoOutOpen(int userId, int busType, int index, const void* pa
|
|||
throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_RESOURCE_BUSY");
|
||||
}
|
||||
return handle;
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutClose(int handle) {
|
||||
int APS5_VABI sceVideoOutClose(int handle) try {
|
||||
return VideoOutDriver::Get().Close(handle) ? 0 : VIDEO_OUT_ERROR_INVALID_HANDLE;
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutSetFlipRate(int handle, int rate) {
|
||||
int APS5_VABI sceVideoOutSetFlipRate(int handle, int rate) try {
|
||||
auto cfg = VideoOutDriver::Get().GetConfig(handle);
|
||||
if (cfg == nullptr) {
|
||||
throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE");
|
||||
|
|
@ -65,9 +69,11 @@ int APS5_VABI sceVideoOutSetFlipRate(int handle, int rate) {
|
|||
cfg->Check();
|
||||
cfg->flipRate = rate;
|
||||
return 0;
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status) {
|
||||
int APS5_VABI sceVideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status) try {
|
||||
if (status == nullptr) {
|
||||
throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_ADDRESS");
|
||||
}
|
||||
|
|
@ -79,9 +85,11 @@ int APS5_VABI sceVideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status) {
|
|||
cfg->Check();
|
||||
*status = cfg->flipStatus;
|
||||
return 0;
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutGetVblankStatus(int handle, VideoOutVblankStatus* status) {
|
||||
int APS5_VABI sceVideoOutGetVblankStatus(int handle, VideoOutVblankStatus* status) try {
|
||||
if (status == nullptr) {
|
||||
throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_ADDRESS");
|
||||
}
|
||||
|
|
@ -93,9 +101,11 @@ int APS5_VABI sceVideoOutGetVblankStatus(int handle, VideoOutVblankStatus* statu
|
|||
cfg->Check();
|
||||
*status = cfg->vblankStatus;
|
||||
return 0;
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutGetOutputStatus(int handle, VideoOutOutputStatus* status) {
|
||||
int APS5_VABI sceVideoOutGetOutputStatus(int handle, VideoOutOutputStatus* status) try {
|
||||
if (status == nullptr) {
|
||||
throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_ADDRESS");
|
||||
}
|
||||
|
|
@ -113,9 +123,11 @@ int APS5_VABI sceVideoOutGetOutputStatus(int handle, VideoOutOutputStatus* statu
|
|||
status->reserved[1] = 0;
|
||||
status->reserved[2] = 0;
|
||||
return 0;
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutIsFlipPending(int handle) {
|
||||
int APS5_VABI sceVideoOutIsFlipPending(int handle) try {
|
||||
auto cfg = VideoOutDriver::Get().GetConfig(handle);
|
||||
if (cfg == nullptr) {
|
||||
throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE");
|
||||
|
|
@ -123,9 +135,11 @@ int APS5_VABI sceVideoOutIsFlipPending(int handle) {
|
|||
std::unique_lock lock(cfg->mutex);
|
||||
cfg->Check();
|
||||
return cfg->flipStatus.flipPendingNum;
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutWaitVblank(int handle) {
|
||||
int APS5_VABI sceVideoOutWaitVblank(int handle) try {
|
||||
auto cfg = VideoOutDriver::Get().GetConfig(handle);
|
||||
if (cfg == nullptr) {
|
||||
throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE");
|
||||
|
|
@ -133,12 +147,11 @@ int APS5_VABI sceVideoOutWaitVblank(int handle) {
|
|||
std::unique_lock lock(cfg->mutex);
|
||||
cfg->Check();
|
||||
const uint64_t count = cfg->vblankStatus.count;
|
||||
while (cfg->opened && !cfg->failure && cfg->vblankStatus.count == count) {
|
||||
cfg->vblankCond.wait(lock);
|
||||
}
|
||||
if (cfg->failure) std::rethrow_exception(cfg->failure);
|
||||
if (!cfg->opened || cfg->closing) throw std::runtime_error("sceVideoOutWaitVblank: port closed during wait");
|
||||
cfg->vblankCond.wait(lock, cfg->shutdownToken, [&] { return !cfg->opened || cfg->closing || cfg->failure || cfg->vblankStatus.count != count; });
|
||||
cfg->Check();
|
||||
return 0;
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutInitializeOutputOptions(VideoOutOutputOptions* options) {
|
||||
|
|
@ -149,15 +162,17 @@ int APS5_VABI sceVideoOutInitializeOutputOptions(VideoOutOutputOptions* options)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutIsOutputSupported(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, uint64_t reserved) {
|
||||
int APS5_VABI sceVideoOutIsOutputSupported(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, uint64_t reserved) try {
|
||||
const int result = validateOutputConfig(handle, mode, options, reservedPtr, reserved);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
return (mode == VIDEO_OUT_OUTPUT_MODE_119_88HZ) ? 0 : 1;
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutConfigureOutput(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, uint64_t reserved) {
|
||||
int APS5_VABI sceVideoOutConfigureOutput(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, uint64_t reserved) try {
|
||||
const int supported = sceVideoOutIsOutputSupported(handle, mode, options, reservedPtr, reserved);
|
||||
if (supported < 0) {
|
||||
return supported;
|
||||
|
|
@ -173,30 +188,38 @@ int APS5_VABI sceVideoOutConfigureOutput(int handle, uint64_t mode, const VideoO
|
|||
cfg->Check();
|
||||
cfg->outputMode = mode;
|
||||
return 0;
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutSetWindowModeMargins(int handle, int top, int bottom) {
|
||||
int APS5_VABI sceVideoOutSetWindowModeMargins(int handle, int top, int bottom) try {
|
||||
(void)top;
|
||||
(void)bottom;
|
||||
if (!VideoOutDriver::Get().IsOpen(handle)) {
|
||||
throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE");
|
||||
}
|
||||
return 0;
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutLatencyControlWaitBeforeInput(int handle) {
|
||||
int APS5_VABI sceVideoOutLatencyControlWaitBeforeInput(int handle) try {
|
||||
if (!VideoOutDriver::Get().IsOpen(handle)) {
|
||||
throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE");
|
||||
}
|
||||
return 0;
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutLatencyMeasureSetStartPoint(int handle, uint32_t point) {
|
||||
int APS5_VABI sceVideoOutLatencyMeasureSetStartPoint(int handle, uint32_t point) try {
|
||||
(void)point;
|
||||
if (!VideoOutDriver::Get().IsOpen(handle)) {
|
||||
throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE");
|
||||
}
|
||||
return 0;
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutColorSettingsSetGamma(VideoOutColorSettings* settings, float gamma) {
|
||||
|
|
@ -210,7 +233,7 @@ int APS5_VABI sceVideoOutColorSettingsSetGamma(VideoOutColorSettings* settings,
|
|||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideoOutAdjustColor(int handle, const VideoOutColorSettings* settings) {
|
||||
int APS5_VABI sceVideoOutAdjustColor(int handle, const VideoOutColorSettings* settings) try {
|
||||
if (settings == nullptr) {
|
||||
throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_ADDRESS");
|
||||
}
|
||||
|
|
@ -220,6 +243,8 @@ int APS5_VABI sceVideoOutAdjustColor(int handle, const VideoOutColorSettings* se
|
|||
}
|
||||
// Output gamma is accepted but not yet applied to presentation.
|
||||
return 0;
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public:
|
|||
: _config(std::move(config)), _index(index), _ticket(ticket) {}
|
||||
void Wait() override {
|
||||
std::unique_lock lock(_config->mutex);
|
||||
_config->vblankCond.wait(lock, [&] {
|
||||
_config->vblankCond.wait(lock, _config->shutdownToken, [&] {
|
||||
return _config->failure || _config->closing || !_config->opened ||
|
||||
_config->bufferReuse[_index].IsComplete(_ticket);
|
||||
});
|
||||
|
|
@ -76,13 +76,14 @@ public:
|
|||
if (waitWhenFull) {
|
||||
int waited = 0;
|
||||
while (queue->reservations.load() >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) {
|
||||
if (cfg->shutdownToken.stop_requested()) throw ProcessShutdown{};
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
if (++waited % 500 == 0) std::fprintf(stderr, "[flip] queue full for %d s; waiting (APS5_FLIP_QUEUE_WAIT)\n", waited / 100);
|
||||
}
|
||||
}
|
||||
std::lock_guard queueLock(queue->mutex);
|
||||
if (queue->failure) std::rethrow_exception(queue->failure);
|
||||
require(!queue->stopping, "flip during shutdown");
|
||||
if (queue->stopping || cfg->shutdownToken.stop_requested()) throw ProcessShutdown{};
|
||||
std::lock_guard lock(cfg->mutex);
|
||||
checkConfig(*cfg);
|
||||
require(queue->reservations.load() < VIDEO_OUT_FLIP_QUEUE_CAPACITY, "flip queue full");
|
||||
|
|
@ -153,7 +154,7 @@ void FlipRequest::GpuReady(const std::shared_ptr<AgcDriver::FrameTiming>& frameT
|
|||
AgcDriver::PerformanceTimer readiness("VideoOut.Readiness");
|
||||
std::lock_guard queueLock(queue->mutex);
|
||||
if (queue->failure) std::rethrow_exception(queue->failure);
|
||||
require(!queue->stopping, "GPU flip during shutdown");
|
||||
if (queue->stopping || cfg->shutdownToken.stop_requested()) throw ProcessShutdown{};
|
||||
std::lock_guard lock(cfg->mutex);
|
||||
checkConfig(*cfg);
|
||||
require(reserved && !ready && !terminal && cfg->generation == generation, "invalid flip readiness transition");
|
||||
|
|
@ -172,7 +173,7 @@ void FlipRequest::GpuReady(const std::shared_ptr<AgcDriver::FrameTiming>& frameT
|
|||
static const bool syncFlip = std::getenv("APS5_SYNC_FLIP") != nullptr;
|
||||
if (!syncFlip) return;
|
||||
std::unique_lock lock(cfg->mutex);
|
||||
cfg->vblankCond.wait(lock, [&] { return gpuComplete || cfg->failure || cfg->closing; });
|
||||
cfg->vblankCond.wait(lock, cfg->shutdownToken, [&] { return gpuComplete || cfg->failure || cfg->closing; });
|
||||
checkConfig(*cfg);
|
||||
require(gpuComplete, "flip preparation did not complete on GPU");
|
||||
}
|
||||
|
|
@ -232,24 +233,21 @@ void VideoOutDriver::Shutdown() {
|
|||
require(std::this_thread::get_id() != presentThread.get_id(), "presentation thread cannot stop itself");
|
||||
std::lock_guard shutdownLock(shutdownMutex);
|
||||
if (stopped) return;
|
||||
LibcRequestShutdown_nid_postfix();
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
{
|
||||
std::lock_guard queueLock(flipQueue->mutex);
|
||||
flipQueue->stopping = true;
|
||||
}
|
||||
for (int handle = 1; handle < VIDEO_OUT_NUM_MAX; ++handle) {
|
||||
if (outputs[handle]) close(handle);
|
||||
}
|
||||
std::lock_guard queueLock(flipQueue->mutex);
|
||||
flipQueue->stopping = true;
|
||||
}
|
||||
presentThread.request_stop();
|
||||
vblankThread.request_stop();
|
||||
flipQueue->changed.notify_all();
|
||||
presentThread.join();
|
||||
vblankThread.join();
|
||||
if (window.Handle() != nullptr) {
|
||||
AgcDriverReleaseWindow_nid_postfix(window.Handle());
|
||||
window.Destroy();
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
for (int handle = 1; handle < VIDEO_OUT_NUM_MAX; ++handle) {
|
||||
if (outputs[handle]) close(handle);
|
||||
}
|
||||
}
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER);
|
||||
stopped = true;
|
||||
|
|
@ -262,7 +260,7 @@ int VideoOutDriver::Open(int busType) {
|
|||
{
|
||||
std::lock_guard queueLock(flipQueue->mutex);
|
||||
if (flipQueue->failure) std::rethrow_exception(flipQueue->failure);
|
||||
require(!flipQueue->stopping, "open during shutdown");
|
||||
if (flipQueue->stopping || LibcShutdownToken_nid_postfix().stop_requested()) throw ProcessShutdown{};
|
||||
}
|
||||
const int handle = busType + 1;
|
||||
require(handle > 0 && handle < VIDEO_OUT_NUM_MAX, "invalid output bus");
|
||||
|
|
@ -288,6 +286,7 @@ int VideoOutDriver::Open(int busType) {
|
|||
|
||||
bool VideoOutDriver::Close(int handle) {
|
||||
std::lock_guard lock(mutex);
|
||||
if (LibcShutdownToken_nid_postfix().stop_requested()) throw ProcessShutdown{};
|
||||
return close(handle);
|
||||
}
|
||||
|
||||
|
|
@ -322,7 +321,7 @@ std::shared_ptr<VideoOutConfig> VideoOutDriver::GetConfig(int handle) {
|
|||
{
|
||||
std::lock_guard lock(flipQueue->mutex);
|
||||
if (flipQueue->failure) std::rethrow_exception(flipQueue->failure);
|
||||
require(!flipQueue->stopping, "output is shutting down");
|
||||
if (flipQueue->stopping || LibcShutdownToken_nid_postfix().stop_requested()) throw ProcessShutdown{};
|
||||
}
|
||||
std::lock_guard lock(mutex);
|
||||
require(handle > 0 && handle < VIDEO_OUT_NUM_MAX && contexts[handle] != nullptr, "invalid output handle");
|
||||
|
|
@ -337,6 +336,7 @@ bool VideoOutDriver::IsOpen(int handle) {
|
|||
}
|
||||
|
||||
int VideoOutDriver::SubmitFlip(int handle, int index, int flipMode, int64_t flipArg) {
|
||||
if (LibcShutdownToken_nid_postfix().stop_requested()) throw ProcessShutdown{};
|
||||
// A title that does not pace on flipPendingNum can run ahead of the presenter now that the queue
|
||||
// worker no longer waits per flip; a full queue is the documented error, not a failure.
|
||||
if (flipQueue->reservations.load() >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) return VIDEO_OUT_ERROR_FLIP_QUEUE_FULL;
|
||||
|
|
@ -392,7 +392,7 @@ void VideoOutDriver::processFlip(FlipRequest& req) {
|
|||
require(req.cfg->lastFlipVblank <= std::numeric_limits<uint64_t>::max() - interval, "flip interval overflow");
|
||||
const auto target = req.cfg->lastFlipVblank + interval;
|
||||
timing.Mark("validate");
|
||||
req.cfg->vblankCond.wait(lock, [&] { return req.cfg->vblankStatus.count >= target || req.cfg->failure || req.cfg->closing; });
|
||||
req.cfg->vblankCond.wait(lock, req.cfg->shutdownToken, [&] { return req.cfg->vblankStatus.count >= target || req.cfg->failure || req.cfg->closing; });
|
||||
timing.Mark("vblank_wait");
|
||||
checkConfig(*req.cfg);
|
||||
}
|
||||
|
|
@ -481,7 +481,10 @@ void VideoOutDriver::presentLoop(std::stop_token token) {
|
|||
}
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
require(event.type != SDL_QUIT, "window was closed");
|
||||
if (event.type == SDL_QUIT) {
|
||||
LibcRequestExit_nid_postfix(0);
|
||||
throw ProcessShutdown{};
|
||||
}
|
||||
padInput.HandleEvent(event, window);
|
||||
if (window.Handle() != nullptr) mouseInput.HandleEvent(event, SDL_GetWindowID(window.Handle()));
|
||||
}
|
||||
|
|
@ -503,15 +506,7 @@ void VideoOutDriver::presentLoop(std::stop_token token) {
|
|||
}
|
||||
current.reset();
|
||||
}
|
||||
std::list<std::shared_ptr<FlipRequest>> cancelled;
|
||||
{
|
||||
std::lock_guard lock(flipQueue->mutex);
|
||||
cancelled.swap(flipQueue->requests);
|
||||
}
|
||||
if (!cancelled.empty()) {
|
||||
auto error = std::make_exception_ptr(std::runtime_error("VideoOut: pending flip cancelled during shutdown"));
|
||||
for (auto& request : cancelled) request->Fail(error);
|
||||
}
|
||||
} catch (const ProcessShutdown&) {
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "[videoout] presentation failed: %s\n", error.what());
|
||||
std::fflush(stderr);
|
||||
|
|
@ -521,6 +516,16 @@ void VideoOutDriver::presentLoop(std::stop_token token) {
|
|||
std::fflush(stderr);
|
||||
std::terminate();
|
||||
}
|
||||
current.reset();
|
||||
{
|
||||
std::list<std::shared_ptr<FlipRequest>> cancelled;
|
||||
{
|
||||
std::lock_guard lock(flipQueue->mutex);
|
||||
cancelled.swap(flipQueue->requests);
|
||||
}
|
||||
}
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
window.Destroy();
|
||||
}
|
||||
|
||||
void VideoOutDriver::vblankLoop(std::stop_token token) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,16 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_SHUTDOWN_HPP
|
||||
#define CORE_LIBS_PRX_LIBC_INCLUDE_SHUTDOWN_HPP
|
||||
|
||||
#include <stop_token>
|
||||
|
||||
class ProcessShutdown final {};
|
||||
|
||||
extern "C" void LibcRegisterShutdown_nid_postfix(void (*callback)());
|
||||
extern "C" void LibcRunShutdown_nid_postfix();
|
||||
extern "C" std::stop_token LibcShutdownToken_nid_postfix();
|
||||
extern "C" void LibcRequestShutdown_nid_postfix();
|
||||
extern "C" void LibcRequestExit_nid_postfix(int code);
|
||||
extern "C" [[noreturn]] void LibcAwaitExit_nid_postfix();
|
||||
extern "C" [[noreturn]] void LibcExit_nid_no_patch(int code);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <stdexcept>
|
||||
#include "prx/libc/include/Shutdown.hpp"
|
||||
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
|
@ -22,9 +24,35 @@ std::thread::id shutdownThread;
|
|||
bool shutdownStarted = false;
|
||||
bool shutdownFinished = false;
|
||||
std::exception_ptr shutdownFailure;
|
||||
std::stop_source shutdownSource;
|
||||
std::atomic<bool> exitRequested{false};
|
||||
std::atomic<bool> exitStarted{false};
|
||||
|
||||
}
|
||||
|
||||
extern "C" std::stop_token LibcShutdownToken_nid_postfix() {
|
||||
return shutdownSource.get_token();
|
||||
}
|
||||
|
||||
extern "C" void LibcRequestShutdown_nid_postfix() {
|
||||
shutdownSource.request_stop();
|
||||
}
|
||||
|
||||
extern "C" void LibcRequestExit_nid_postfix(int code) {
|
||||
if (exitRequested.exchange(true)) return;
|
||||
LibcRequestShutdown_nid_postfix();
|
||||
std::thread([code] { LibcExit_nid_no_patch(code); }).detach();
|
||||
}
|
||||
|
||||
extern "C" [[noreturn]] void LibcAwaitExit_nid_postfix() {
|
||||
if (!exitRequested.load()) throw ProcessShutdown{};
|
||||
{
|
||||
std::lock_guard lock(shutdownMutex);
|
||||
if (shutdownThread == std::this_thread::get_id()) throw std::runtime_error("libc: exit thread cannot wait for itself");
|
||||
}
|
||||
for (;;) exitRequested.wait(true);
|
||||
}
|
||||
|
||||
extern "C" void LibcRegisterShutdown_nid_postfix(void (*callback)()) {
|
||||
std::lock_guard lock(shutdownMutex);
|
||||
if (callback == nullptr || shutdownStarted) throw std::runtime_error("libc: invalid shutdown registration");
|
||||
|
|
@ -43,6 +71,7 @@ extern "C" void LibcRunShutdown_nid_postfix() {
|
|||
shutdownThread = std::this_thread::get_id();
|
||||
auto callbacks = std::move(shutdownCallbacks);
|
||||
lock.unlock();
|
||||
LibcRequestShutdown_nid_postfix();
|
||||
std::exception_ptr error;
|
||||
for (auto it = callbacks.rbegin(); it != callbacks.rend(); ++it) {
|
||||
try { (*it)(); }
|
||||
|
|
@ -68,6 +97,8 @@ extern "C" {
|
|||
}
|
||||
|
||||
[[noreturn]] void LibcExit_nid_no_patch(int code) {
|
||||
exitRequested.store(true);
|
||||
if (exitStarted.exchange(true)) LibcAwaitExit_nid_postfix();
|
||||
LibcRunShutdown_nid_postfix();
|
||||
std::exit(code);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -355,7 +355,9 @@ void InvalidParameterHandler(const wchar_t*, const wchar_t*, const wchar_t*, uns
|
|||
// Static destruction runs on the thread that ends the process (ExitProcess -> DLL_PROCESS_DETACH),
|
||||
// so its stack names the exit's caller when no libc exit/abort path was taken.
|
||||
struct ExitReporter {
|
||||
~ExitReporter() { ReportBacktrace("process exit (static destruction)"); }
|
||||
~ExitReporter() {
|
||||
if (std::getenv("APS5_TRACE_EXIT") != nullptr) ReportBacktrace("process exit (static destruction)");
|
||||
}
|
||||
} g_exitReporter;
|
||||
|
||||
const bool g_crashReportInstalled = [] {
|
||||
|
|
|
|||
Loading…
Reference in New Issue