Files
strato/app/src/main/cpp/skyline/gpu/buffer.cpp
2022-04-18 13:28:58 +01:00

241 lines
8.9 KiB
C++

// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <gpu.h>
#include <kernel/memory.h>
#include <kernel/types/KProcess.h>
#include <common/trace.h>
#include "buffer.h"
namespace skyline::gpu {
void Buffer::SetupGuestMappings() {
u8 *alignedData{util::AlignDown(guest->data(), PAGE_SIZE)};
size_t alignedSize{static_cast<size_t>(util::AlignUp(guest->data() + guest->size(), PAGE_SIZE) - alignedData)};
alignedMirror = gpu.state.process->memory.CreateMirror(alignedData, alignedSize);
mirror = alignedMirror.subspan(static_cast<size_t>(guest->data() - alignedData), guest->size());
trapHandle = gpu.state.nce->TrapRegions(*guest, true, [this] {
std::lock_guard lock(*this);
SynchronizeGuest(true); // We can skip trapping since the caller will do it
WaitOnFence();
}, [this] {
std::lock_guard lock(*this);
SynchronizeGuest(true);
dirtyState = DirtyState::CpuDirty; // We need to assume the buffer is dirty since we don't know what the guest is writing
WaitOnFence();
});
}
Buffer::Buffer(GPU &gpu, GuestBuffer guest) : gpu(gpu), backing(gpu.memory.AllocateBuffer(guest.size())), guest(guest) {
SetupGuestMappings();
}
Buffer::Buffer(GPU &gpu, vk::DeviceSize size) : gpu(gpu), backing(gpu.memory.AllocateBuffer(size)) {
dirtyState = DirtyState::Clean; // Since this is a host-only buffer it's always going to be clean
}
Buffer::~Buffer() {
std::lock_guard lock(*this);
if (trapHandle)
gpu.state.nce->DeleteTrap(*trapHandle);
SynchronizeGuest(true);
if (alignedMirror.valid())
munmap(alignedMirror.data(), alignedMirror.size());
}
void Buffer::MarkGpuDirty() {
if (dirtyState == DirtyState::GpuDirty || !guest)
return;
gpu.state.nce->RetrapRegions(*trapHandle, false);
dirtyState = DirtyState::GpuDirty;
}
void Buffer::WaitOnFence() {
TRACE_EVENT("gpu", "Buffer::WaitOnFence");
auto lCycle{cycle.lock()};
if (lCycle) {
lCycle->Wait();
cycle.reset();
}
}
void Buffer::SynchronizeHost(bool rwTrap) {
if (dirtyState != DirtyState::CpuDirty || !guest)
return; // If the buffer has not been modified on the CPU or there's no guest buffer, there is no need to synchronize it
WaitOnFence();
TRACE_EVENT("gpu", "Buffer::SynchronizeHost");
std::memcpy(backing.data(), mirror.data(), mirror.size());
if (rwTrap) {
gpu.state.nce->RetrapRegions(*trapHandle, false);
dirtyState = DirtyState::GpuDirty;
} else {
gpu.state.nce->RetrapRegions(*trapHandle, true);
dirtyState = DirtyState::Clean;
}
}
void Buffer::SynchronizeHostWithCycle(const std::shared_ptr<FenceCycle> &pCycle, bool rwTrap) {
if (dirtyState != DirtyState::CpuDirty || !guest)
return;
if (!cycle.owner_before(pCycle))
WaitOnFence();
TRACE_EVENT("gpu", "Buffer::SynchronizeHostWithCycle");
std::memcpy(backing.data(), mirror.data(), mirror.size());
if (rwTrap) {
gpu.state.nce->RetrapRegions(*trapHandle, false);
dirtyState = DirtyState::GpuDirty;
} else {
gpu.state.nce->RetrapRegions(*trapHandle, true);
dirtyState = DirtyState::Clean;
}
}
void Buffer::SynchronizeGuest(bool skipTrap, bool skipFence) {
if (dirtyState != DirtyState::GpuDirty || !guest)
return; // If the buffer has not been used on the GPU or there's no guest buffer, there is no need to synchronize it
if (!skipFence)
WaitOnFence();
TRACE_EVENT("gpu", "Buffer::SynchronizeGuest");
std::memcpy(mirror.data(), backing.data(), mirror.size());
if (!skipTrap)
gpu.state.nce->RetrapRegions(*trapHandle, true);
dirtyState = DirtyState::Clean;
}
/**
* @brief A FenceCycleDependency that synchronizes the contents of a host buffer with the guest buffer
*/
struct BufferGuestSync : public FenceCycleDependency {
std::shared_ptr<Buffer> buffer;
explicit BufferGuestSync(std::shared_ptr<Buffer> buffer) : buffer(std::move(buffer)) {}
~BufferGuestSync() {
TRACE_EVENT("gpu", "Buffer::BufferGuestSync");
buffer->SynchronizeGuest();
}
};
void Buffer::SynchronizeGuestWithCycle(const std::shared_ptr<FenceCycle> &pCycle) {
if (!cycle.owner_before(pCycle))
WaitOnFence();
pCycle->AttachObject(std::make_shared<BufferGuestSync>(shared_from_this()));
cycle = pCycle;
}
void Buffer::Read(span<u8> data, vk::DeviceSize offset) {
if (dirtyState == DirtyState::CpuDirty || dirtyState == DirtyState::Clean)
std::memcpy(data.data(), mirror.data() + offset, data.size());
else if (dirtyState == DirtyState::GpuDirty)
std::memcpy(data.data(), backing.data() + offset, data.size());
}
void Buffer::Write(span<u8> data, vk::DeviceSize offset) {
if (dirtyState == DirtyState::CpuDirty || dirtyState == DirtyState::Clean)
std::memcpy(mirror.data() + offset, data.data(), data.size());
if (dirtyState == DirtyState::GpuDirty || dirtyState == DirtyState::Clean)
std::memcpy(backing.data() + offset, data.data(), data.size());
}
Buffer::BufferViewStorage::BufferViewStorage(vk::DeviceSize offset, vk::DeviceSize size, vk::Format format) : offset(offset), size(size), format(format) {}
Buffer::BufferDelegate::BufferDelegate(std::shared_ptr<Buffer> pBuffer, Buffer::BufferViewStorage *view) : buffer(std::move(pBuffer)), view(view) {
iterator = buffer->delegates.emplace(buffer->delegates.end(), this);
}
Buffer::BufferDelegate::~BufferDelegate() {
std::scoped_lock lock(*this);
buffer->delegates.erase(iterator);
}
void Buffer::BufferDelegate::lock() {
auto lBuffer{std::atomic_load(&buffer)};
while (true) {
lBuffer->lock();
auto latestBacking{std::atomic_load(&buffer)};
if (lBuffer == latestBacking)
return;
lBuffer->unlock();
lBuffer = latestBacking;
}
}
void Buffer::BufferDelegate::unlock() {
buffer->unlock();
}
bool Buffer::BufferDelegate::try_lock() {
auto lBuffer{std::atomic_load(&buffer)};
while (true) {
bool success{lBuffer->try_lock()};
auto latestBuffer{std::atomic_load(&buffer)};
if (lBuffer == latestBuffer)
// We want to ensure that the try_lock() was on the latest backing and not on an outdated one
return success;
if (success)
// We only unlock() if the try_lock() was successful and we acquired the mutex
lBuffer->unlock();
lBuffer = latestBuffer;
}
}
BufferView Buffer::GetView(vk::DeviceSize offset, vk::DeviceSize size, vk::Format format) {
for (auto &view : views)
if (view.offset == offset && view.size == size && view.format == format)
return BufferView{shared_from_this(), &view};
views.emplace_back(offset, size, format);
return BufferView{shared_from_this(), &views.back()};
}
BufferView::BufferView(std::shared_ptr<Buffer> buffer, Buffer::BufferViewStorage *view) : bufferDelegate(std::make_shared<Buffer::BufferDelegate>(std::move(buffer), view)) {}
void BufferView::AttachCycle(const std::shared_ptr<FenceCycle> &cycle) {
auto buffer{bufferDelegate->buffer.get()};
if (!buffer->cycle.owner_before(cycle)) {
buffer->WaitOnFence();
buffer->cycle = cycle;
cycle->AttachObject(bufferDelegate);
}
}
void BufferView::RegisterUsage(const std::function<void(const Buffer::BufferViewStorage &, const std::shared_ptr<Buffer> &)> &usageCallback) {
usageCallback(*bufferDelegate->view, bufferDelegate->buffer);
if (!bufferDelegate->usageCallback) {
bufferDelegate->usageCallback = usageCallback;
} else {
bufferDelegate->usageCallback = [usageCallback, oldCallback = std::move(bufferDelegate->usageCallback)](const Buffer::BufferViewStorage &pView, const std::shared_ptr<Buffer> &buffer) {
oldCallback(pView, buffer);
usageCallback(pView, buffer);
};
}
}
void BufferView::Read(span<u8> data, vk::DeviceSize offset) const {
bufferDelegate->buffer->Read(data, offset + bufferDelegate->view->offset);
}
void BufferView::Write(span<u8> data, vk::DeviceSize offset) const {
bufferDelegate->buffer->Write(data, offset + bufferDelegate->view->offset);
}
}