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

@ -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();
};
}
}