[Memory/Vulkan] Move old memory watches to the Vulkan backend
This commit is contained in:
parent
0370f8bbd9
commit
d20c2fa9da
|
@ -16,7 +16,6 @@
|
||||||
#include "xenia/base/byte_order.h"
|
#include "xenia/base/byte_order.h"
|
||||||
#include "xenia/base/exception_handler.h"
|
#include "xenia/base/exception_handler.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/math.h"
|
|
||||||
#include "xenia/base/memory.h"
|
#include "xenia/base/memory.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -107,211 +106,6 @@ bool MMIOHandler::CheckStore(uint32_t virtual_address, uint32_t value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uintptr_t MMIOHandler::AddPhysicalAccessWatch(uint32_t guest_address,
|
|
||||||
size_t length, WatchType type,
|
|
||||||
AccessWatchCallback callback,
|
|
||||||
void* callback_context,
|
|
||||||
void* callback_data) {
|
|
||||||
uint32_t base_address = guest_address & 0x1FFFFFFF;
|
|
||||||
|
|
||||||
// Can only protect sizes matching system page size.
|
|
||||||
// This means we need to round up, which will cause spurious access
|
|
||||||
// violations and invalidations.
|
|
||||||
// TODO(benvanik): only invalidate if actually within the region?
|
|
||||||
length = xe::round_up(length + (base_address % xe::memory::page_size()),
|
|
||||||
xe::memory::page_size());
|
|
||||||
base_address = base_address - (base_address % xe::memory::page_size());
|
|
||||||
|
|
||||||
auto lock = global_critical_region_.Acquire();
|
|
||||||
|
|
||||||
// Fire any access watches that overlap this region.
|
|
||||||
for (auto it = access_watches_.begin(); it != access_watches_.end();) {
|
|
||||||
// Case 1: 2222222|222|11111111
|
|
||||||
// Case 2: 1111111|222|22222222
|
|
||||||
// Case 3: 1111111|222|11111111 (fragmentation)
|
|
||||||
// Case 4: 2222222|222|22222222 (complete overlap)
|
|
||||||
bool hit = false;
|
|
||||||
auto entry = *it;
|
|
||||||
|
|
||||||
if (base_address <= (*it)->address &&
|
|
||||||
base_address + length > (*it)->address) {
|
|
||||||
hit = true;
|
|
||||||
} else if ((*it)->address <= base_address &&
|
|
||||||
(*it)->address + (*it)->length > base_address) {
|
|
||||||
hit = true;
|
|
||||||
} else if ((*it)->address <= base_address &&
|
|
||||||
(*it)->address + (*it)->length > base_address + length) {
|
|
||||||
hit = true;
|
|
||||||
} else if ((*it)->address >= base_address &&
|
|
||||||
(*it)->address + (*it)->length < base_address + length) {
|
|
||||||
hit = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hit) {
|
|
||||||
FireAccessWatch(*it);
|
|
||||||
it = access_watches_.erase(it);
|
|
||||||
delete entry;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to table. The slot reservation may evict a previous watch, which
|
|
||||||
// could include our target, so we do it first.
|
|
||||||
auto entry = new AccessWatchEntry();
|
|
||||||
entry->address = base_address;
|
|
||||||
entry->length = uint32_t(length);
|
|
||||||
entry->type = type;
|
|
||||||
entry->callback = callback;
|
|
||||||
entry->callback_context = callback_context;
|
|
||||||
entry->callback_data = callback_data;
|
|
||||||
access_watches_.push_back(entry);
|
|
||||||
|
|
||||||
auto page_access = memory::PageAccess::kNoAccess;
|
|
||||||
switch (type) {
|
|
||||||
case kWatchWrite:
|
|
||||||
page_access = memory::PageAccess::kReadOnly;
|
|
||||||
break;
|
|
||||||
case kWatchReadWrite:
|
|
||||||
page_access = memory::PageAccess::kNoAccess;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assert_unhandled_case(type);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Protect the range under all address spaces
|
|
||||||
memory::Protect(physical_membase_ + entry->address, entry->length,
|
|
||||||
page_access, nullptr);
|
|
||||||
memory::Protect(virtual_membase_ + 0xA0000000 + entry->address, entry->length,
|
|
||||||
page_access, nullptr);
|
|
||||||
memory::Protect(virtual_membase_ + 0xC0000000 + entry->address, entry->length,
|
|
||||||
page_access, nullptr);
|
|
||||||
memory::Protect(virtual_membase_ + 0xE0000000 + entry->address, entry->length,
|
|
||||||
page_access, nullptr);
|
|
||||||
|
|
||||||
return reinterpret_cast<uintptr_t>(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MMIOHandler::FireAccessWatch(AccessWatchEntry* entry) {
|
|
||||||
ClearAccessWatch(entry);
|
|
||||||
entry->callback(entry->callback_context, entry->callback_data,
|
|
||||||
entry->address);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MMIOHandler::ClearAccessWatch(AccessWatchEntry* entry) {
|
|
||||||
memory::Protect(physical_membase_ + entry->address, entry->length,
|
|
||||||
xe::memory::PageAccess::kReadWrite, nullptr);
|
|
||||||
memory::Protect(virtual_membase_ + 0xA0000000 + entry->address, entry->length,
|
|
||||||
xe::memory::PageAccess::kReadWrite, nullptr);
|
|
||||||
memory::Protect(virtual_membase_ + 0xC0000000 + entry->address, entry->length,
|
|
||||||
xe::memory::PageAccess::kReadWrite, nullptr);
|
|
||||||
memory::Protect(virtual_membase_ + 0xE0000000 + entry->address, entry->length,
|
|
||||||
xe::memory::PageAccess::kReadWrite, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MMIOHandler::CancelAccessWatch(uintptr_t watch_handle) {
|
|
||||||
auto entry = reinterpret_cast<AccessWatchEntry*>(watch_handle);
|
|
||||||
auto lock = global_critical_region_.Acquire();
|
|
||||||
|
|
||||||
// Allow access to the range again.
|
|
||||||
ClearAccessWatch(entry);
|
|
||||||
|
|
||||||
// Remove from table.
|
|
||||||
auto it = std::find(access_watches_.begin(), access_watches_.end(), entry);
|
|
||||||
assert_false(it == access_watches_.end());
|
|
||||||
|
|
||||||
if (it != access_watches_.end()) {
|
|
||||||
access_watches_.erase(it);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MMIOHandler::InvalidateRange(uint32_t physical_address_and_heap,
|
|
||||||
uint32_t length) {
|
|
||||||
uint32_t heap_relative_address = physical_address_and_heap & 0x1FFFFFFF;
|
|
||||||
length = std::min(length, 0x20000000u - heap_relative_address);
|
|
||||||
if (length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto lock = global_critical_region_.Acquire();
|
|
||||||
|
|
||||||
// Trigger the legacy (per-range) watches.
|
|
||||||
for (auto it = access_watches_.begin(); it != access_watches_.end();) {
|
|
||||||
auto entry = *it;
|
|
||||||
if ((entry->address <= heap_relative_address &&
|
|
||||||
entry->address + entry->length > heap_relative_address) ||
|
|
||||||
(entry->address >= heap_relative_address &&
|
|
||||||
entry->address < heap_relative_address + length)) {
|
|
||||||
// This watch lies within the range. End it.
|
|
||||||
FireAccessWatch(entry);
|
|
||||||
it = access_watches_.erase(it);
|
|
||||||
delete entry;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MMIOHandler::IsRangeWatched(uint32_t physical_address, size_t length) {
|
|
||||||
auto lock = global_critical_region_.Acquire();
|
|
||||||
|
|
||||||
for (auto it = access_watches_.begin(); it != access_watches_.end(); ++it) {
|
|
||||||
auto entry = *it;
|
|
||||||
if ((entry->address <= physical_address &&
|
|
||||||
entry->address + entry->length > physical_address + length)) {
|
|
||||||
// This range lies entirely within this watch.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(DrChat): Check if the range is partially covered, and subtract the
|
|
||||||
// covered portion if it is.
|
|
||||||
if ((entry->address <= physical_address &&
|
|
||||||
entry->address + entry->length > physical_address)) {
|
|
||||||
// The beginning of range lies partially within this watch.
|
|
||||||
} else if ((entry->address < physical_address + length &&
|
|
||||||
entry->address + entry->length > physical_address + length)) {
|
|
||||||
// The ending of this range lies partially within this watch.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MMIOHandler::CheckAccessWatch(uint32_t physical_address,
|
|
||||||
uint32_t heap_address) {
|
|
||||||
bool hit = false;
|
|
||||||
|
|
||||||
// Trigger legacy (per-range) access watches.
|
|
||||||
// TODO(Triang3l): Remove when legacy watches are deleted.
|
|
||||||
auto lock = global_critical_region_.Acquire();
|
|
||||||
for (auto it = access_watches_.begin(); it != access_watches_.end();) {
|
|
||||||
auto entry = *it;
|
|
||||||
if (entry->address <= physical_address &&
|
|
||||||
entry->address + entry->length > physical_address) {
|
|
||||||
// Hit! Remove the watch.
|
|
||||||
hit = true;
|
|
||||||
FireAccessWatch(entry);
|
|
||||||
it = access_watches_.erase(it);
|
|
||||||
delete entry;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hit) {
|
|
||||||
// Rethrow access violation - range was not being watched.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Range was watched, so lets eat this access violation.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DecodedMov {
|
struct DecodedMov {
|
||||||
size_t length;
|
size_t length;
|
||||||
// Inidicates this is a load (or conversely a store).
|
// Inidicates this is a load (or conversely a store).
|
||||||
|
@ -514,8 +308,8 @@ bool MMIOHandler::ExceptionCallback(Exception* ex) {
|
||||||
guest_heap_address = 0;
|
guest_heap_address = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK: Recheck if the pages are still protected (race condition - another
|
// Recheck if the pages are still protected (race condition - another thread
|
||||||
// thread clears the writewatch we just hit)
|
// clears the writewatch we just hit).
|
||||||
// Do this under the lock so we don't introduce another race condition.
|
// Do this under the lock so we don't introduce another race condition.
|
||||||
auto lock = global_critical_region_.Acquire();
|
auto lock = global_critical_region_.Acquire();
|
||||||
memory::PageAccess cur_access;
|
memory::PageAccess cur_access;
|
||||||
|
@ -527,26 +321,22 @@ bool MMIOHandler::ExceptionCallback(Exception* ex) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Access is not found within any range, so fail and let the caller handle
|
// The address is not found within any range, so either a write watch or an
|
||||||
// it (likely by aborting).
|
// actual access violation.
|
||||||
// TODO(Triang3l): Remove legacy CheckAccessWatch, only call the callback.
|
|
||||||
bool hit = CheckAccessWatch(guest_address, guest_heap_address);
|
|
||||||
if (access_violation_callback_) {
|
if (access_violation_callback_) {
|
||||||
switch (ex->access_violation_operation()) {
|
switch (ex->access_violation_operation()) {
|
||||||
case Exception::AccessViolationOperation::kRead:
|
case Exception::AccessViolationOperation::kRead:
|
||||||
hit |= access_violation_callback_(access_violation_callback_context_,
|
return access_violation_callback_(access_violation_callback_context_,
|
||||||
size_t(ex->fault_address()), false);
|
size_t(ex->fault_address()), false);
|
||||||
break;
|
|
||||||
case Exception::AccessViolationOperation::kWrite:
|
case Exception::AccessViolationOperation::kWrite:
|
||||||
hit |= access_violation_callback_(access_violation_callback_context_,
|
return access_violation_callback_(access_violation_callback_context_,
|
||||||
size_t(ex->fault_address()), true);
|
size_t(ex->fault_address()), true);
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
// Data Execution Prevention or something else uninteresting.
|
// Data Execution Prevention or something else uninteresting.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hit;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto rip = ex->pc();
|
auto rip = ex->pc();
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
#ifndef XENIA_CPU_MMIO_HANDLER_H_
|
#ifndef XENIA_CPU_MMIO_HANDLER_H_
|
||||||
#define XENIA_CPU_MMIO_HANDLER_H_
|
#define XENIA_CPU_MMIO_HANDLER_H_
|
||||||
|
|
||||||
#include <list>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -48,12 +47,6 @@ class MMIOHandler {
|
||||||
typedef bool (*AccessViolationCallback)(void* context, size_t host_address,
|
typedef bool (*AccessViolationCallback)(void* context, size_t host_address,
|
||||||
bool is_write);
|
bool is_write);
|
||||||
|
|
||||||
enum WatchType {
|
|
||||||
kWatchInvalid = 0,
|
|
||||||
kWatchWrite = 1,
|
|
||||||
kWatchReadWrite = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
// access_violation_callback is called in global_critical_region, so if
|
// access_violation_callback is called in global_critical_region, so if
|
||||||
// multiple threads trigger an access violation in the same page, the callback
|
// multiple threads trigger an access violation in the same page, the callback
|
||||||
// will be called only once.
|
// will be called only once.
|
||||||
|
@ -71,37 +64,7 @@ class MMIOHandler {
|
||||||
bool CheckLoad(uint32_t virtual_address, uint32_t* out_value);
|
bool CheckLoad(uint32_t virtual_address, uint32_t* out_value);
|
||||||
bool CheckStore(uint32_t virtual_address, uint32_t value);
|
bool CheckStore(uint32_t virtual_address, uint32_t value);
|
||||||
|
|
||||||
// Memory watches: These are one-shot alarms that fire a callback (in the
|
|
||||||
// context of the thread that caused the callback) when a memory range is
|
|
||||||
// either written to or read from, depending on the watch type. These fire as
|
|
||||||
// soon as a read/write happens, and only fire once.
|
|
||||||
// These watches may be spuriously fired if memory is accessed nearby.
|
|
||||||
// TODO(Triang3l): This is legacy currently used only to support the old
|
|
||||||
// Vulkan graphics layer. Remove and use WatchPhysicalMemoryWrite instead.
|
|
||||||
uintptr_t AddPhysicalAccessWatch(uint32_t guest_address, size_t length,
|
|
||||||
WatchType type, AccessWatchCallback callback,
|
|
||||||
void* callback_context, void* callback_data);
|
|
||||||
void CancelAccessWatch(uintptr_t watch_handle);
|
|
||||||
|
|
||||||
// Fires and clears any write watches that overlap this range in one heap.
|
|
||||||
// Unprotecting can be inhibited if this is called right before applying
|
|
||||||
// different protection to the same range.
|
|
||||||
void InvalidateRange(uint32_t physical_address_and_heap, uint32_t length);
|
|
||||||
|
|
||||||
// Returns true if /all/ of this range is watched.
|
|
||||||
// TODO(Triang3l): Remove when legacy watches are removed.
|
|
||||||
bool IsRangeWatched(uint32_t physical_address, size_t length);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
struct AccessWatchEntry {
|
|
||||||
uint32_t address;
|
|
||||||
uint32_t length;
|
|
||||||
WatchType type;
|
|
||||||
AccessWatchCallback callback;
|
|
||||||
void* callback_context;
|
|
||||||
void* callback_data;
|
|
||||||
};
|
|
||||||
|
|
||||||
MMIOHandler(uint8_t* virtual_membase, uint8_t* physical_membase,
|
MMIOHandler(uint8_t* virtual_membase, uint8_t* physical_membase,
|
||||||
uint8_t* membase_end,
|
uint8_t* membase_end,
|
||||||
AccessViolationCallback access_violation_callback,
|
AccessViolationCallback access_violation_callback,
|
||||||
|
@ -110,10 +73,6 @@ class MMIOHandler {
|
||||||
static bool ExceptionCallbackThunk(Exception* ex, void* data);
|
static bool ExceptionCallbackThunk(Exception* ex, void* data);
|
||||||
bool ExceptionCallback(Exception* ex);
|
bool ExceptionCallback(Exception* ex);
|
||||||
|
|
||||||
void FireAccessWatch(AccessWatchEntry* entry);
|
|
||||||
void ClearAccessWatch(AccessWatchEntry* entry);
|
|
||||||
bool CheckAccessWatch(uint32_t guest_address, uint32_t guest_heap_address);
|
|
||||||
|
|
||||||
uint8_t* virtual_membase_;
|
uint8_t* virtual_membase_;
|
||||||
uint8_t* physical_membase_;
|
uint8_t* physical_membase_;
|
||||||
uint8_t* memory_end_;
|
uint8_t* memory_end_;
|
||||||
|
@ -123,11 +82,9 @@ class MMIOHandler {
|
||||||
AccessViolationCallback access_violation_callback_;
|
AccessViolationCallback access_violation_callback_;
|
||||||
void* access_violation_callback_context_;
|
void* access_violation_callback_context_;
|
||||||
|
|
||||||
xe::global_critical_region global_critical_region_;
|
|
||||||
// TODO(benvanik): data structure magic.
|
|
||||||
std::list<AccessWatchEntry*> access_watches_;
|
|
||||||
|
|
||||||
static MMIOHandler* global_handler_;
|
static MMIOHandler* global_handler_;
|
||||||
|
|
||||||
|
xe::global_critical_region global_critical_region_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace cpu
|
} // namespace cpu
|
||||||
|
|
|
@ -632,9 +632,8 @@ std::pair<uint32_t, uint32_t> SharedMemory::MemoryWriteCallback(
|
||||||
|
|
||||||
FireWatches(page_first, page_last, false);
|
FireWatches(page_first, page_last, false);
|
||||||
|
|
||||||
return std::make_pair<uint32_t, uint32_t>(page_first << page_size_log2_,
|
return std::make_pair(page_first << page_size_log2_,
|
||||||
(page_last - page_first + 1)
|
(page_last - page_first + 1) << page_size_log2_);
|
||||||
<< page_size_log2_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SharedMemory::TransitionBuffer(D3D12_RESOURCE_STATES new_state) {
|
void SharedMemory::TransitionBuffer(D3D12_RESOURCE_STATES new_state) {
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
#include "xenia/gpu/vulkan/texture_cache.h"
|
#include "xenia/gpu/vulkan/texture_cache.h"
|
||||||
#include "xenia/gpu/vulkan/texture_config.h"
|
#include "xenia/gpu/vulkan/texture_config.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/base/memory.h"
|
#include "xenia/base/memory.h"
|
||||||
|
@ -147,10 +149,19 @@ VkResult TextureCache::Initialize() {
|
||||||
invalidated_textures_ = &invalidated_textures_sets_[0];
|
invalidated_textures_ = &invalidated_textures_sets_[0];
|
||||||
|
|
||||||
device_queue_ = device_->AcquireQueue(device_->queue_family_index());
|
device_queue_ = device_->AcquireQueue(device_->queue_family_index());
|
||||||
|
|
||||||
|
physical_write_watch_handle_ =
|
||||||
|
memory_->RegisterPhysicalWriteWatch(MemoryWriteCallbackThunk, this);
|
||||||
|
|
||||||
return VK_SUCCESS;
|
return VK_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureCache::Shutdown() {
|
void TextureCache::Shutdown() {
|
||||||
|
if (physical_write_watch_handle_ != nullptr) {
|
||||||
|
memory_->UnregisterPhysicalWriteWatch(physical_write_watch_handle_);
|
||||||
|
physical_write_watch_handle_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
if (device_queue_) {
|
if (device_queue_) {
|
||||||
device_->ReleaseQueue(device_queue_, device_->queue_family_index());
|
device_->ReleaseQueue(device_queue_, device_->queue_family_index());
|
||||||
}
|
}
|
||||||
|
@ -290,7 +301,7 @@ TextureCache::Texture* TextureCache::AllocateTexture(
|
||||||
texture->alloc_info = vma_info;
|
texture->alloc_info = vma_info;
|
||||||
texture->framebuffer = nullptr;
|
texture->framebuffer = nullptr;
|
||||||
texture->usage_flags = image_info.usage;
|
texture->usage_flags = image_info.usage;
|
||||||
texture->access_watch_handle = 0;
|
texture->is_watched = false;
|
||||||
texture->texture_info = texture_info;
|
texture->texture_info = texture_info;
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
@ -313,9 +324,19 @@ bool TextureCache::FreeTexture(Texture* texture) {
|
||||||
it = texture->views.erase(it);
|
it = texture->views.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (texture->access_watch_handle) {
|
{
|
||||||
memory_->CancelAccessWatch(texture->access_watch_handle);
|
global_critical_region_.Acquire();
|
||||||
texture->access_watch_handle = 0;
|
if (texture->is_watched) {
|
||||||
|
for (auto it = watched_textures_.begin();
|
||||||
|
it != watched_textures_.end();) {
|
||||||
|
if (it->texture == texture) {
|
||||||
|
watched_textures_.erase(it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
texture->is_watched = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vmaDestroyImage(mem_allocator_, texture->image, texture->alloc);
|
vmaDestroyImage(mem_allocator_, texture->image, texture->alloc);
|
||||||
|
@ -323,25 +344,134 @@ bool TextureCache::FreeTexture(Texture* texture) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureCache::WatchCallback(void* context_ptr, void* data_ptr,
|
void TextureCache::WatchTexture(Texture* texture) {
|
||||||
uint32_t address) {
|
uint32_t address, size;
|
||||||
auto self = reinterpret_cast<TextureCache*>(context_ptr);
|
|
||||||
auto touched_texture = reinterpret_cast<Texture*>(data_ptr);
|
{
|
||||||
if (!touched_texture || !touched_texture->access_watch_handle ||
|
global_critical_region_.Acquire();
|
||||||
touched_texture->pending_invalidation) {
|
|
||||||
|
assert_false(texture->is_watched);
|
||||||
|
|
||||||
|
WatchedTexture watched_texture;
|
||||||
|
if (texture->texture_info.memory.base_address &&
|
||||||
|
texture->texture_info.memory.base_size) {
|
||||||
|
watched_texture.is_mip = false;
|
||||||
|
address = texture->texture_info.memory.base_address;
|
||||||
|
size = texture->texture_info.memory.base_size;
|
||||||
|
} else if (texture->texture_info.memory.mip_address &&
|
||||||
|
texture->texture_info.memory.mip_size) {
|
||||||
|
watched_texture.is_mip = true;
|
||||||
|
address = texture->texture_info.memory.mip_address;
|
||||||
|
size = texture->texture_info.memory.mip_size;
|
||||||
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
watched_texture.texture = texture;
|
||||||
|
|
||||||
assert_not_zero(touched_texture->access_watch_handle);
|
// Fire any access watches that overlap this region.
|
||||||
// Clear watch handle first so we don't redundantly
|
for (auto it = watched_textures_.begin(); it != watched_textures_.end();) {
|
||||||
// remove.
|
// Case 1: 2222222|222|11111111
|
||||||
touched_texture->access_watch_handle = 0;
|
// Case 2: 1111111|222|22222222
|
||||||
touched_texture->pending_invalidation = true;
|
// Case 3: 1111111|222|11111111 (fragmentation)
|
||||||
|
// Case 4: 2222222|222|22222222 (complete overlap)
|
||||||
|
Texture* other_texture = it->texture;
|
||||||
|
uint32_t other_address, other_size;
|
||||||
|
if (it->is_mip) {
|
||||||
|
other_address = other_texture->texture_info.memory.mip_address;
|
||||||
|
other_size = other_texture->texture_info.memory.mip_size;
|
||||||
|
} else {
|
||||||
|
other_address = other_texture->texture_info.memory.base_address;
|
||||||
|
other_size = other_texture->texture_info.memory.base_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hit = false;
|
||||||
|
if (address <= other_address && address + size > other_address) {
|
||||||
|
hit = true;
|
||||||
|
} else if (other_address <= address &&
|
||||||
|
other_address + other_size > address) {
|
||||||
|
hit = true;
|
||||||
|
} else if (other_address <= address &&
|
||||||
|
other_address + other_size > address + size) {
|
||||||
|
hit = true;
|
||||||
|
} else if (other_address >= address &&
|
||||||
|
other_address + other_size < address + size) {
|
||||||
|
hit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hit) {
|
||||||
|
TextureTouched(other_texture);
|
||||||
|
it = watched_textures_.erase(it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
watched_textures_.push_back(watched_texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
memory_->WatchPhysicalMemoryWrite(address, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureCache::TextureTouched(Texture* texture) {
|
||||||
|
if (texture->pending_invalidation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
|
assert_true(texture->is_watched);
|
||||||
|
texture->is_watched = false;
|
||||||
// Add to pending list so Scavenge will clean it up.
|
// Add to pending list so Scavenge will clean it up.
|
||||||
self->invalidated_textures_mutex_.lock();
|
invalidated_textures_->insert(texture);
|
||||||
self->invalidated_textures_->insert(touched_texture);
|
}
|
||||||
self->invalidated_textures_mutex_.unlock();
|
texture->pending_invalidation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<uint32_t, uint32_t> TextureCache::MemoryWriteCallback(
|
||||||
|
uint32_t physical_address_start, uint32_t length, bool exact_range) {
|
||||||
|
global_critical_region_.Acquire();
|
||||||
|
if (watched_textures_.empty()) {
|
||||||
|
return std::make_pair<uint32_t, uint32_t>(0, UINT32_MAX);
|
||||||
|
}
|
||||||
|
// Get the texture within the range, or otherwise get the gap between two
|
||||||
|
// adjacent textures that can be safely unwatched.
|
||||||
|
uint32_t written_range_end = physical_address_start + length;
|
||||||
|
uint32_t previous_end = 0, next_start = UINT32_MAX;
|
||||||
|
for (auto it = watched_textures_.begin(); it != watched_textures_.end();) {
|
||||||
|
Texture* texture = it->texture;
|
||||||
|
uint32_t texture_address, texture_size;
|
||||||
|
if (it->is_mip) {
|
||||||
|
texture_address = texture->texture_info.memory.mip_address;
|
||||||
|
texture_size = texture->texture_info.memory.mip_size;
|
||||||
|
} else {
|
||||||
|
texture_address = texture->texture_info.memory.base_address;
|
||||||
|
texture_size = texture->texture_info.memory.base_size;
|
||||||
|
}
|
||||||
|
if (texture_address >= written_range_end) {
|
||||||
|
// Completely after the written range.
|
||||||
|
next_start = std::min(next_start, texture_address);
|
||||||
|
} else {
|
||||||
|
uint32_t texture_end = texture_address + texture_size;
|
||||||
|
if (texture_end <= physical_address_start) {
|
||||||
|
// Completely before the written range.
|
||||||
|
previous_end = std::max(previous_end, texture_end);
|
||||||
|
} else {
|
||||||
|
// Hit.
|
||||||
|
TextureTouched(texture);
|
||||||
|
it = watched_textures_.erase(it);
|
||||||
|
return std::make_pair(texture_address, texture_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
return std::make_pair(previous_end, next_start - previous_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<uint32_t, uint32_t> TextureCache::MemoryWriteCallbackThunk(
|
||||||
|
void* context_ptr, uint32_t physical_address_start, uint32_t length,
|
||||||
|
bool exact_range) {
|
||||||
|
return reinterpret_cast<TextureCache*>(context_ptr)
|
||||||
|
->MemoryWriteCallback(physical_address_start, length, exact_range);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureCache::Texture* TextureCache::DemandResolveTexture(
|
TextureCache::Texture* TextureCache::DemandResolveTexture(
|
||||||
|
@ -396,15 +526,7 @@ TextureCache::Texture* TextureCache::DemandResolveTexture(
|
||||||
get_dimension_name(texture_info.dimension)));
|
get_dimension_name(texture_info.dimension)));
|
||||||
|
|
||||||
// Setup an access watch. If this texture is touched, it is destroyed.
|
// Setup an access watch. If this texture is touched, it is destroyed.
|
||||||
if (texture_info.memory.base_address && texture_info.memory.base_size) {
|
WatchTexture(texture);
|
||||||
texture->access_watch_handle = memory_->AddPhysicalAccessWatch(
|
|
||||||
texture_info.memory.base_address, texture_info.memory.base_size,
|
|
||||||
cpu::MMIOHandler::kWatchWrite, &WatchCallback, this, texture);
|
|
||||||
} else if (texture_info.memory.mip_address && texture_info.memory.mip_size) {
|
|
||||||
texture->access_watch_handle = memory_->AddPhysicalAccessWatch(
|
|
||||||
texture_info.memory.mip_address, texture_info.memory.mip_size,
|
|
||||||
cpu::MMIOHandler::kWatchWrite, &WatchCallback, this, texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
textures_[texture_hash] = texture;
|
textures_[texture_hash] = texture;
|
||||||
COUNT_profile_set("gpu/texture_cache/textures", textures_.size());
|
COUNT_profile_set("gpu/texture_cache/textures", textures_.size());
|
||||||
|
@ -491,15 +613,7 @@ TextureCache::Texture* TextureCache::Demand(const TextureInfo& texture_info,
|
||||||
|
|
||||||
// Okay. Put a writewatch on it to tell us if it's been modified from the
|
// Okay. Put a writewatch on it to tell us if it's been modified from the
|
||||||
// guest.
|
// guest.
|
||||||
if (texture_info.memory.base_address && texture_info.memory.base_size) {
|
WatchTexture(texture);
|
||||||
texture->access_watch_handle = memory_->AddPhysicalAccessWatch(
|
|
||||||
texture_info.memory.base_address, texture_info.memory.base_size,
|
|
||||||
cpu::MMIOHandler::kWatchWrite, &WatchCallback, this, texture);
|
|
||||||
} else if (texture_info.memory.mip_address && texture_info.memory.mip_size) {
|
|
||||||
texture->access_watch_handle = memory_->AddPhysicalAccessWatch(
|
|
||||||
texture_info.memory.mip_address, texture_info.memory.mip_size,
|
|
||||||
cpu::MMIOHandler::kWatchWrite, &WatchCallback, this, texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
@ -1442,15 +1556,17 @@ bool TextureCache::SetupTextureBinding(VkCommandBuffer command_buffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureCache::RemoveInvalidatedTextures() {
|
void TextureCache::RemoveInvalidatedTextures() {
|
||||||
// Clean up any invalidated textures.
|
|
||||||
invalidated_textures_mutex_.lock();
|
|
||||||
std::unordered_set<Texture*>& invalidated_textures = *invalidated_textures_;
|
std::unordered_set<Texture*>& invalidated_textures = *invalidated_textures_;
|
||||||
|
|
||||||
|
// Clean up any invalidated textures.
|
||||||
|
{
|
||||||
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
if (invalidated_textures_ == &invalidated_textures_sets_[0]) {
|
if (invalidated_textures_ == &invalidated_textures_sets_[0]) {
|
||||||
invalidated_textures_ = &invalidated_textures_sets_[1];
|
invalidated_textures_ = &invalidated_textures_sets_[1];
|
||||||
} else {
|
} else {
|
||||||
invalidated_textures_ = &invalidated_textures_sets_[0];
|
invalidated_textures_ = &invalidated_textures_sets_[0];
|
||||||
}
|
}
|
||||||
invalidated_textures_mutex_.unlock();
|
}
|
||||||
|
|
||||||
// Append all invalidated textures to a deletion queue. They will be deleted
|
// Append all invalidated textures to a deletion queue. They will be deleted
|
||||||
// when all command buffers using them have finished executing.
|
// when all command buffers using them have finished executing.
|
||||||
|
|
|
@ -10,9 +10,12 @@
|
||||||
#ifndef XENIA_GPU_VULKAN_TEXTURE_CACHE_H_
|
#ifndef XENIA_GPU_VULKAN_TEXTURE_CACHE_H_
|
||||||
#define XENIA_GPU_VULKAN_TEXTURE_CACHE_H_
|
#define XENIA_GPU_VULKAN_TEXTURE_CACHE_H_
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <list>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#include "xenia/base/mutex.h"
|
||||||
#include "xenia/gpu/register_file.h"
|
#include "xenia/gpu/register_file.h"
|
||||||
#include "xenia/gpu/sampler_info.h"
|
#include "xenia/gpu/sampler_info.h"
|
||||||
#include "xenia/gpu/shader.h"
|
#include "xenia/gpu/shader.h"
|
||||||
|
@ -50,7 +53,7 @@ class TextureCache {
|
||||||
VkFramebuffer framebuffer; // Blit target frame buffer.
|
VkFramebuffer framebuffer; // Blit target frame buffer.
|
||||||
VkImageUsageFlags usage_flags;
|
VkImageUsageFlags usage_flags;
|
||||||
|
|
||||||
uintptr_t access_watch_handle;
|
bool is_watched;
|
||||||
bool pending_invalidation;
|
bool pending_invalidation;
|
||||||
|
|
||||||
// Pointer to the latest usage fence.
|
// Pointer to the latest usage fence.
|
||||||
|
@ -131,14 +134,24 @@ class TextureCache {
|
||||||
VkSampler sampler;
|
VkSampler sampler;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct WatchedTexture {
|
||||||
|
Texture* texture;
|
||||||
|
bool is_mip;
|
||||||
|
};
|
||||||
|
|
||||||
// Allocates a new texture and memory to back it on the GPU.
|
// Allocates a new texture and memory to back it on the GPU.
|
||||||
Texture* AllocateTexture(const TextureInfo& texture_info,
|
Texture* AllocateTexture(const TextureInfo& texture_info,
|
||||||
VkFormatFeatureFlags required_flags =
|
VkFormatFeatureFlags required_flags =
|
||||||
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT);
|
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT);
|
||||||
bool FreeTexture(Texture* texture);
|
bool FreeTexture(Texture* texture);
|
||||||
|
|
||||||
static void WatchCallback(void* context_ptr, void* data_ptr,
|
void WatchTexture(Texture* texture);
|
||||||
uint32_t address);
|
void TextureTouched(Texture* texture);
|
||||||
|
std::pair<uint32_t, uint32_t> MemoryWriteCallback(
|
||||||
|
uint32_t physical_address_start, uint32_t length, bool exact_range);
|
||||||
|
static std::pair<uint32_t, uint32_t> MemoryWriteCallbackThunk(
|
||||||
|
void* context_ptr, uint32_t physical_address_start, uint32_t length,
|
||||||
|
bool exact_range);
|
||||||
|
|
||||||
// Demands a texture. If command_buffer is null and the texture hasn't been
|
// Demands a texture. If command_buffer is null and the texture hasn't been
|
||||||
// uploaded to graphics memory already, we will return null and bail.
|
// uploaded to graphics memory already, we will return null and bail.
|
||||||
|
@ -207,7 +220,10 @@ class TextureCache {
|
||||||
std::unordered_map<uint64_t, Sampler*> samplers_;
|
std::unordered_map<uint64_t, Sampler*> samplers_;
|
||||||
std::list<Texture*> pending_delete_textures_;
|
std::list<Texture*> pending_delete_textures_;
|
||||||
|
|
||||||
std::mutex invalidated_textures_mutex_;
|
void* physical_write_watch_handle_ = nullptr;
|
||||||
|
|
||||||
|
xe::global_critical_region global_critical_region_;
|
||||||
|
std::list<WatchedTexture> watched_textures_;
|
||||||
std::unordered_set<Texture*>* invalidated_textures_;
|
std::unordered_set<Texture*>* invalidated_textures_;
|
||||||
std::unordered_set<Texture*> invalidated_textures_sets_[2];
|
std::unordered_set<Texture*> invalidated_textures_sets_[2];
|
||||||
|
|
||||||
|
|
|
@ -443,31 +443,12 @@ bool Memory::AccessViolationCallbackThunk(void* context, size_t host_address,
|
||||||
host_address, is_write);
|
host_address, is_write);
|
||||||
}
|
}
|
||||||
|
|
||||||
uintptr_t Memory::AddPhysicalAccessWatch(uint32_t physical_address,
|
|
||||||
uint32_t length,
|
|
||||||
cpu::MMIOHandler::WatchType type,
|
|
||||||
cpu::AccessWatchCallback callback,
|
|
||||||
void* callback_context,
|
|
||||||
void* callback_data) {
|
|
||||||
return mmio_handler_->AddPhysicalAccessWatch(physical_address, length, type,
|
|
||||||
callback, callback_context,
|
|
||||||
callback_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Memory::CancelAccessWatch(uintptr_t watch_handle) {
|
|
||||||
mmio_handler_->CancelAccessWatch(watch_handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Memory::TriggerWatches(uint32_t virtual_address, uint32_t length,
|
bool Memory::TriggerWatches(uint32_t virtual_address, uint32_t length,
|
||||||
bool is_write, bool unwatch_exact_range,
|
bool is_write, bool unwatch_exact_range,
|
||||||
bool unprotect) {
|
bool unprotect) {
|
||||||
BaseHeap* heap = LookupHeap(virtual_address);
|
BaseHeap* heap = LookupHeap(virtual_address);
|
||||||
if (heap == &heaps_.vA0000000 || heap == &heaps_.vC0000000 ||
|
if (heap == &heaps_.vA0000000 || heap == &heaps_.vC0000000 ||
|
||||||
heap == &heaps_.vE0000000) {
|
heap == &heaps_.vE0000000) {
|
||||||
// TODO(Triang3l): Remove InvalidateRange when legacy (old Vulkan renderer)
|
|
||||||
// watches are removed.
|
|
||||||
cpu::MMIOHandler::global_handler()->InvalidateRange(virtual_address,
|
|
||||||
length);
|
|
||||||
return static_cast<PhysicalHeap*>(heap)->TriggerWatches(
|
return static_cast<PhysicalHeap*>(heap)->TriggerWatches(
|
||||||
virtual_address, length, is_write, unwatch_exact_range, unprotect);
|
virtual_address, length, is_write, unwatch_exact_range, unprotect);
|
||||||
}
|
}
|
||||||
|
@ -1456,10 +1437,6 @@ bool PhysicalHeap::Release(uint32_t base_address, uint32_t* out_region_size) {
|
||||||
uint32_t parent_base_address = GetPhysicalAddress(base_address);
|
uint32_t parent_base_address = GetPhysicalAddress(base_address);
|
||||||
uint32_t region_size = 0;
|
uint32_t region_size = 0;
|
||||||
if (QuerySize(base_address, ®ion_size)) {
|
if (QuerySize(base_address, ®ion_size)) {
|
||||||
// TODO(Triang3l): Remove InvalidateRange when legacy (old Vulkan renderer)
|
|
||||||
// watches are removed.
|
|
||||||
cpu::MMIOHandler::global_handler()->InvalidateRange(base_address,
|
|
||||||
region_size);
|
|
||||||
TriggerWatches(base_address, region_size, true, true,
|
TriggerWatches(base_address, region_size, true, true,
|
||||||
!cvars::protect_on_release);
|
!cvars::protect_on_release);
|
||||||
}
|
}
|
||||||
|
@ -1476,9 +1453,6 @@ bool PhysicalHeap::Protect(uint32_t address, uint32_t size, uint32_t protect,
|
||||||
uint32_t* old_protect) {
|
uint32_t* old_protect) {
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
|
|
||||||
// TODO(Triang3l): Remove InvalidateRange when legacy (old Vulkan renderer)
|
|
||||||
// watches are removed.
|
|
||||||
cpu::MMIOHandler::global_handler()->InvalidateRange(address, size);
|
|
||||||
TriggerWatches(address, size, true, true, false);
|
TriggerWatches(address, size, true, true, false);
|
||||||
|
|
||||||
if (!parent_heap_->Protect(GetPhysicalAddress(address), size, protect,
|
if (!parent_heap_->Protect(GetPhysicalAddress(address), size, protect,
|
||||||
|
|
|
@ -319,21 +319,6 @@ class Memory {
|
||||||
// Gets the defined MMIO range for the given virtual address, if any.
|
// Gets the defined MMIO range for the given virtual address, if any.
|
||||||
cpu::MMIORange* LookupVirtualMappedRange(uint32_t virtual_address);
|
cpu::MMIORange* LookupVirtualMappedRange(uint32_t virtual_address);
|
||||||
|
|
||||||
// Adds a write watch for the given physical address range that will trigger
|
|
||||||
// the specified callback whenever any bytes are written in that range.
|
|
||||||
// The returned handle can be used with CancelWriteWatch to remove the watch
|
|
||||||
// if it is no longer required.
|
|
||||||
//
|
|
||||||
// This has a significant performance penalty for writes in in the range or
|
|
||||||
// nearby (sharing 64KiB pages).
|
|
||||||
uintptr_t AddPhysicalAccessWatch(uint32_t physical_address, uint32_t length,
|
|
||||||
cpu::MMIOHandler::WatchType type,
|
|
||||||
cpu::AccessWatchCallback callback,
|
|
||||||
void* callback_context, void* callback_data);
|
|
||||||
|
|
||||||
// Cancels a write watch requested with AddPhysicalAccessWatch.
|
|
||||||
void CancelAccessWatch(uintptr_t watch_handle);
|
|
||||||
|
|
||||||
// Returns start and length of the smallest physical memory region surrounding
|
// Returns start and length of the smallest physical memory region surrounding
|
||||||
// the watched region that can be safely unwatched, if it doesn't matter,
|
// the watched region that can be safely unwatched, if it doesn't matter,
|
||||||
// return (0, UINT32_MAX).
|
// return (0, UINT32_MAX).
|
||||||
|
|
Loading…
Reference in New Issue