mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-26 17:04:14 +01:00
Run Guest on Main Emulator Thread + Remove Mutex/GroupMutex + Introduce PresentationEngine
This commit is contained in:
parent
779884edcf
commit
3cde568c51
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)) {}
|
||||
};
|
||||
}
|
||||
|
117
app/src/main/cpp/skyline/gpu/circular_queue.h
Normal file
117
app/src/main/cpp/skyline/gpu/circular_queue.h
Normal 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();
|
||||
}
|
||||
};
|
||||
}
|
@ -3,7 +3,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gpu/texture.h>
|
||||
#include <gpu/macro_interpreter.h>
|
||||
#include "engine.h"
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
84
app/src/main/cpp/skyline/gpu/presentation_engine.cpp
Normal file
84
app/src/main/cpp/skyline/gpu/presentation_engine.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
36
app/src/main/cpp/skyline/gpu/presentation_engine.h
Normal file
36
app/src/main/cpp/skyline/gpu/presentation_engine.h
Normal 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{};
|
||||
};
|
||||
}
|
@ -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};
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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) {}
|
||||
};
|
||||
}
|
||||
|
@ -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,
|
||||
}};
|
||||
}
|
||||
|
||||
|
@ -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)};
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user