mirror of
https://github.com/Takiiiiiiii/strato.git
synced 2025-07-17 08:46:39 +00:00
Run Guest on Main Emulator Thread + Remove Mutex/GroupMutex + Introduce PresentationEngine
This commit is contained in:
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();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user