Implement Exceptional Signal Handler + Fix Destruction Behavior

An exceptional signal handler allows us to convert an OS signal into a C++ exception, this allows us to alleviate a lot of crashes that would otherwise occur from signals being thrown during execution of games and be able to handle them gracefully.
This commit is contained in:
◱ PixelyIon
2020-11-03 15:14:09 +05:30
committed by ◱ PixelyIon
parent 3cde568c51
commit 668f623256
45 changed files with 692 additions and 312 deletions

View File

@ -1,117 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <common.h>
namespace skyline::gpu {
/**
* @brief An efficient consumer-producer structure with internal synchronization
*/
template<typename Type>
class CircularQueue {
private:
std::vector<u8> vector; //!< The internal vector holding the circular queue's data, we use a byte vector due to the default item construction/destruction semantics not being appropriate for a circular buffer
Type *start{reinterpret_cast<Type *>(vector.begin().base())}; //!< The start/oldest element of the queue
Type *end{reinterpret_cast<Type *>(vector.begin().base())}; //!< The end/newest element of the queue
std::mutex consumptionMutex;
std::condition_variable consumeCondition;
std::mutex productionMutex;
std::condition_variable produceCondition;
public:
CircularQueue(size_t size) : vector(size * sizeof(Type)) {}
~CircularQueue() {
ssize_t size{};
if (start < end)
size = end - start;
else
size = (reinterpret_cast<Type *>(vector.end().base()) - start) + (end - reinterpret_cast<Type *>(vector.begin().base()));
while (size--) {
std::destroy_at(start);
if (start + 1 == reinterpret_cast<Type *>(vector.end().base()))
start = reinterpret_cast<Type *>(vector.begin().base());
}
}
/**
* @brief A blocking for-each that runs on every item and waits till new items to run on them as well
* @param function A function that is called for each item (with the only parameter as a reference to that item)
*/
template<typename F>
[[noreturn]] inline void Process(F function) {
while (true) {
if (start == end) {
std::unique_lock lock(productionMutex);
produceCondition.wait(lock, [this]() { return start != end; });
}
ssize_t size{};
if (start < end)
size = end - start;
else
size = (reinterpret_cast<Type *>(vector.end().base()) - start) + (end - reinterpret_cast<Type *>(vector.begin().base()));
while (size--) {
function(*start);
if (start + 1 != reinterpret_cast<Type *>(vector.end().base()))
start++;
else
start = reinterpret_cast<Type *>(vector.begin().base());
}
consumeCondition.notify_one();
}
}
inline void Push(const Type &item) {
std::unique_lock lock(productionMutex);
auto next{end + 1};
next = (next == reinterpret_cast<Type *>(vector.end().base())) ? reinterpret_cast<Type *>(vector.begin().base()) : next;
if (next == start) {
std::unique_lock consumeLock(consumptionMutex);
consumeCondition.wait(consumeLock, [=]() { return next != start; });
}
*next = item;
end = next;
produceCondition.notify_one();
}
inline void Append(span <Type> buffer) {
std::unique_lock lock(productionMutex);
for (auto &item : buffer) {
auto next{end + 1};
next = (next == reinterpret_cast<Type *>(vector.end().base())) ? reinterpret_cast<Type *>(vector.begin().base()) : next;
if (next == start) {
std::unique_lock consumeLock(consumptionMutex);
consumeCondition.wait(consumeLock, [=]() { return next != start; });
}
*next = item;
end = next;
}
produceCondition.notify_one();
}
/**
* @brief Appends a buffer with an alternative input type while supplied transformation function
* @param tranformation A function that takes in an item of TransformedType as input and returns an item of Type
*/
template<typename TransformedType, typename Transformation>
inline void AppendTranform(span <TransformedType> buffer, Transformation transformation) {
std::unique_lock lock(productionMutex);
auto next{end};
for (auto &item : buffer) {
end = (end == reinterpret_cast<Type *>(vector.end().base())) ? reinterpret_cast<Type *>(vector.begin().base()) : end;
if (start == end + 1) {
std::unique_lock consumeLock(consumptionMutex);
consumeCondition.wait(consumeLock, [=]() { return start != end + 1; });
}
*(end++) = transformation(item);
}
produceCondition.notify_one();
}
};
}

View File

