Basic hacky write watching for texture invalidation. Doesn't scale.
This commit is contained in:
parent
55c4488ab2
commit
0529fdb84d
|
@ -78,6 +78,109 @@ bool MMIOHandler::CheckStore(uint64_t address, uint64_t value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uintptr_t MMIOHandler::AddWriteWatch(uint32_t guest_address, size_t length,
|
||||||
|
WriteWatchCallback callback,
|
||||||
|
void* callback_context,
|
||||||
|
void* callback_data) {
|
||||||
|
uint32_t base_address = guest_address;
|
||||||
|
if (base_address > 0xA0000000) {
|
||||||
|
base_address -= 0xA0000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to table. The slot reservation may evict a previous watch, which
|
||||||
|
// could include our target, so we do it first.
|
||||||
|
auto entry = new WriteWatchEntry();
|
||||||
|
entry->address = base_address;
|
||||||
|
entry->length = uint32_t(length);
|
||||||
|
entry->callback = callback;
|
||||||
|
entry->callback_context = callback_context;
|
||||||
|
entry->callback_data = callback_data;
|
||||||
|
write_watch_mutex_.lock();
|
||||||
|
write_watches_.push_back(entry);
|
||||||
|
write_watch_mutex_.unlock();
|
||||||
|
|
||||||
|
// Make the desired range read only under all address spaces.
|
||||||
|
auto host_address = mapping_base_ + base_address;
|
||||||
|
DWORD old_protect;
|
||||||
|
VirtualProtect(host_address, length, PAGE_READONLY, &old_protect);
|
||||||
|
VirtualProtect(host_address + 0xA0000000, length, PAGE_READONLY,
|
||||||
|
&old_protect);
|
||||||
|
VirtualProtect(host_address + 0xC0000000, length, PAGE_READONLY,
|
||||||
|
&old_protect);
|
||||||
|
VirtualProtect(host_address + 0xE0000000, length, PAGE_READONLY,
|
||||||
|
&old_protect);
|
||||||
|
|
||||||
|
return reinterpret_cast<uintptr_t>(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMIOHandler::ClearWriteWatch(WriteWatchEntry* entry) {
|
||||||
|
auto host_address = mapping_base_ + entry->address;
|
||||||
|
DWORD old_protect;
|
||||||
|
VirtualProtect(host_address, entry->length, PAGE_READWRITE, nullptr);
|
||||||
|
VirtualProtect(host_address + 0xA0000000, entry->length, PAGE_READWRITE,
|
||||||
|
&old_protect);
|
||||||
|
VirtualProtect(host_address + 0xC0000000, entry->length, PAGE_READWRITE,
|
||||||
|
&old_protect);
|
||||||
|
VirtualProtect(host_address + 0xE0000000, entry->length, PAGE_READWRITE,
|
||||||
|
&old_protect);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MMIOHandler::CancelWriteWatch(uintptr_t watch_handle) {
|
||||||
|
auto entry = reinterpret_cast<WriteWatchEntry*>(watch_handle);
|
||||||
|
|
||||||
|
// Allow access to the range again.
|
||||||
|
ClearWriteWatch(entry);
|
||||||
|
|
||||||
|
// Remove from table.
|
||||||
|
write_watch_mutex_.lock();
|
||||||
|
auto it = std::find(write_watches_.begin(), write_watches_.end(), entry);
|
||||||
|
if (it != write_watches_.end()) {
|
||||||
|
write_watches_.erase(it);
|
||||||
|
}
|
||||||
|
write_watch_mutex_.unlock();
|
||||||
|
|
||||||
|
delete entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MMIOHandler::CheckWriteWatch(void* thread_state, uint64_t fault_address) {
|
||||||
|
uint32_t guest_address = uint32_t(fault_address - uintptr_t(mapping_base_));
|
||||||
|
uint32_t base_address = guest_address;
|
||||||
|
if (base_address > 0xA0000000) {
|
||||||
|
base_address -= 0xA0000000;
|
||||||
|
}
|
||||||
|
std::list<WriteWatchEntry*> pending_invalidates;
|
||||||
|
write_watch_mutex_.lock();
|
||||||
|
for (auto it = write_watches_.begin(); it != write_watches_.end();) {
|
||||||
|
auto entry = *it;
|
||||||
|
if (entry->address <= base_address &&
|
||||||
|
entry->address + entry->length > base_address) {
|
||||||
|
// Hit!
|
||||||
|
pending_invalidates.push_back(entry);
|
||||||
|
// TODO(benvanik): outside of lock?
|
||||||
|
ClearWriteWatch(entry);
|
||||||
|
auto erase_it = it;
|
||||||
|
++it;
|
||||||
|
write_watches_.erase(erase_it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
write_watch_mutex_.unlock();
|
||||||
|
if (pending_invalidates.empty()) {
|
||||||
|
// Rethrow access violation - range was not being watched.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
while (!pending_invalidates.empty()) {
|
||||||
|
auto entry = pending_invalidates.back();
|
||||||
|
pending_invalidates.pop_back();
|
||||||
|
entry->callback(entry->callback_context, entry->callback_data,
|
||||||
|
guest_address);
|
||||||
|
delete entry;
|
||||||
|
}
|
||||||
|
// Range was watched, so lets eat this access violation.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool MMIOHandler::HandleAccessFault(void* thread_state,
|
bool MMIOHandler::HandleAccessFault(void* thread_state,
|
||||||
uint64_t fault_address) {
|
uint64_t fault_address) {
|
||||||
// Access violations are pretty rare, so we can do a linear search here.
|
// Access violations are pretty rare, so we can do a linear search here.
|
||||||
|
@ -92,7 +195,7 @@ bool MMIOHandler::HandleAccessFault(void* thread_state,
|
||||||
if (!range) {
|
if (!range) {
|
||||||
// Access is not found within any range, so fail and let the caller handle
|
// Access is not found within any range, so fail and let the caller handle
|
||||||
// it (likely by aborting).
|
// it (likely by aborting).
|
||||||
return false;
|
return CheckWriteWatch(thread_state, fault_address);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(benvanik): replace with simple check of mov (that's all
|
// TODO(benvanik): replace with simple check of mov (that's all
|
||||||
|
@ -175,8 +278,7 @@ bool MMIOHandler::HandleAccessFault(void* thread_state,
|
||||||
}
|
}
|
||||||
range->write(range->context, fault_address & 0xFFFFFFFF, value);
|
range->write(range->context, fault_address & 0xFFFFFFFF, value);
|
||||||
} else {
|
} else {
|
||||||
// Unknown MMIO instruction type.
|
assert_always("Unknown MMIO instruction type");
|
||||||
assert_always();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,9 @@
|
||||||
#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 <mutex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -19,6 +21,9 @@ namespace cpu {
|
||||||
typedef uint64_t (*MMIOReadCallback)(void* context, uint64_t addr);
|
typedef uint64_t (*MMIOReadCallback)(void* context, uint64_t addr);
|
||||||
typedef void (*MMIOWriteCallback)(void* context, uint64_t addr, uint64_t value);
|
typedef void (*MMIOWriteCallback)(void* context, uint64_t addr, uint64_t value);
|
||||||
|
|
||||||
|
typedef void (*WriteWatchCallback)(void* context_ptr, void* data_ptr,
|
||||||
|
uint32_t address);
|
||||||
|
|
||||||
// NOTE: only one can exist at a time!
|
// NOTE: only one can exist at a time!
|
||||||
class MMIOHandler {
|
class MMIOHandler {
|
||||||
public:
|
public:
|
||||||
|
@ -34,14 +39,30 @@ class MMIOHandler {
|
||||||
bool CheckLoad(uint64_t address, uint64_t* out_value);
|
bool CheckLoad(uint64_t address, uint64_t* out_value);
|
||||||
bool CheckStore(uint64_t address, uint64_t value);
|
bool CheckStore(uint64_t address, uint64_t value);
|
||||||
|
|
||||||
|
uintptr_t AddWriteWatch(uint32_t guest_address, size_t length,
|
||||||
|
WriteWatchCallback callback, void* callback_context,
|
||||||
|
void* callback_data);
|
||||||
|
void CancelWriteWatch(uintptr_t watch_handle);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool HandleAccessFault(void* thread_state, uint64_t fault_address);
|
bool HandleAccessFault(void* thread_state, uint64_t fault_address);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
struct WriteWatchEntry {
|
||||||
|
uint32_t address;
|
||||||
|
uint32_t length;
|
||||||
|
WriteWatchCallback callback;
|
||||||
|
void* callback_context;
|
||||||
|
void* callback_data;
|
||||||
|
};
|
||||||
|
|
||||||
MMIOHandler(uint8_t* mapping_base) : mapping_base_(mapping_base) {}
|
MMIOHandler(uint8_t* mapping_base) : mapping_base_(mapping_base) {}
|
||||||
|
|
||||||
virtual bool Initialize() = 0;
|
virtual bool Initialize() = 0;
|
||||||
|
|
||||||
|
void ClearWriteWatch(WriteWatchEntry* entry);
|
||||||
|
bool CheckWriteWatch(void* thread_state, uint64_t fault_address);
|
||||||
|
|
||||||
virtual uint64_t GetThreadStateRip(void* thread_state_ptr) = 0;
|
virtual uint64_t GetThreadStateRip(void* thread_state_ptr) = 0;
|
||||||
virtual void SetThreadStateRip(void* thread_state_ptr, uint64_t rip) = 0;
|
virtual void SetThreadStateRip(void* thread_state_ptr, uint64_t rip) = 0;
|
||||||
virtual uint64_t* GetThreadStateRegPtr(void* thread_state_ptr,
|
virtual uint64_t* GetThreadStateRegPtr(void* thread_state_ptr,
|
||||||
|
@ -59,6 +80,10 @@ class MMIOHandler {
|
||||||
};
|
};
|
||||||
std::vector<MMIORange> mapped_ranges_;
|
std::vector<MMIORange> mapped_ranges_;
|
||||||
|
|
||||||
|
// TODO(benvanik): data structure magic.
|
||||||
|
std::mutex write_watch_mutex_;
|
||||||
|
std::list<WriteWatchEntry*> write_watches_;
|
||||||
|
|
||||||
static MMIOHandler* global_handler_;
|
static MMIOHandler* global_handler_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -183,7 +183,7 @@ bool CommandProcessor::SetupGL() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Texture cache that keeps track of any textures/samplers used.
|
// Texture cache that keeps track of any textures/samplers used.
|
||||||
if (!texture_cache_.Initialize(membase_, &scratch_buffer_)) {
|
if (!texture_cache_.Initialize(memory_, &scratch_buffer_)) {
|
||||||
PLOGE("Unable to initialize texture cache");
|
PLOGE("Unable to initialize texture cache");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ using namespace xe::gpu::xenos;
|
||||||
extern "C" GLEWContext* glewGetContext();
|
extern "C" GLEWContext* glewGetContext();
|
||||||
extern "C" WGLEWContext* wglewGetContext();
|
extern "C" WGLEWContext* wglewGetContext();
|
||||||
|
|
||||||
TextureCache::TextureCache() : membase_(nullptr), scratch_buffer_(nullptr) {
|
TextureCache::TextureCache() : memory_(nullptr), scratch_buffer_(nullptr) {
|
||||||
invalidated_textures_sets_[0].reserve(64);
|
invalidated_textures_sets_[0].reserve(64);
|
||||||
invalidated_textures_sets_[1].reserve(64);
|
invalidated_textures_sets_[1].reserve(64);
|
||||||
invalidated_textures_ = &invalidated_textures_sets_[0];
|
invalidated_textures_ = &invalidated_textures_sets_[0];
|
||||||
|
@ -30,9 +30,8 @@ TextureCache::TextureCache() : membase_(nullptr), scratch_buffer_(nullptr) {
|
||||||
|
|
||||||
TextureCache::~TextureCache() { Shutdown(); }
|
TextureCache::~TextureCache() { Shutdown(); }
|
||||||
|
|
||||||
bool TextureCache::Initialize(uint8_t* membase,
|
bool TextureCache::Initialize(Memory* memory, CircularBuffer* scratch_buffer) {
|
||||||
CircularBuffer* scratch_buffer) {
|
memory_ = memory;
|
||||||
membase_ = membase;
|
|
||||||
scratch_buffer_ = scratch_buffer;
|
scratch_buffer_ = scratch_buffer;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -59,18 +58,22 @@ void TextureCache::Scavenge() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureCache::Clear() {
|
void TextureCache::Clear() {
|
||||||
// Kill all textures - some may be in the eviction list, but that's fine
|
EvictAllTextures();
|
||||||
// as we will clear that below.
|
|
||||||
while (texture_entries_.size()) {
|
|
||||||
auto entry = texture_entries_.begin()->second;
|
|
||||||
EvictTexture(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Samplers must go last, as textures depend on them.
|
// Samplers must go last, as textures depend on them.
|
||||||
while (sampler_entries_.size()) {
|
while (sampler_entries_.size()) {
|
||||||
auto entry = sampler_entries_.begin()->second;
|
auto entry = sampler_entries_.begin()->second;
|
||||||
EvictSampler(entry);
|
EvictSampler(entry);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureCache::EvictAllTextures() {
|
||||||
|
// Kill all textures - some may be in the eviction list, but that's fine
|
||||||
|
// as we will clear that below.
|
||||||
|
while (texture_entries_.size()) {
|
||||||
|
auto entry = texture_entries_.begin()->second;
|
||||||
|
EvictTexture(entry);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(invalidated_textures_mutex_);
|
std::lock_guard<std::mutex> lock(invalidated_textures_mutex_);
|
||||||
|
@ -79,13 +82,6 @@ void TextureCache::Clear() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//typedef void (*WriteWatchCallback)(void* context, void* data, void* address);
|
|
||||||
//uintptr_t AddWriteWatch(void* address, size_t length,
|
|
||||||
// WriteWatchCallback callback, void* callback_context,
|
|
||||||
// void* callback_data) {
|
|
||||||
// //
|
|
||||||
//}
|
|
||||||
|
|
||||||
TextureCache::TextureEntryView* TextureCache::Demand(
|
TextureCache::TextureEntryView* TextureCache::Demand(
|
||||||
const TextureInfo& texture_info, const SamplerInfo& sampler_info) {
|
const TextureInfo& texture_info, const SamplerInfo& sampler_info) {
|
||||||
uint64_t texture_hash = texture_info.hash();
|
uint64_t texture_hash = texture_info.hash();
|
||||||
|
@ -121,6 +117,7 @@ TextureCache::TextureEntryView* TextureCache::Demand(
|
||||||
view->texture_sampler_handle = glGetTextureSamplerHandleARB(
|
view->texture_sampler_handle = glGetTextureSamplerHandleARB(
|
||||||
texture_entry->handle, sampler_entry->handle);
|
texture_entry->handle, sampler_entry->handle);
|
||||||
if (!view->texture_sampler_handle) {
|
if (!view->texture_sampler_handle) {
|
||||||
|
assert_always("Unable to get texture handle?");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
glMakeTextureHandleResidentARB(view->texture_sampler_handle);
|
glMakeTextureHandleResidentARB(view->texture_sampler_handle);
|
||||||
|
@ -261,6 +258,12 @@ TextureCache::TextureEntry* TextureCache::LookupOrInsertTexture(
|
||||||
const uint64_t hash = opt_hash ? opt_hash : texture_info.hash();
|
const uint64_t hash = opt_hash ? opt_hash : texture_info.hash();
|
||||||
for (auto it = texture_entries_.find(hash); it != texture_entries_.end();
|
for (auto it = texture_entries_.find(hash); it != texture_entries_.end();
|
||||||
++it) {
|
++it) {
|
||||||
|
if (it->second->pending_invalidation) {
|
||||||
|
// Whoa, we've been invalidated! Let's scavenge to cleanup and try again.
|
||||||
|
// TODO(benvanik): reuse existing texture storage.
|
||||||
|
Scavenge();
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (it->second->texture_info == texture_info) {
|
if (it->second->texture_info == texture_info) {
|
||||||
// Found in cache!
|
// Found in cache!
|
||||||
return it->second;
|
return it->second;
|
||||||
|
@ -270,6 +273,8 @@ TextureCache::TextureEntry* TextureCache::LookupOrInsertTexture(
|
||||||
// Not found, create.
|
// Not found, create.
|
||||||
auto entry = std::make_unique<TextureEntry>();
|
auto entry = std::make_unique<TextureEntry>();
|
||||||
entry->texture_info = texture_info;
|
entry->texture_info = texture_info;
|
||||||
|
entry->write_watch_handle = 0;
|
||||||
|
entry->pending_invalidation = false;
|
||||||
entry->handle = 0;
|
entry->handle = 0;
|
||||||
|
|
||||||
GLenum target;
|
GLenum target;
|
||||||
|
@ -331,10 +336,24 @@ TextureCache::TextureEntry* TextureCache::LookupOrInsertTexture(
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddWriteWatch(host_base, length, [](void* context_ptr, void* data_ptr,
|
// Add a write watch. If any data in the given range is touched we'll get a
|
||||||
// void* address) {
|
// callback and evict the texture. We could reuse the storage, though the
|
||||||
// //
|
// driver is likely in a better position to pool that kind of stuff.
|
||||||
//}, this, &entry);
|
entry->write_watch_handle = memory_->AddWriteWatch(
|
||||||
|
texture_info.guest_address, texture_info.input_length,
|
||||||
|
[](void* context_ptr, void* data_ptr, uint32_t address) {
|
||||||
|
auto self = reinterpret_cast<TextureCache*>(context_ptr);
|
||||||
|
auto touched_entry = reinterpret_cast<TextureEntry*>(data_ptr);
|
||||||
|
// Clear watch handle first so we don't redundantly
|
||||||
|
// remove.
|
||||||
|
touched_entry->write_watch_handle = 0;
|
||||||
|
touched_entry->pending_invalidation = true;
|
||||||
|
// Add to pending list so Scavenge will clean it up.
|
||||||
|
self->invalidated_textures_mutex_.lock();
|
||||||
|
self->invalidated_textures_->push_back(touched_entry);
|
||||||
|
self->invalidated_textures_mutex_.unlock();
|
||||||
|
},
|
||||||
|
this, entry.get());
|
||||||
|
|
||||||
// Add to map - map takes ownership.
|
// Add to map - map takes ownership.
|
||||||
auto entry_ptr = entry.get();
|
auto entry_ptr = entry.get();
|
||||||
|
@ -343,9 +362,10 @@ TextureCache::TextureEntry* TextureCache::LookupOrInsertTexture(
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureCache::EvictTexture(TextureEntry* entry) {
|
void TextureCache::EvictTexture(TextureEntry* entry) {
|
||||||
/*if (entry->write_watch_handle) {
|
if (entry->write_watch_handle) {
|
||||||
// remove from watch list
|
memory_->CancelWriteWatch(entry->write_watch_handle);
|
||||||
}*/
|
entry->write_watch_handle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& view : entry->views) {
|
for (auto& view : entry->views) {
|
||||||
glMakeTextureHandleNonResidentARB(view->texture_sampler_handle);
|
glMakeTextureHandleNonResidentARB(view->texture_sampler_handle);
|
||||||
|
@ -394,8 +414,7 @@ void TextureSwap(Endian endianness, void* dest, const void* src,
|
||||||
|
|
||||||
bool TextureCache::UploadTexture2D(GLuint texture,
|
bool TextureCache::UploadTexture2D(GLuint texture,
|
||||||
const TextureInfo& texture_info) {
|
const TextureInfo& texture_info) {
|
||||||
auto host_address =
|
const auto host_address = memory_->Translate(texture_info.guest_address);
|
||||||
reinterpret_cast<const uint8_t*>(membase_ + texture_info.guest_address);
|
|
||||||
|
|
||||||
GLenum internal_format = GL_RGBA8;
|
GLenum internal_format = GL_RGBA8;
|
||||||
GLenum format = GL_RGBA;
|
GLenum format = GL_RGBA;
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include <xenia/gpu/gl4/gl_context.h>
|
#include <xenia/gpu/gl4/gl_context.h>
|
||||||
#include <xenia/gpu/sampler_info.h>
|
#include <xenia/gpu/sampler_info.h>
|
||||||
#include <xenia/gpu/texture_info.h>
|
#include <xenia/gpu/texture_info.h>
|
||||||
|
#include <xenia/memory.h>
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
|
@ -36,18 +37,21 @@ class TextureCache {
|
||||||
};
|
};
|
||||||
struct TextureEntry {
|
struct TextureEntry {
|
||||||
TextureInfo texture_info;
|
TextureInfo texture_info;
|
||||||
|
uintptr_t write_watch_handle;
|
||||||
GLuint handle;
|
GLuint handle;
|
||||||
|
bool pending_invalidation;
|
||||||
std::vector<std::unique_ptr<TextureEntryView>> views;
|
std::vector<std::unique_ptr<TextureEntryView>> views;
|
||||||
};
|
};
|
||||||
|
|
||||||
TextureCache();
|
TextureCache();
|
||||||
~TextureCache();
|
~TextureCache();
|
||||||
|
|
||||||
bool Initialize(uint8_t* membase, CircularBuffer* scratch_buffer);
|
bool Initialize(Memory* memory, CircularBuffer* scratch_buffer);
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
void Scavenge();
|
void Scavenge();
|
||||||
void Clear();
|
void Clear();
|
||||||
|
void EvictAllTextures();
|
||||||
|
|
||||||
TextureEntryView* Demand(const TextureInfo& texture_info,
|
TextureEntryView* Demand(const TextureInfo& texture_info,
|
||||||
const SamplerInfo& sampler_info);
|
const SamplerInfo& sampler_info);
|
||||||
|
@ -62,7 +66,7 @@ class TextureCache {
|
||||||
|
|
||||||
bool UploadTexture2D(GLuint texture, const TextureInfo& texture_info);
|
bool UploadTexture2D(GLuint texture, const TextureInfo& texture_info);
|
||||||
|
|
||||||
uint8_t* membase_;
|
Memory* memory_;
|
||||||
CircularBuffer* scratch_buffer_;
|
CircularBuffer* scratch_buffer_;
|
||||||
std::unordered_map<uint64_t, SamplerEntry*> sampler_entries_;
|
std::unordered_map<uint64_t, SamplerEntry*> sampler_entries_;
|
||||||
std::unordered_map<uint64_t, TextureEntry*> texture_entries_;
|
std::unordered_map<uint64_t, TextureEntry*> texture_entries_;
|
||||||
|
|
|
@ -270,6 +270,17 @@ bool Memory::AddMappedRange(uint64_t address, uint64_t mask, uint64_t size,
|
||||||
read_callback, write_callback);
|
read_callback, write_callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uintptr_t Memory::AddWriteWatch(uint32_t guest_address, size_t length,
|
||||||
|
cpu::WriteWatchCallback callback,
|
||||||
|
void* callback_context, void* callback_data) {
|
||||||
|
return mmio_handler_->AddWriteWatch(guest_address, length, callback,
|
||||||
|
callback_context, callback_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Memory::CancelWriteWatch(uintptr_t watch_handle) {
|
||||||
|
mmio_handler_->CancelWriteWatch(watch_handle);
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t Memory::LoadI8(uint64_t address) {
|
uint8_t Memory::LoadI8(uint64_t address) {
|
||||||
uint64_t value;
|
uint64_t value;
|
||||||
if (!mmio_handler_->CheckLoad(address, &value)) {
|
if (!mmio_handler_->CheckLoad(address, &value)) {
|
||||||
|
|
|
@ -53,6 +53,11 @@ class Memory : public alloy::Memory {
|
||||||
void* context, cpu::MMIOReadCallback read_callback,
|
void* context, cpu::MMIOReadCallback read_callback,
|
||||||
cpu::MMIOWriteCallback write_callback);
|
cpu::MMIOWriteCallback write_callback);
|
||||||
|
|
||||||
|
uintptr_t AddWriteWatch(uint32_t guest_address, size_t length,
|
||||||
|
cpu::WriteWatchCallback callback,
|
||||||
|
void* callback_context, void* callback_data);
|
||||||
|
void CancelWriteWatch(uintptr_t watch_handle);
|
||||||
|
|
||||||
uint8_t LoadI8(uint64_t address) override;
|
uint8_t LoadI8(uint64_t address) override;
|
||||||
uint16_t LoadI16(uint64_t address) override;
|
uint16_t LoadI16(uint64_t address) override;
|
||||||
uint32_t LoadI32(uint64_t address) override;
|
uint32_t LoadI32(uint64_t address) override;
|
||||||
|
|
Loading…
Reference in New Issue