mirror of
https://github.com/Takiiiiiiii/strato.git
synced 2025-07-17 08:46:39 +00:00
This is a prerequisite to memory trapping as we need to write to the mirror to avoid a race condition with external threads writing to a texture/buffer while we do so ourselves for the sync on a read/write, it also avoids an additional `mprotect` to `-WX`/`RWX` on a read access. An additional advantage for textures especially is that we now support split-mapping textures due to laying them out in a contiguous mirror and they will not require costly algorithmic changes. Buffers should also benefit from not needing to iterate over every region when they are split into multiple mappings.
187 lines
6.3 KiB
C++
187 lines
6.3 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 {
|
|
vk::DeviceSize GuestBuffer::BufferSize() const {
|
|
vk::DeviceSize size{};
|
|
for (const auto &buffer : mappings)
|
|
size += buffer.size_bytes();
|
|
return size;
|
|
}
|
|
|
|
void Buffer::SetupGuestMappings() {
|
|
auto &mappings{guest.mappings};
|
|
if (mappings.size() == 1) {
|
|
auto mapping{mappings.front()};
|
|
u8 *alignedData{util::AlignDown(mapping.data(), PAGE_SIZE)};
|
|
size_t alignedSize{static_cast<size_t>(util::AlignUp(mapping.data() + mapping.size(), PAGE_SIZE) - alignedData)};
|
|
|
|
alignedMirror = gpu.state.process->memory.CreateMirror(alignedData, alignedSize);
|
|
mirror = alignedMirror.subspan(static_cast<size_t>(mapping.data() - alignedData), mapping.size());
|
|
} else {
|
|
std::vector<span<u8>> alignedMappings;
|
|
|
|
const auto &frontMapping{mappings.front()};
|
|
u8 *alignedData{util::AlignDown(frontMapping.data(), PAGE_SIZE)};
|
|
alignedMappings.emplace_back(alignedData, (frontMapping.data() + frontMapping.size()) - alignedData);
|
|
|
|
size_t totalSize{frontMapping.size()};
|
|
for (auto it{std::next(mappings.begin())}; it != std::prev(mappings.end()); ++it) {
|
|
auto mappingSize{it->size()};
|
|
alignedMappings.emplace_back(it->data(), mappingSize);
|
|
totalSize += mappingSize;
|
|
}
|
|
|
|
const auto &backMapping{mappings.back()};
|
|
totalSize += backMapping.size();
|
|
alignedMappings.emplace_back(backMapping.data(), util::AlignUp(backMapping.size(), PAGE_SIZE));
|
|
|
|
alignedMirror = gpu.state.process->memory.CreateMirrors(alignedMappings);
|
|
mirror = alignedMirror.subspan(static_cast<size_t>(frontMapping.data() - alignedData), totalSize);
|
|
}
|
|
}
|
|
|
|
Buffer::Buffer(GPU &gpu, GuestBuffer guest) : gpu(gpu), size(guest.BufferSize()), backing(gpu.memory.AllocateBuffer(size)), guest(std::move(guest)) {
|
|
SetupGuestMappings();
|
|
SynchronizeHost();
|
|
}
|
|
|
|
Buffer::~Buffer() {
|
|
std::lock_guard lock(*this);
|
|
SynchronizeGuest(true);
|
|
if (alignedMirror.valid())
|
|
munmap(alignedMirror.data(), alignedMirror.size());
|
|
}
|
|
|
|
void Buffer::WaitOnFence() {
|
|
TRACE_EVENT("gpu", "Buffer::WaitOnFence");
|
|
|
|
auto lCycle{cycle.lock()};
|
|
if (lCycle) {
|
|
lCycle->Wait();
|
|
cycle.reset();
|
|
}
|
|
}
|
|
|
|
void Buffer::SynchronizeHost() {
|
|
WaitOnFence();
|
|
|
|
TRACE_EVENT("gpu", "Buffer::SynchronizeHost");
|
|
|
|
auto host{backing.data()};
|
|
for (auto &mapping : guest.mappings) {
|
|
auto mappingSize{mapping.size_bytes()};
|
|
std::memcpy(host, mapping.data(), mappingSize);
|
|
host += mappingSize;
|
|
}
|
|
}
|
|
|
|
void Buffer::SynchronizeHostWithCycle(const std::shared_ptr<FenceCycle> &pCycle) {
|
|
if (pCycle != cycle.lock())
|
|
WaitOnFence();
|
|
|
|
TRACE_EVENT("gpu", "Buffer::SynchronizeHostWithCycle");
|
|
|
|
auto host{backing.data()};
|
|
for (auto &mapping : guest.mappings) {
|
|
auto mappingSize{mapping.size_bytes()};
|
|
std::memcpy(host, mapping.data(), mappingSize);
|
|
host += mappingSize;
|
|
}
|
|
}
|
|
|
|
void Buffer::SynchronizeGuest() {
|
|
WaitOnFence();
|
|
|
|
TRACE_EVENT("gpu", "Buffer::SynchronizeGuest");
|
|
|
|
auto host{backing.data()};
|
|
for (auto &mapping : guest.mappings) {
|
|
auto mappingSize{mapping.size_bytes()};
|
|
std::memcpy(mapping.data(), host, mappingSize);
|
|
host += mappingSize;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @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 (pCycle != cycle.lock())
|
|
WaitOnFence();
|
|
|
|
pCycle->AttachObject(std::make_shared<BufferGuestSync>(shared_from_this()));
|
|
cycle = pCycle;
|
|
}
|
|
|
|
void Buffer::Write(span<u8> data, vk::DeviceSize offset) {
|
|
std::memcpy(mirror.data() + offset, data.data(), data.size());
|
|
}
|
|
|
|
std::shared_ptr<BufferView> Buffer::GetView(vk::DeviceSize offset, vk::DeviceSize range, vk::Format format) {
|
|
for (const auto &viewWeak : views) {
|
|
auto view{viewWeak.lock()};
|
|
if (view && view->offset == offset && view->range == range && view->format == format)
|
|
return view;
|
|
}
|
|
|
|
auto view{std::make_shared<BufferView>(shared_from_this(), offset, range, format)};
|
|
views.push_back(view);
|
|
return view;
|
|
}
|
|
|
|
BufferView::BufferView(std::shared_ptr<Buffer> backing, vk::DeviceSize offset, vk::DeviceSize range, vk::Format format) : buffer(std::move(backing)), offset(offset), range(range), format(format) {}
|
|
|
|
void BufferView::lock() {
|
|
auto backing{std::atomic_load(&buffer)};
|
|
while (true) {
|
|
backing->lock();
|
|
|
|
auto latestBacking{std::atomic_load(&buffer)};
|
|
if (backing == latestBacking)
|
|
return;
|
|
|
|
backing->unlock();
|
|
backing = latestBacking;
|
|
}
|
|
}
|
|
|
|
void BufferView::unlock() {
|
|
buffer->unlock();
|
|
}
|
|
|
|
bool BufferView::try_lock() {
|
|
auto backing{std::atomic_load(&buffer)};
|
|
while (true) {
|
|
bool success{backing->try_lock()};
|
|
|
|
auto latestBacking{std::atomic_load(&buffer)};
|
|
if (backing == latestBacking)
|
|
// 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
|
|
backing->unlock();
|
|
backing = latestBacking;
|
|
}
|
|
}
|
|
}
|