@ -1,6 +1,8 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <common/signal.h>
#include <kernel/types/KProcess.h>
#include <gpu.h>
#include <gpu/engines/maxwell_3d.h>
#include "gpfifo.h"
@ -86,19 +88,38 @@ namespace skyline::gpu::gpfifo {
void GPFIFO::Run() {
pthread_setname_np(pthread_self(), "GPFIFO");
pushBuffers->Process([this](PushBuffer& pushBuffer){
if (pushBuffer.segment.empty())
pushBuffer.Fetch(state.gpu->memoryManager);
Process(pushBuffer.segment);
});
try {
signal::SetSignalHandler({SIGINT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV}, signal::ExceptionalSignalHandler);
pushBuffers->Process([this](PushBuffer &pushBuffer) {
if (pushBuffer.segment.empty())
pushBuffer.Fetch(state.gpu->memoryManager);
Process(pushBuffer.segment);
});
} catch (const signal::SignalException &e) {
if (e.signal != SIGINT) {
state.logger->Write(Logger::LogLevel::Error, e.what());
signal::BlockSignal({SIGINT});
state.process->mainThread->Kill(false);
}
} catch (const std::exception &e) {
state.logger->Write(Logger::LogLevel::Error, e.what());
state.process->mainThread->Kill(false);
}
}
void GPFIFO::Push(span<GpEntry> entries) {
bool beforeBarrier{true};
pushBuffers->AppendTranform(entries, [&beforeBarrier, this](const GpEntry& entry){
pushBuffers->AppendTranform(entries, [&beforeBarrier, this](const GpEntry &entry) {
if (entry.sync == GpEntry::Sync::Wait)
beforeBarrier = false;
return PushBuffer(entry, state.gpu->memoryManager, beforeBarrier);
});
}
GPFIFO::~GPFIFO() {
if (thread.joinable()) {
pthread_kill(thread.native_handle(), SIGINT);
thread.join();
}
}
}

View File

@ -3,7 +3,7 @@
#pragma once
#include "circular_queue.h"
#include <common/circular_queue.h>
#include "engines/gpfifo.h"
#include "memory_manager.h"
@ -163,6 +163,8 @@ namespace skyline::gpu {
public:
GPFIFO(const DeviceState &state) : state(state), gpfifoEngine(state) {}
~GPFIFO();
/**
* @param numBuffers The amount of push-buffers to allocate in the circular buffer
*/

View File

@ -5,8 +5,8 @@
#include "jvm.h"
#include "presentation_engine.h"
extern skyline::u16 fps;
extern skyline::u32 frametime;
extern skyline::u16 Fps;
extern skyline::u32 FrameTime;
namespace skyline::gpu {
PresentationEngine::PresentationEngine(const DeviceState &state) : state(state), vsyncEvent(std::make_shared<kernel::type::KEvent>(state)), bufferEvent(std::make_shared<kernel::type::KEvent>(state)) {}
@ -29,7 +29,7 @@ namespace skyline::gpu {
if (!env->IsSameObject(newSurface, nullptr))
surface = env->NewGlobalRef(newSurface);
if (surface) {
window = ANativeWindow_fromSurface(state.jvm->GetEnv(), surface);
window = ANativeWindow_fromSurface(env, surface);
ANativeWindow_acquire(window);
resolution.width = static_cast<u32>(ANativeWindow_getWidth(window));
resolution.height = static_cast<u32>(ANativeWindow_getHeight(window));
@ -73,8 +73,8 @@ namespace skyline::gpu {
if (frameTimestamp) {
auto now{util::GetTimeNs()};
frametime = static_cast<u32>((now - frameTimestamp) / 10000); // frametime / 100 is the real ms value, this is to retain the first two decimals
fps = static_cast<u16>(constant::NsInSecond / (now - frameTimestamp));
FrameTime = static_cast<u32>((now - frameTimestamp) / 10000); // frametime / 100 is the real ms value, this is to retain the first two decimals
Fps = static_cast<u16>(constant::NsInSecond / (now - frameTimestamp));
frameTimestamp = now;
} else {

View File

@ -14,7 +14,7 @@ namespace skyline::gpu {
const DeviceState &state;
std::mutex windowMutex;
std::condition_variable windowConditional;
jobject surface; //!< The Surface object backing the ANativeWindow
jobject surface{}; //!< The Surface object backing the ANativeWindow
u64 frameTimestamp{}; //!< The timestamp of the last frame being shown
public: