Run Guest on Main Emulator Thread + Remove Mutex/GroupMutex + Introduce PresentationEngine

This commit is contained in:
◱ PixelyIon 2020-10-28 21:30:39 +05:30 committed by ◱ PixelyIon
parent 779884edcf
commit 3cde568c51
34 changed files with 380 additions and 418 deletions

View File

@ -9,6 +9,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(source_DIR ${CMAKE_SOURCE_DIR}/src/main/cpp)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-strict-aliasing")
set(CMAKE_CXX_FLAGS_RELEASE "-Ofast -flto=full -Wno-unused-command-line-argument")
if (uppercase_CMAKE_BUILD_TYPE STREQUAL "RELEASE")
add_compile_definitions(NDEBUG)
@ -48,7 +49,7 @@ add_library(skyline SHARED
${source_DIR}/skyline/audio/track.cpp
${source_DIR}/skyline/audio/resampler.cpp
${source_DIR}/skyline/audio/adpcm_decoder.cpp
${source_DIR}/skyline/gpu.cpp
${source_DIR}/skyline/gpu/presentation_engine.cpp
${source_DIR}/skyline/gpu/macro_interpreter.cpp
${source_DIR}/skyline/gpu/memory_manager.cpp
${source_DIR}/skyline/gpu/gpfifo.cpp

View File

@ -9,34 +9,19 @@
#include "skyline/common.h"
#include "skyline/os.h"
#include "skyline/jvm.h"
#include "skyline/gpu.h"
#include "skyline/input.h"
bool Halt;
jobject Surface;
skyline::GroupMutex JniMtx;
skyline::u16 fps;
skyline::u32 frametime;
std::weak_ptr<skyline::gpu::GPU> gpuWeak;
std::weak_ptr<skyline::input::Input> inputWeak;
void signalHandler(int signal) {
__android_log_print(ANDROID_LOG_FATAL, "emu-cpp", "Halting program due to signal: %s", strsignal(signal));
exit(signal);
}
extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(JNIEnv *env, jobject instance, jstring romUriJstring, jint romType, jint romFd, jint preferenceFd, jstring appFilesPathJstring) {
Halt = false;
fps = 0;
frametime = 0;
/*
std::signal(SIGTERM, signalHandler);
std::signal(SIGSEGV, signalHandler);
std::signal(SIGINT, signalHandler);
std::signal(SIGILL, signalHandler);
std::signal(SIGABRT, signalHandler);
std::signal(SIGFPE, signalHandler);
*/
pthread_setname_np(pthread_self(), "EmuMain");
setpriority(PRIO_PROCESS, static_cast<id_t>(gettid()), -8); // Set the priority of this process to the highest value
auto jvmManager{std::make_shared<skyline::JvmManager>(env, instance)};
@ -51,6 +36,7 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(
try {
skyline::kernel::OS os(jvmManager, logger, settings, std::string(appFilesPath));
gpuWeak = os.state.gpu;
inputWeak = os.state.input;
jvmManager->InitializeControllers();
env->ReleaseStringUTFChars(appFilesPathJstring, appFilesPath);
@ -76,21 +62,16 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(
close(romFd);
}
extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_setHalt(JNIEnv *, jobject, jboolean halt) {
JniMtx.lock(skyline::GroupMutex::Group::Group2);
Halt = halt;
JniMtx.unlock();
extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_exitGuest(JNIEnv *, jobject, jboolean halt) {
// TODO
exit(0);
}
extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_setSurface(JNIEnv *env, jobject, jobject surface) {
JniMtx.lock(skyline::GroupMutex::Group::Group2);
if (!env->IsSameObject(Surface, nullptr))
env->DeleteGlobalRef(Surface);
if (!env->IsSameObject(surface, nullptr))
Surface = env->NewGlobalRef(surface);
else
Surface = surface;
JniMtx.unlock();
extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_setSurface(JNIEnv *, jobject, jobject surface) {
auto gpu{gpuWeak.lock()};
while (!gpu)
gpu = gpuWeak.lock();
gpu->presentation.UpdateSurface(surface);
}
extern "C" JNIEXPORT jint Java_emu_skyline_EmulationActivity_getFps(JNIEnv *, jobject) {
@ -112,38 +93,32 @@ extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_updateContr
}
extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setButtonState(JNIEnv *, jobject, jint index, jlong mask, jboolean pressed) {
try {
auto input{inputWeak.lock()};
if (!input)
return; // We don't mind if we miss button updates while input hasn't been initialized
auto device{input->npad.controllers[index].device};
if (device)
device->SetButtonState(skyline::input::NpadButton{.raw = static_cast<skyline::u64>(mask)}, pressed);
} catch (const std::bad_weak_ptr &) {
// We don't mind if we miss button updates while input hasn't been initialized
}
}
extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setAxisValue(JNIEnv *, jobject, jint index, jint axis, jint value) {
try {
auto input{inputWeak.lock()};
if (!input)
return; // We don't mind if we miss axis updates while input hasn't been initialized
auto device{input->npad.controllers[index].device};
if (device)
device->SetAxisValue(static_cast<skyline::input::NpadAxisId>(axis), value);
} catch (const std::bad_weak_ptr &) {
// We don't mind if we miss axis updates while input hasn't been initialized
}
}
extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setTouchState(JNIEnv *env, jobject, jintArray pointsJni) {
try {
using Point = skyline::input::TouchScreenPoint;
auto input{inputWeak.lock()};
if (!input)
return; // We don't mind if we miss touch updates while input hasn't been initialized
jboolean isCopy{false};
skyline::span<Point> points(reinterpret_cast<Point *>(env->GetIntArrayElements(pointsJni, &isCopy)), env->GetArrayLength(pointsJni) / (sizeof(Point) / sizeof(jint)));
input->touch.SetState(points);
env->ReleaseIntArrayElements(pointsJni, reinterpret_cast<jint *>(points.data()), JNI_ABORT);
} catch (const std::bad_weak_ptr &) {
// We don't mind if we miss axis updates while input hasn't been initialized
}
}

View File

@ -14,7 +14,7 @@ namespace skyline::audio {
oboe::AudioStreamBuilder builder;
oboe::ManagedStream outputStream;
std::vector<std::shared_ptr<AudioTrack>> audioTracks;
Mutex trackLock; //!< Synchronizes modifications to the audio tracks
std::mutex trackLock; //!< Synchronizes modifications to the audio tracks
public:
Audio(const DeviceState &state);

View File

@ -19,14 +19,13 @@ namespace skyline::audio {
Type *start{array.begin()}; //!< The start/oldest element of the internal array
Type *end{array.begin()}; //!< The end/newest element of the internal array
bool empty{true}; //!< If the buffer is full or empty, as start == end can mean either
Mutex mtx; //!< Synchronizes buffer operations so they don't overlap
std::mutex mtx; //!< Synchronizes buffer operations so they don't overlap
public:
/**
* @brief Reads data from this buffer into the specified buffer
* @param address The address to write buffer data into
* @param maxSize The maximum amount of data to write in units of Type
* @param copyFunction If this is specified, then this is called rather than memcpy
* @param copyOffset The offset into the buffer after which to use memcpy rather than copyFunction, -1 will use it for the entire buffer
* @return The amount of data written into the input buffer in units of Type
*/
inline size_t Read(span<Type> buffer, void copyFunction(Type *, Type *) = {}, ssize_t copyOffset = -1) {

View File

@ -20,7 +20,7 @@ namespace skyline::audio {
public:
CircularBuffer<i16, constant::SampleRate * constant::ChannelCount * 10> samples; //!< A circular buffer with all appended audio samples
Mutex bufferLock; //!< Synchronizes appending to audio buffers
std::mutex bufferLock; //!< Synchronizes appending to audio buffers
AudioOutState playbackState{AudioOutState::Stopped}; //!< The current state of playback
u64 sampleCounter{}; //!< A counter used for tracking when buffers have been played and can be released

View File

@ -11,61 +11,6 @@
#include "kernel/types/KThread.h"
namespace skyline {
void Mutex::lock() {
while (true) {
for (int i{}; i < 1000; ++i) {
if (!flag.test_and_set(std::memory_order_acquire))
return;
asm volatile("yield");
}
sched_yield();
}
}
void GroupMutex::lock(Group group) {
auto none{Group::None};
constexpr u64 timeout{100}; // The timeout in ns
auto end{util::GetTimeNs() + timeout};
while (true) {
if (next == group) {
if (flag == group) {
std::lock_guard lock(mtx);
if (flag == group) {
auto groupT{group};
next.compare_exchange_strong(groupT, Group::None);
num++;
return;
}
} else {
flag.compare_exchange_weak(none, group);
}
} else if (flag == group && (next == Group::None || util::GetTimeNs() >= end)) {
std::lock_guard lock(mtx);
if (flag == group) {
num++;
return;
}
} else {
next.compare_exchange_weak(none, group);
}
none = Group::None;
asm volatile("yield");
}
}
void GroupMutex::unlock() {
std::lock_guard lock(mtx);
if (!--num)
flag.exchange(next);
}
Settings::Settings(int fd) {
tinyxml2::XMLDocument pref;
@ -125,6 +70,7 @@ namespace skyline {
Logger::Logger(const std::string &path, LogLevel configLevel) : configLevel(configLevel) {
logFile.open(path, std::ios::trunc);
UpdateTag();
WriteHeader("Logging started");
}
@ -133,6 +79,17 @@ namespace skyline {
logFile.flush();
}
thread_local static std::string logTag, threadName;
void Logger::UpdateTag() {
std::array<char, 16> name;
if (!pthread_getname_np(pthread_self(), name.data(), name.size()))
threadName = name.data();
else
threadName = "unk";
logTag = std::string("emu-cpp-") + threadName;
}
void Logger::WriteHeader(const std::string &str) {
__android_log_write(ANDROID_LOG_INFO, "emu-cpp", str.c_str());
@ -144,14 +101,17 @@ namespace skyline {
constexpr std::array<char, 5> levelCharacter{'0', '1', '2', '3', '4'}; // The LogLevel as written out to a file
constexpr std::array<int, 5> levelAlog{ANDROID_LOG_ERROR, ANDROID_LOG_WARN, ANDROID_LOG_INFO, ANDROID_LOG_DEBUG, ANDROID_LOG_VERBOSE}; // This corresponds to LogLevel and provides it's equivalent for NDK Logging
__android_log_write(levelAlog[static_cast<u8>(level)], "emu-cpp", str.c_str());
if (logTag.empty())
UpdateTag();
__android_log_write(levelAlog[static_cast<u8>(level)], logTag.c_str(), str.c_str());
for (auto &character : str)
if (character == '\n')
character = '\\';
std::lock_guard guard(mtx);
logFile << "1|" << levelCharacter[static_cast<u8>(level)] << '|' << std::dec << pthread_self() << '|' << str << '\n';
logFile << "1|" << levelCharacter[static_cast<u8>(level)] << '|' << threadName << '|' << str << '\n';
}
DeviceState::DeviceState(kernel::OS *os, std::shared_ptr<kernel::type::KProcess> &process, std::shared_ptr<JvmManager> jvmManager, std::shared_ptr<Settings> settings, std::shared_ptr<Logger> logger)

View File

@ -362,73 +362,13 @@ namespace skyline {
template<class Container>
span(const Container &) -> span<const typename Container::value_type>;
/**
* @brief The Mutex class is a wrapper around an atomic bool used for low-contention synchronization
*/
class Mutex {
std::atomic_flag flag = ATOMIC_FLAG_INIT; //!< An atomic flag to hold the state of the mutex
public:
/**
* @brief Wait on and lock the mutex
*/
void lock();
/**
* @brief Try to lock the mutex if it is unlocked else return
* @return If the mutex was successfully locked or not
*/
inline bool try_lock() {
return !flag.test_and_set(std::memory_order_acquire);
}
/**
* @brief Unlock the mutex if it is held by this thread
*/
inline void unlock() {
flag.clear(std::memory_order_release);
}
};
/**
* @brief The GroupMutex class is a special type of mutex that allows two groups of users and only allows one group to run in parallel
*/
class GroupMutex {
public:
/**
* @brief All the possible owners of the mutex
*/
enum class Group : u8 {
None = 0, //!< No group owns this mutex
Group1 = 1, //!< Group 1 owns this mutex
Group2 = 2, //!< Group 2 owns this mutex
};
/**
* @brief Wait on and lock the mutex
*/
void lock(Group group = Group::Group1);
/**
* @brief Unlock the mutex
* @note Undefined behavior in case unlocked by thread in non-owner group
*/
void unlock();
private:
std::atomic<Group> flag{Group::None}; //!< An atomic flag to hold which group holds the mutex
std::atomic<Group> next{Group::None}; //!< An atomic flag to hold which group will hold the mutex next
std::atomic<u8> num{}; //!< An atomic u8 keeping track of how many users are holding the mutex
Mutex mtx; //!< A mutex to lock before changing of num and flag
};
/**
* @brief The Logger class is to write log output to file and logcat
*/
class Logger {
private:
std::ofstream logFile; //!< An output stream to the log file
Mutex mtx; //!< A mutex to lock before logging anything
std::mutex mtx; //!< A mutex to lock before logging anything
public:
enum class LogLevel {
@ -452,6 +392,11 @@ namespace skyline {
*/
~Logger();
/**
* @brief Update the tag in log messages with a new thread name
*/
static void UpdateTag();
/**
* @brief Writes a header, should only be used for emulation starting and ending
*/

View File

@ -1,78 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include "gpu.h"
#include "jvm.h"
#include <kernel/types/KProcess.h>
#include <android/native_window_jni.h>
extern bool Halt;
extern jobject Surface;
extern skyline::u16 fps;
extern skyline::u32 frametime;
namespace skyline::gpu {
GPU::GPU(const DeviceState &state) : state(state), memoryManager(state), gpfifo(state), fermi2D(std::make_shared<engine::Engine>(state)), keplerMemory(std::make_shared<engine::Engine>(state)), maxwell3D(std::make_shared<engine::Maxwell3D>(state)), maxwellCompute(std::make_shared<engine::Engine>(state)), maxwellDma(std::make_shared<engine::Engine>(state)), window(ANativeWindow_fromSurface(state.jvm->GetEnv(), Surface)), vsyncEvent(std::make_shared<kernel::type::KEvent>(state)), bufferEvent(std::make_shared<kernel::type::KEvent>(state)) {
ANativeWindow_acquire(window);
resolution.width = static_cast<u32>(ANativeWindow_getWidth(window));
resolution.height = static_cast<u32>(ANativeWindow_getHeight(window));
format = ANativeWindow_getFormat(window);
vsyncEvent->Signal();
}
GPU::~GPU() {
ANativeWindow_release(window);
}
void GPU::Loop() {
gpfifo.Run();
vsyncEvent->Signal();
if (surfaceUpdate) {
if (Surface == nullptr)
return;
window = ANativeWindow_fromSurface(state.jvm->GetEnv(), Surface);
ANativeWindow_acquire(window);
resolution.width = static_cast<u32>(ANativeWindow_getWidth(window));
resolution.height = static_cast<u32>(ANativeWindow_getHeight(window));
format = ANativeWindow_getFormat(window);
surfaceUpdate = false;
} else if (Surface == nullptr) {
surfaceUpdate = true;
return;
}
if (!presentationQueue.empty()) {
auto &texture{presentationQueue.front()};
presentationQueue.pop();
auto textureFormat{texture->GetAndroidFormat()};
if (resolution != texture->dimensions || textureFormat != format) {
ANativeWindow_setBuffersGeometry(window, texture->dimensions.width, texture->dimensions.height, textureFormat);
resolution = texture->dimensions;
format = textureFormat;
}
ANativeWindow_Buffer windowBuffer;
ARect rect;
ANativeWindow_lock(window, &windowBuffer, &rect);
std::memcpy(windowBuffer.bits, texture->backing.data(), texture->backing.size());
ANativeWindow_unlockAndPost(window);
vsyncEvent->Signal();
texture->releaseCallback();
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));
frameTimestamp = now;
} else {
frameTimestamp = util::GetTimeNs();
}
}
}
}

View File

@ -3,11 +3,10 @@
#pragma once
#include <android/native_window.h>
#include "services/nvdrv/devices/nvmap.h"
#include "gpu/gpfifo.h"
#include "gpu/syncpoint.h"
#include "gpu/engines/maxwell_3d.h"
#include "gpu/presentation_engine.h"
namespace skyline::gpu {
/**
@ -15,18 +14,11 @@ namespace skyline::gpu {
*/
class GPU {
private:
ANativeWindow *window; //!< The ANativeWindow that is presented to
const DeviceState &state;
bool surfaceUpdate{}; //!< If the surface needs to be updated
u64 frameTimestamp{}; //!< The timestamp of the last frame being shown
public:
std::queue<std::shared_ptr<PresentationTexture>> presentationQueue; //!< A queue of all the PresentationTextures to be posted to the display
texture::Dimensions resolution{}; //!< The resolution of the surface
i32 format{}; //!< The format of the display window
std::shared_ptr<kernel::type::KEvent> vsyncEvent; //!< This KEvent is triggered every time a frame is drawn
std::shared_ptr<kernel::type::KEvent> bufferEvent; //!< This KEvent is triggered every time a buffer is freed
vmm::MemoryManager memoryManager; //!< The GPU Virtual Memory Manager
PresentationEngine presentation;
vmm::MemoryManager memoryManager;
std::shared_ptr<engine::Engine> fermi2D;
std::shared_ptr<engine::Maxwell3D> maxwell3D;
std::shared_ptr<engine::Engine> maxwellCompute;
@ -35,10 +27,6 @@ namespace skyline::gpu {
gpfifo::GPFIFO gpfifo;
std::array<Syncpoint, constant::MaxHwSyncpointCount> syncpoints{};
GPU(const DeviceState &state);
~GPU();
void Loop();
inline GPU(const DeviceState &state) : state(state), presentation(state), memoryManager(state), gpfifo(state), fermi2D(std::make_shared<engine::Engine>(state)), keplerMemory(std::make_shared<engine::Engine>(state)), maxwell3D(std::make_shared<engine::Maxwell3D>(state)), maxwellCompute(std::make_shared<engine::Engine>(state)), maxwellDma(std::make_shared<engine::Engine>(state)) {}
};
}

View File

@ -0,0 +1,117 @@
// 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

@ -3,7 +3,6 @@
#pragma once
#include <gpu/texture.h>
#include <gpu/macro_interpreter.h>
#include "engine.h"

View File

@ -77,27 +77,28 @@ namespace skyline::gpu::gpfifo {
}
}
void GPFIFO::Initialize(size_t numBuffers) {
if (pushBuffers)
throw exception("GPFIFO Initialization cannot be done multiple times");
pushBuffers.emplace(numBuffers);
thread = std::thread(&GPFIFO::Run, this);
}
void GPFIFO::Run() {
std::lock_guard lock(pushBufferQueueLock);
while (!pushBufferQueue.empty()) {
auto pushBuffer{pushBufferQueue.front()};
pthread_setname_np(pthread_self(), "GPFIFO");
pushBuffers->Process([this](PushBuffer& pushBuffer){
if (pushBuffer.segment.empty())
pushBuffer.Fetch(state.gpu->memoryManager);
Process(pushBuffer.segment);
pushBufferQueue.pop();
}
});
}
void GPFIFO::Push(span<GpEntry> entries) {
std::lock_guard lock(pushBufferQueueLock);
bool beforeBarrier{false};
for (const auto &entry : entries) {
bool beforeBarrier{true};
pushBuffers->AppendTranform(entries, [&beforeBarrier, this](const GpEntry& entry){
if (entry.sync == GpEntry::Sync::Wait)
beforeBarrier = false;
pushBufferQueue.emplace(PushBuffer(entry, state.gpu->memoryManager, beforeBarrier));
}
return PushBuffer(entry, state.gpu->memoryManager, beforeBarrier);
});
}
}

View File

@ -3,7 +3,7 @@
#pragma once
#include <queue>
#include "circular_queue.h"
#include "engines/gpfifo.h"
#include "memory_manager.h"
@ -147,8 +147,8 @@ namespace skyline::gpu {
const DeviceState &state;
engine::GPFIFO gpfifoEngine; //!< The engine for processing GPFIFO method calls
std::array<std::shared_ptr<engine::Engine>, 8> subchannels;
std::queue<PushBuffer> pushBufferQueue;
skyline::Mutex pushBufferQueueLock; //!< Synchronizes pushbuffer queue insertions as the GPU is multi-threaded
std::optional<CircularQueue<PushBuffer>> pushBuffers;
std::thread thread; //!< The thread that manages processing of push-buffers
/**
* @brief Processes a pushbuffer segment, calling methods as needed
@ -163,6 +163,11 @@ namespace skyline::gpu {
public:
GPFIFO(const DeviceState &state) : state(state), gpfifoEngine(state) {}
/**
* @param numBuffers The amount of push-buffers to allocate in the circular buffer
*/
void Initialize(size_t numBuffers);
/**
* @brief Executes all pending entries in the FIFO
*/

View File

@ -0,0 +1,84 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <android/native_window_jni.h>
#include "jvm.h"
#include "presentation_engine.h"
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)) {}
PresentationEngine::~PresentationEngine() {
if (window)
ANativeWindow_release(window);
auto env{state.jvm->GetEnv()};
if (!env->IsSameObject(surface, nullptr))
env->DeleteGlobalRef(surface);
}
void PresentationEngine::UpdateSurface(jobject newSurface) {
std::lock_guard lock(windowMutex);
auto env{state.jvm->GetEnv()};
if (!env->IsSameObject(surface, nullptr)) {
env->DeleteGlobalRef(surface);
surface = nullptr;
}
if (!env->IsSameObject(newSurface, nullptr))
surface = env->NewGlobalRef(newSurface);
if (surface) {
window = ANativeWindow_fromSurface(state.jvm->GetEnv(), surface);
ANativeWindow_acquire(window);
resolution.width = static_cast<u32>(ANativeWindow_getWidth(window));
resolution.height = static_cast<u32>(ANativeWindow_getHeight(window));
format = ANativeWindow_getFormat(window);
windowConditional.notify_all();
} else {
window = nullptr;
}
}
void PresentationEngine::Present(const std::shared_ptr<Texture> &texture) {
std::unique_lock lock(windowMutex);
if (!window)
windowConditional.wait(lock, [this]() { return window; });
auto textureFormat{[&texture]() {
switch (texture->format.vkFormat) {
case vk::Format::eR8G8B8A8Unorm:
return WINDOW_FORMAT_RGBA_8888;
case vk::Format::eR5G6B5UnormPack16:
return WINDOW_FORMAT_RGB_565;
default:
throw exception("Cannot find corresponding Android surface format");
}
}()};
if (resolution != texture->dimensions || textureFormat != format) {
ANativeWindow_setBuffersGeometry(window, texture->dimensions.width, texture->dimensions.height, textureFormat);
resolution = texture->dimensions;
format = textureFormat;
}
ANativeWindow_Buffer buffer;
ARect rect;
ANativeWindow_lock(window, &buffer, &rect);
std::memcpy(buffer.bits, texture->backing.data(), texture->backing.size());
ANativeWindow_unlockAndPost(window);
vsyncEvent->Signal();
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));
frameTimestamp = now;
} else {
frameTimestamp = util::GetTimeNs();
}
}
}

View File

@ -0,0 +1,36 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <kernel/types/KEvent.h>
#include "texture.h"
struct ANativeWindow;
namespace skyline::gpu {
class PresentationEngine {
private:
const DeviceState &state;
std::mutex windowMutex;
std::condition_variable windowConditional;
jobject surface; //!< The Surface object backing the ANativeWindow
u64 frameTimestamp{}; //!< The timestamp of the last frame being shown
public:
texture::Dimensions resolution{};
i32 format{};
std::shared_ptr<kernel::type::KEvent> vsyncEvent; //!< Signalled every time a frame is drawn
std::shared_ptr<kernel::type::KEvent> bufferEvent; //!< Signalled every time a buffer is freed
PresentationEngine(const DeviceState &state);
~PresentationEngine();
void UpdateSurface(jobject newSurface);
void Present(const std::shared_ptr<Texture>& texture);
ANativeWindow *window{};
};
}

View File

@ -21,7 +21,7 @@ namespace skyline {
std::function<void()> callback; //!< The callback to do after the wait has ended
};
Mutex waiterLock; //!< Synchronizes insertions and deletions of waiters
std::mutex waiterLock; //!< Synchronizes insertions and deletions of waiters
std::map<u64, Waiter> waiterMap;
u64 nextWaiterId{1};

View File

@ -17,14 +17,6 @@ namespace skyline::gpu {
return sharedHost;
}
std::shared_ptr<PresentationTexture> GuestTexture::InitializePresentationTexture() {
if (!host.expired())
throw exception("Trying to create multiple PresentationTexture objects from a single GuestTexture");
auto presentation{std::make_shared<PresentationTexture>(state, shared_from_this(), dimensions, format)};
host = std::static_pointer_cast<Texture>(presentation);
return presentation;
}
Texture::Texture(const DeviceState &state, std::shared_ptr<GuestTexture> guest, texture::Dimensions dimensions, texture::Format format, texture::Swizzle swizzle) : state(state), guest(guest), dimensions(dimensions), format(format), swizzle(swizzle) {
SynchronizeHost();
}
@ -92,17 +84,4 @@ namespace skyline::gpu {
std::memcpy(output, pointer, size);
}
}
PresentationTexture::PresentationTexture(const DeviceState &state, const std::shared_ptr<GuestTexture> &guest, const texture::Dimensions &dimensions, const texture::Format &format, const std::function<void()> &releaseCallback) : releaseCallback(releaseCallback), Texture(state, guest, dimensions, format, {}) {}
i32 PresentationTexture::GetAndroidFormat() {
switch (format.vkFormat) {
case vk::Format::eR8G8B8A8Unorm:
return WINDOW_FORMAT_RGBA_8888;
case vk::Format::eR5G6B5UnormPack16:
return WINDOW_FORMAT_RGB_565;
default:
throw exception("GetAndroidFormat: Cannot find corresponding Android surface format");
}
}
}

View File

@ -114,7 +114,6 @@ namespace skyline {
}
class Texture;
class PresentationTexture;
/**
* @brief A texture present in guest memory, it can be used to create a corresponding Texture object for usage on the host
@ -146,11 +145,6 @@ namespace skyline {
* @note There can only be one host texture for a corresponding guest texture
*/
std::shared_ptr<Texture> InitializeTexture(std::optional<texture::Format> format = std::nullopt, std::optional<texture::Dimensions> dimensions = std::nullopt, texture::Swizzle swizzle = {});
protected:
std::shared_ptr<PresentationTexture> InitializePresentationTexture();
friend service::hosbinder::GraphicBufferProducer;
};
/**
@ -203,20 +197,5 @@ namespace skyline {
*/
void SynchronizeGuest();
};
/**
* @brief A texture object alongside a release callback used for display presentation
*/
class PresentationTexture : public Texture {
public:
std::function<void()> releaseCallback; //!< The release callback after this texture has been displayed
PresentationTexture(const DeviceState &state, const std::shared_ptr<GuestTexture> &guest, const texture::Dimensions &dimensions, const texture::Format &format, const std::function<void()> &releaseCallback = {});
/**
* @return The corresponding Android surface format for the current texture format
*/
i32 GetAndroidFormat();
};
}
}

View File

@ -24,6 +24,6 @@ namespace skyline::input {
NpadManager npad;
TouchManager touch;
Input(const DeviceState &state) : state(state), kHid(std::make_shared<kernel::type::KSharedMemory>(state, sizeof(HidSharedMemory))), hid(reinterpret_cast<HidSharedMemory *>(kHid->kernel.ptr)), npad(state, hid), touch(state, hid) {}
inline Input(const DeviceState &state) : state(state), kHid(std::make_shared<kernel::type::KSharedMemory>(state, sizeof(HidSharedMemory))), hid(reinterpret_cast<HidSharedMemory *>(kHid->kernel.ptr)), npad(state, hid), touch(state, hid) {}
};
}

View File

@ -33,11 +33,14 @@ namespace skyline::kernel {
throw exception("VMM initialization with unknown address space");
}
// Search for a suitable carveout in host AS to fit the guest AS inside of
std::ifstream mapsFile("/proc/self/maps");
std::string maps((std::istreambuf_iterator<char>(mapsFile)), std::istreambuf_iterator<char>());
size_t line{}, start{}, alignedStart{};
size_t line{}, start{1ULL << 35}, alignedStart{1ULL << 35}; // 1 << 35 is where QC KGSL (Kernel Graphic Support Layer) maps down from, we skip over this or KGSL goes OOM
do {
auto end{util::HexStringToInt<u64>(std::string_view(maps.data() + line, sizeof(u64) * 2))};
if (end < start)
continue;
if (end - start > base.size + (alignedStart - start)) { // We don't want to overflow if alignedStart > start
base.address = alignedStart;
break;
@ -54,10 +57,21 @@ namespace skyline::kernel {
mmap(reinterpret_cast<void *>(base.address), base.size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
chunks = {ChunkDescriptor{
chunks = {
ChunkDescriptor{
.ptr = reinterpret_cast<u8 *>(addressSpace.address),
.size = addressSpace.size,
.size = base.address - addressSpace.address,
.state = memory::states::Reserved,
},
ChunkDescriptor{
.ptr = reinterpret_cast<u8 *>(base.address),
.size = base.size,
.state = memory::states::Unmapped,
},
ChunkDescriptor{
.ptr = reinterpret_cast<u8 *>(base.address + base.size),
.size = addressSpace.size - (base.address + base.size),
.state = memory::states::Reserved,
}};
}

View File

@ -196,7 +196,7 @@ namespace skyline::kernel::svc {
.ipcRefCount = 0,
};
state.logger->Debug("svcQueryMemory: Pointer: 0x{:X}, Region Start: 0x{:X}, Size: 0x{:X}, Type: 0x{:X}, Is Uncached: {}, Permissions: {}{}{}", pointer, memInfo.address, memInfo.size, memInfo.type, static_cast<bool>(chunk->attributes.isUncached), chunk->permission.r ? 'R' : '-', chunk->permission.w ? 'W' : '-', chunk->permission.x ? 'X' : '-');
state.logger->Debug("svcQueryMemory: Address: 0x{:X}, Region Start: 0x{:X}, Size: 0x{:X}, Type: 0x{:X}, Is Uncached: {}, Permissions: {}{}{}", pointer, memInfo.address, memInfo.size, memInfo.type, static_cast<bool>(chunk->attributes.isUncached), chunk->permission.r ? 'R' : '-', chunk->permission.w ? 'W' : '-', chunk->permission.x ? 'X' : '-');
} else {
auto addressSpaceEnd{reinterpret_cast<u64>(state.process->memory.addressSpace.address + state.process->memory.addressSpace.size)};

View File

@ -44,8 +44,8 @@ namespace skyline {
std::unordered_map<u64, std::vector<std::shared_ptr<WaitStatus>>> mutexes; //!< A map from a mutex's address to a vector of Mutex objects for threads waiting on it
std::unordered_map<u64, std::list<std::shared_ptr<WaitStatus>>> conditionals; //!< A map from a conditional variable's address to a vector of threads waiting on it
Mutex mutexLock;
Mutex conditionalLock;
std::mutex mutexLock;
std::mutex conditionalLock;
/**
* @brief The status of a single TLS page (A page is 4096 bytes on ARMv8)

View File

@ -1,14 +1,11 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <sys/types.h>
#include <sys/resource.h>
#include <unistd.h>
#include <asm/unistd.h>
#include <dlfcn.h>
#include <android/log.h>
#include <nce.h>
#include <os.h>
#include <android/log.h>
#include <dlfcn.h>
#include "KProcess.h"
namespace skyline::kernel::type {
@ -41,6 +38,7 @@ namespace skyline::kernel::type {
void KThread::StartThread() {
pthread_setname_np(pthread_self(), fmt::format("HOS-{}", id).c_str());
state.logger->UpdateTag();
if (!ctx.tpidrroEl0)
ctx.tpidrroEl0 = parent->AllocateTlsSlot();

View File

@ -12,10 +12,6 @@
#include "nce/instructions.h"
#include "nce.h"
extern bool Halt;
extern jobject Surface;
extern skyline::GroupMutex JniMtx;
namespace skyline::nce {
void NCE::SvcHandler(u16 svc, ThreadContext *ctx) {
const auto &state{*ctx->state};
@ -79,27 +75,6 @@ namespace skyline::nce {
NCE::NCE(DeviceState &state) : state(state) {}
void NCE::Execute() {
try {
while (true) {
std::lock_guard guard(JniMtx);
if (Halt)
break;
state.gpu->Loop();
}
} catch (const std::exception &e) {
state.logger->Error(e.what());
} catch (...) {
state.logger->Error("An unknown exception has occurred");
}
if (!Halt) {
JniMtx.lock(GroupMutex::Group::Group2);
Halt = true;
JniMtx.unlock();
}
}
constexpr u8 MainSvcTrampolineSize{17}; // Size of the main SVC trampoline function in u32 units
constexpr u32 TpidrEl0{0x5E82}; // ID of TPIDR_EL0 in MRS
constexpr u32 TpidrroEl0{0x5E83}; // ID of TPIDRRO_EL0 in MRS

View File

@ -21,8 +21,6 @@ namespace skyline::nce {
NCE(DeviceState &state);
void Execute();
struct PatchData {
size_t size; //!< Size of the .patch section
std::vector<size_t> offsets; //!< Offsets in .text of instructions that need to be patched

View File

@ -32,8 +32,6 @@ namespace skyline::kernel {
process = std::make_shared<kernel::type::KProcess>(state);
auto entry{state.loader->LoadProcessData(process, state)};
process->InitializeHeap();
process->CreateThread(entry)->Start();
state.nce->Execute();
process->CreateThread(entry)->Start(true);
}
}

View File

@ -1,14 +1,16 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <android/hardware_buffer.h>
#include <gpu.h>
#include <services/nvdrv/driver.h>
#include <services/common/fence.h>
#include <gpu/format.h>
#include <services/nvdrv/driver.h>
#include <services/nvdrv/devices/nvmap.h>
#include <services/common/fence.h>
#include "GraphicBufferProducer.h"
namespace skyline::service::hosbinder {
Buffer::Buffer(const GbpBuffer &gbpBuffer, const std::shared_ptr<gpu::PresentationTexture> &texture) : gbpBuffer(gbpBuffer), texture(texture) {}
Buffer::Buffer(const GbpBuffer &gbpBuffer, const std::shared_ptr<gpu::Texture> &texture) : gbpBuffer(gbpBuffer), texture(texture) {}
GraphicBufferProducer::GraphicBufferProducer(const DeviceState &state) : state(state) {}
@ -64,15 +66,10 @@ namespace skyline::service::hosbinder {
auto buffer{queue.at(data.slot)};
buffer->status = BufferStatus::Queued;
auto slot{data.slot};
auto bufferEvent{state.gpu->bufferEvent};
buffer->texture->releaseCallback = [this, slot, bufferEvent]() {
queue.at(slot)->status = BufferStatus::Free;
bufferEvent->Signal();
};
buffer->texture->SynchronizeHost();
state.gpu->presentationQueue.push(buffer->texture);
state.gpu->presentation.Present(buffer->texture);
queue.at(data.slot)->status = BufferStatus::Free;
state.gpu->presentation.bufferEvent->Signal();
struct {
u32 width;
@ -140,11 +137,11 @@ namespace skyline::service::hosbinder {
gpu::texture::Format format;
switch (gbpBuffer.format) {
case WINDOW_FORMAT_RGBA_8888:
case WINDOW_FORMAT_RGBX_8888:
case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
format = gpu::format::RGBA8888Unorm;
break;
case WINDOW_FORMAT_RGB_565:
case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
format = gpu::format::RGB565Unorm;
break;
default:
@ -153,8 +150,8 @@ namespace skyline::service::hosbinder {
auto texture{std::make_shared<gpu::GuestTexture>(state, nvBuffer->pointer + gbpBuffer.offset, gpu::texture::Dimensions(gbpBuffer.width, gbpBuffer.height), format, gpu::texture::TileMode::Block, gpu::texture::TileConfig{.surfaceWidth = static_cast<u16>(gbpBuffer.stride), .blockHeight = static_cast<u8>(1U << gbpBuffer.blockHeightLog2), .blockDepth = 1})};
queue[data.slot] = std::make_shared<Buffer>(gbpBuffer, texture->InitializePresentationTexture());
state.gpu->bufferEvent->Signal();
queue[data.slot] = std::make_shared<Buffer>(gbpBuffer, texture->InitializeTexture());
state.gpu->presentation.bufferEvent->Signal();
state.logger->Debug("SetPreallocatedBuffer: Slot: {}, Magic: 0x{:X}, Width: {}, Height: {}, Stride: {}, Format: {}, Usage: {}, Index: {}, ID: {}, Handle: {}, Offset: 0x{:X}, Block Height: {}, Size: 0x{:X}", data.slot, gbpBuffer.magic, gbpBuffer.width, gbpBuffer.height, gbpBuffer.stride, gbpBuffer.format, gbpBuffer.usage, gbpBuffer.index, gbpBuffer.nvmapId, gbpBuffer.nvmapHandle, gbpBuffer.offset, (1U << gbpBuffer.blockHeightLog2), gbpBuffer.size);
}

View File

@ -6,7 +6,7 @@
#include <services/common/parcel.h>
namespace skyline::gpu {
class PresentationTexture;
class Texture;
}
namespace skyline::service::hosbinder {
@ -47,10 +47,10 @@ namespace skyline::service::hosbinder {
class Buffer {
public:
BufferStatus status{BufferStatus::Free};
std::shared_ptr<gpu::PresentationTexture> texture;
std::shared_ptr<gpu::Texture> texture;
GbpBuffer gbpBuffer;
Buffer(const GbpBuffer &gbpBuffer, const std::shared_ptr<gpu::PresentationTexture> &texture);
Buffer(const GbpBuffer &gbpBuffer, const std::shared_ptr<gpu::Texture> &texture);
};
/**

View File

@ -40,7 +40,7 @@ namespace skyline::service::hosbinder {
}
Result IHOSBinderDriver::GetNativeHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
KHandle handle{state.process->InsertItem(state.gpu->bufferEvent)};
KHandle handle{state.process->InsertItem(state.gpu->presentation.bufferEvent)};
state.logger->Debug("Display Buffer Event Handle: 0x{:X}", handle);
response.copyHandles.push_back(handle);

View File

@ -110,6 +110,8 @@ namespace skyline::service::nvdrv::device {
u32 reserved[3]; // In
} &data = buffer.as<Data>();
state.gpu->gpfifo.Initialize(data.numEntries);
auto driver{nvdrv::driver.lock()};
channelFence.UpdateValue(driver->hostSyncpoint);
data.fence = channelFence;

View File

@ -23,7 +23,7 @@ namespace skyline::service::nvdrv {
const DeviceState &state;
std::array<SyncpointInfo, skyline::constant::MaxHwSyncpointCount> syncpoints{};
Mutex reservationLock;
std::mutex reservationLock;
/**
* @note reservationLock should be locked when calling this

View File

@ -14,7 +14,7 @@ namespace skyline::service {
private:
const DeviceState &state;
std::unordered_map<ServiceName, std::shared_ptr<BaseService>> serviceMap; //!< A mapping from a Service to the underlying object
Mutex mutex; //!< Synchronizes concurrent access to services to prevent crashes
std::mutex mutex; //!< Synchronizes concurrent access to services to prevent crashes
/**
* @brief Creates an instance of the service if it doesn't already exist, otherwise returns an existing instance

View File

@ -95,7 +95,7 @@ namespace skyline::service::visrv {
}
Result IApplicationDisplayService::GetDisplayVsyncEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
KHandle handle{state.process->InsertItem(state.gpu->vsyncEvent)};
KHandle handle{state.process->InsertItem(state.gpu->presentation.vsyncEvent)};
state.logger->Debug("VSync Event Handle: 0x{:X}", handle);
response.copyHandles.push_back(handle);

View File

@ -43,11 +43,6 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
@Volatile
private var surface : Surface? = null
/**
* A condition variable keeping track of if the surface is ready or not
*/
private var surfaceReady = ConditionVariable()
/**
* A boolean flag denoting if the emulation thread should call finish() or not
*/
@ -77,7 +72,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
*
* @param halt The value to set halt to
*/
private external fun setHalt(halt : Boolean)
private external fun exitGuest(halt : Boolean)
/**
* This sets the surface object in libskyline to the provided value, emulation is halted if set to null
@ -175,10 +170,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
val preferenceFd = ParcelFileDescriptor.open(File("${applicationInfo.dataDir}/shared_prefs/${applicationInfo.packageName}_preferences.xml"), ParcelFileDescriptor.MODE_READ_WRITE)
emulationThread = Thread {
surfaceReady.block()
executeApplication(rom.toString(), romType, romFd.detachFd(), preferenceFd.detachFd(), applicationContext.filesDir.canonicalPath + "/")
if (shouldFinish)
runOnUiThread { finish() }
}
@ -243,7 +235,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
override fun onNewIntent(intent : Intent?) {
shouldFinish = false
setHalt(true)
exitGuest(true)
emulationThread.join()
shouldFinish = true
@ -259,7 +251,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
override fun onDestroy() {
shouldFinish = false
setHalt(true)
exitGuest(true)
emulationThread.join(1000)
vibrators.forEach { (_, vibrator) -> vibrator.cancel() }
@ -275,7 +267,6 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
Log.d(Tag, "surfaceCreated Holder: $holder")
surface = holder.surface
setSurface(surface)
surfaceReady.open()
}
/**
@ -290,7 +281,6 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
*/
override fun surfaceDestroyed(holder : SurfaceHolder) {
Log.d(Tag, "surfaceDestroyed Holder: $holder")
surfaceReady.close()
surface = null
setSurface(surface)
}