texture_cache: Implement asynchronous flushing
This commit is contained in:
@@ -245,28 +245,61 @@ StagingBuffer::StagingBuffer(std::size_t size) {
|
||||
GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT));
|
||||
}
|
||||
|
||||
StagingBuffer::~StagingBuffer() = default;
|
||||
|
||||
void StagingBuffer::QueueFence() {
|
||||
sync.Release();
|
||||
sync.Create();
|
||||
StagingBuffer::~StagingBuffer() {
|
||||
if (sync) {
|
||||
glDeleteSync(sync);
|
||||
}
|
||||
}
|
||||
|
||||
bool StagingBuffer::IsAvailable() const {
|
||||
if (!sync.handle) {
|
||||
return true;
|
||||
}
|
||||
switch (glClientWaitSync(sync.handle, 0, 0)) {
|
||||
void StagingBuffer::QueueFence(bool own) {
|
||||
DEBUG_ASSERT(!sync);
|
||||
owned = own;
|
||||
sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
}
|
||||
|
||||
void StagingBuffer::WaitFence() {
|
||||
ASSERT(sync);
|
||||
switch (glClientWaitSync(sync, 0, GL_TIMEOUT_IGNORED)) {
|
||||
case GL_ALREADY_SIGNALED:
|
||||
case GL_CONDITION_SATISFIED:
|
||||
return true;
|
||||
break;
|
||||
case GL_TIMEOUT_EXPIRED:
|
||||
return false;
|
||||
case GL_WAIT_FAILED:
|
||||
UNREACHABLE_MSG("Fence wait failed");
|
||||
break;
|
||||
}
|
||||
Discard();
|
||||
}
|
||||
|
||||
void StagingBuffer::Discard() {
|
||||
DEBUG_ASSERT(sync);
|
||||
glDeleteSync(sync);
|
||||
sync = nullptr;
|
||||
owned = false;
|
||||
}
|
||||
|
||||
bool StagingBuffer::IsAvailable() {
|
||||
if (owned) {
|
||||
return false;
|
||||
}
|
||||
if (!sync) {
|
||||
return true;
|
||||
}
|
||||
UNREACHABLE();
|
||||
switch (glClientWaitSync(sync, 0, 0)) {
|
||||
case GL_TIMEOUT_EXPIRED:
|
||||
return false;
|
||||
case GL_ALREADY_SIGNALED:
|
||||
case GL_CONDITION_SATISFIED:
|
||||
break;
|
||||
case GL_WAIT_FAILED:
|
||||
UNREACHABLE_MSG("Fence wait failed");
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown glClientWaitSync result");
|
||||
break;
|
||||
}
|
||||
glDeleteSync(sync);
|
||||
sync = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -308,7 +341,10 @@ void CachedSurface::DownloadTexture(StagingBuffer& buffer) {
|
||||
static_cast<GLsizei>(params.GetHostMipmapSize(level)), mip_offset);
|
||||
}
|
||||
}
|
||||
glFinish();
|
||||
// According to Cemu glGetTextureImage and friends do not flush, resulting in a softlock if we
|
||||
// wait for a fence. To fix this we have to explicitly flush and then queue a fence.
|
||||
glFlush();
|
||||
buffer.QueueFence(true);
|
||||
}
|
||||
|
||||
void CachedSurface::UploadTexture(StagingBuffer& buffer) {
|
||||
@@ -319,7 +355,7 @@ void CachedSurface::UploadTexture(StagingBuffer& buffer) {
|
||||
for (u32 level = 0; level < params.emulated_levels; ++level) {
|
||||
UploadTextureMipmap(level, buffer);
|
||||
}
|
||||
buffer.QueueFence();
|
||||
buffer.QueueFence(false);
|
||||
}
|
||||
|
||||
void CachedSurface::UploadTextureMipmap(u32 level, const StagingBuffer& staging_buffer) {
|
||||
|
||||
@@ -122,9 +122,13 @@ public:
|
||||
explicit StagingBuffer(std::size_t size);
|
||||
~StagingBuffer();
|
||||
|
||||
void QueueFence();
|
||||
void QueueFence(bool own);
|
||||
|
||||
[[nodiscard]] bool IsAvailable() const;
|
||||
void WaitFence();
|
||||
|
||||
void Discard();
|
||||
|
||||
[[nodiscard]] bool IsAvailable();
|
||||
|
||||
[[nodiscard]] GLuint GetHandle() const {
|
||||
return buffer.handle;
|
||||
@@ -136,8 +140,9 @@ public:
|
||||
|
||||
private:
|
||||
OGLBuffer buffer;
|
||||
OGLSync sync;
|
||||
GLsync sync{};
|
||||
u8* pointer{};
|
||||
bool owned{};
|
||||
};
|
||||
|
||||
class TextureCacheOpenGL final : public TextureCacheBase {
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
const u32 ceil = Common::Log2Ceil64(size);
|
||||
auto& buffers = cache[ceil];
|
||||
const auto it = std::find_if(buffers.begin(), buffers.end(),
|
||||
[](const auto& buffer) { return buffer->IsAvailable(); });
|
||||
[](auto& buffer) { return buffer->IsAvailable(); });
|
||||
if (it != buffers.end()) {
|
||||
return **it;
|
||||
}
|
||||
|
||||
@@ -177,9 +177,24 @@ public:
|
||||
|
||||
virtual void DownloadTexture(StagingBufferType& buffer) = 0;
|
||||
|
||||
void SetFlushBuffer(StagingBufferType* buffer) {
|
||||
flush_buffer = buffer;
|
||||
}
|
||||
|
||||
StagingBufferType* GetFlushBuffer() const {
|
||||
return flush_buffer;
|
||||
}
|
||||
|
||||
void MarkAsModified(const bool is_modified_, const u64 tick) {
|
||||
is_modified = is_modified_ || is_target;
|
||||
modification_tick = tick;
|
||||
|
||||
if (is_modified && flush_buffer) {
|
||||
// The buffer has been modified while we thought it was no longer being to be used and
|
||||
// we queued a flush.
|
||||
flush_buffer->Discard();
|
||||
flush_buffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void MarkAsRenderTarget(const bool is_target, const u32 index) {
|
||||
@@ -303,6 +318,8 @@ private:
|
||||
bool is_picked{};
|
||||
u32 index{NO_RT};
|
||||
u64 modification_tick{};
|
||||
|
||||
StagingBufferType* flush_buffer{};
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
||||
@@ -133,12 +133,18 @@ public:
|
||||
regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height,
|
||||
regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)};
|
||||
auto surface_view = GetSurface(gpu_addr, depth_params, preserve_contents, true);
|
||||
if (depth_buffer.target)
|
||||
if (auto& old_target = depth_buffer.target; old_target != surface_view.first) {
|
||||
FlushAoT(old_target);
|
||||
}
|
||||
|
||||
if (depth_buffer.target) {
|
||||
depth_buffer.target->MarkAsRenderTarget(false, NO_RT);
|
||||
}
|
||||
depth_buffer.target = surface_view.first;
|
||||
depth_buffer.view = surface_view.second;
|
||||
if (depth_buffer.target)
|
||||
if (depth_buffer.target) {
|
||||
depth_buffer.target->MarkAsRenderTarget(true, DEPTH_RT);
|
||||
}
|
||||
return surface_view.second;
|
||||
}
|
||||
|
||||
@@ -167,12 +173,18 @@ public:
|
||||
|
||||
auto surface_view = GetSurface(gpu_addr, SurfaceParams::CreateForFramebuffer(system, index),
|
||||
preserve_contents, true);
|
||||
if (render_targets[index].target)
|
||||
if (auto& old_target = render_targets[index].target; old_target != surface_view.first) {
|
||||
FlushAoT(old_target);
|
||||
}
|
||||
|
||||
if (render_targets[index].target) {
|
||||
render_targets[index].target->MarkAsRenderTarget(false, NO_RT);
|
||||
}
|
||||
render_targets[index].target = surface_view.first;
|
||||
render_targets[index].view = surface_view.second;
|
||||
if (render_targets[index].target)
|
||||
if (render_targets[index].target) {
|
||||
render_targets[index].target->MarkAsRenderTarget(true, static_cast<u32>(index));
|
||||
}
|
||||
return surface_view.second;
|
||||
}
|
||||
|
||||
@@ -189,19 +201,25 @@ public:
|
||||
}
|
||||
|
||||
void SetEmptyDepthBuffer() {
|
||||
if (depth_buffer.target == nullptr) {
|
||||
auto& target = depth_buffer.target;
|
||||
if (target == nullptr) {
|
||||
return;
|
||||
}
|
||||
depth_buffer.target->MarkAsRenderTarget(false, NO_RT);
|
||||
FlushAoT(target);
|
||||
target->MarkAsRenderTarget(false, NO_RT);
|
||||
|
||||
depth_buffer.target = nullptr;
|
||||
depth_buffer.view = nullptr;
|
||||
}
|
||||
|
||||
void SetEmptyColorBuffer(std::size_t index) {
|
||||
if (render_targets[index].target == nullptr) {
|
||||
auto& target = render_targets[index].target;
|
||||
if (target == nullptr) {
|
||||
return;
|
||||
}
|
||||
render_targets[index].target->MarkAsRenderTarget(false, NO_RT);
|
||||
FlushAoT(target);
|
||||
target->MarkAsRenderTarget(false, NO_RT);
|
||||
|
||||
render_targets[index].target = nullptr;
|
||||
render_targets[index].view = nullptr;
|
||||
}
|
||||
@@ -697,9 +715,16 @@ private:
|
||||
if (!surface->IsModified()) {
|
||||
return;
|
||||
}
|
||||
auto& buffer = staging_buffer_cache.GetBuffer(surface->GetHostSizeInBytes());
|
||||
surface->DownloadTexture(buffer);
|
||||
surface->FlushBuffer(system.GPU().MemoryManager(), buffer.GetPointer());
|
||||
|
||||
auto buffer = surface->GetFlushBuffer();
|
||||
if (!buffer) {
|
||||
buffer = &staging_buffer_cache.GetBuffer(surface->GetHostSizeInBytes());
|
||||
surface->DownloadTexture(*buffer);
|
||||
}
|
||||
buffer->WaitFence();
|
||||
surface->SetFlushBuffer(nullptr);
|
||||
|
||||
surface->FlushBuffer(system.GPU().MemoryManager(), buffer->GetPointer());
|
||||
surface->MarkAsModified(false, Tick());
|
||||
}
|
||||
|
||||
@@ -767,6 +792,15 @@ private:
|
||||
return {};
|
||||
}
|
||||
|
||||
void FlushAoT(TSurface& surface) {
|
||||
if (!surface || !surface->IsLinear() || surface->GetFlushBuffer()) {
|
||||
return;
|
||||
}
|
||||
auto& buffer = staging_buffer_cache.GetBuffer(surface->GetHostSizeInBytes());
|
||||
surface->DownloadTexture(buffer);
|
||||
surface->SetFlushBuffer(&buffer);
|
||||
}
|
||||
|
||||
constexpr PixelFormat GetSiblingFormat(PixelFormat format) const {
|
||||
return siblings_table[static_cast<std::size_t>(format)];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user