Compare commits

...

4 Commits

Author SHA1 Message Date
Radosław Gliński a0f3865951
Merge 656b80a93e into 0474053931 2025-01-17 22:32:18 +00:00
Gliniak 0474053931 [GPU] Improvements to GPU settings modification
- Renamed CommonGPUSetting to GPUSetting
- Removed readback_resolve and memexport from d3d12 exclusive option. In the future it will be available for Vulkan too.
- Removed unused enum class: gpu_cvar
- Removed OS specific code from emulator_window
2025-01-17 20:40:53 +01:00
Gliniak 85695692a7 [Base] Fixed issues with thread priority introduced in previous commit 2025-01-17 19:51:18 +01:00
Gliniak 656b80a93e [Kernel] Rewrite of: ObjectTable
- Added simple Spinlock
2025-01-01 18:46:00 +01:00
12 changed files with 349 additions and 292 deletions

View File

@ -31,7 +31,6 @@
#include "xenia/cpu/processor.h" #include "xenia/cpu/processor.h"
#include "xenia/emulator.h" #include "xenia/emulator.h"
#include "xenia/gpu/command_processor.h" #include "xenia/gpu/command_processor.h"
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
#include "xenia/gpu/graphics_system.h" #include "xenia/gpu/graphics_system.h"
#include "xenia/hid/input_system.h" #include "xenia/hid/input_system.h"
#include "xenia/kernel/xam/profile_manager.h" #include "xenia/kernel/xam/profile_manager.h"
@ -58,7 +57,9 @@ DECLARE_bool(guide_button);
DECLARE_bool(clear_memory_page_state); DECLARE_bool(clear_memory_page_state);
DECLARE_bool(d3d12_readback_resolve); DECLARE_bool(readback_resolve);
DECLARE_bool(readback_memexport);
DEFINE_bool(fullscreen, false, "Whether to launch the emulator in fullscreen.", DEFINE_bool(fullscreen, false, "Whether to launch the emulator in fullscreen.",
"Display"); "Display");
@ -1674,7 +1675,7 @@ EmulatorWindow::ControllerHotKey EmulatorWindow::ProcessControllerHotkey(
} }
} break; } break;
case ButtonFunctions::ClearMemoryPageState: case ButtonFunctions::ClearMemoryPageState:
ToggleGPUSetting(gpu_cvar::ClearMemoryPageState); ToggleGPUSetting(GPUSetting::ClearMemoryPageState);
// Assume the user wants ClearCaches as well // Assume the user wants ClearCaches as well
if (cvars::clear_memory_page_state) { if (cvars::clear_memory_page_state) {
@ -1689,10 +1690,10 @@ EmulatorWindow::ControllerHotKey EmulatorWindow::ProcessControllerHotkey(
xe::threading::Sleep(delay); xe::threading::Sleep(delay);
break; break;
case ButtonFunctions::ReadbackResolve: case ButtonFunctions::ReadbackResolve:
ToggleGPUSetting(gpu_cvar::ReadbackResolve); ToggleGPUSetting(GPUSetting::ReadbackResolve);
notificationTitle = "Toggle Readback Resolve"; notificationTitle = "Toggle Readback Resolve";
notificationDesc = cvars::d3d12_readback_resolve ? "Enabled" : "Disabled"; notificationDesc = cvars::readback_resolve ? "Enabled" : "Disabled";
// Extra Sleep // Extra Sleep
xe::threading::Sleep(delay); xe::threading::Sleep(delay);
@ -1862,15 +1863,17 @@ void EmulatorWindow::GamepadHotKeys() {
} }
} }
void EmulatorWindow::ToggleGPUSetting(gpu_cvar value) { void EmulatorWindow::ToggleGPUSetting(gpu::GPUSetting setting) {
switch (value) { switch (setting) {
case gpu_cvar::ClearMemoryPageState: case GPUSetting::ClearMemoryPageState:
CommonSaveGPUSetting(CommonGPUSetting::ClearMemoryPageState, SaveGPUSetting(GPUSetting::ClearMemoryPageState,
!cvars::clear_memory_page_state); !cvars::clear_memory_page_state);
break; break;
case gpu_cvar::ReadbackResolve: case GPUSetting::ReadbackResolve:
D3D12SaveGPUSetting(D3D12GPUSetting::ReadbackResolve, SaveGPUSetting(GPUSetting::ReadbackResolve, !cvars::readback_resolve);
!cvars::d3d12_readback_resolve); break;
case GPUSetting::ReadbackMemexport:
SaveGPUSetting(GPUSetting::ReadbackMemexport, !cvars::readback_memexport);
break; break;
} }
} }
@ -1915,7 +1918,7 @@ void EmulatorWindow::DisplayHotKeysConfig() {
msg += "\n"; msg += "\n";
msg += "Readback Resolve: " + msg += "Readback Resolve: " +
xe::string_util::BoolToString(cvars::d3d12_readback_resolve); xe::string_util::BoolToString(cvars::readback_resolve);
msg += "\n"; msg += "\n";
msg += "Clear Memory Page State: " + msg += "Clear Memory Page State: " +

View File

@ -112,11 +112,6 @@ class EmulatorWindow {
Unknown Unknown
}; };
enum class gpu_cvar {
ClearMemoryPageState,
ReadbackResolve,
};
class ControllerHotKey { class ControllerHotKey {
public: public:
// If true the hotkey can be activated while a title is running, otherwise // If true the hotkey can be activated while a title is running, otherwise
@ -235,7 +230,7 @@ class EmulatorWindow {
void VibrateController(xe::hid::InputSystem* input_sys, uint32_t user_index, void VibrateController(xe::hid::InputSystem* input_sys, uint32_t user_index,
bool vibrate = true); bool vibrate = true);
void GamepadHotKeys(); void GamepadHotKeys();
void ToggleGPUSetting(gpu_cvar index); void ToggleGPUSetting(gpu::GPUSetting setting);
void DisplayHotKeysConfig(); void DisplayHotKeysConfig();
static std::string CanonicalizeFileExtension( static std::string CanonicalizeFileExtension(

30
src/xenia/base/spinlock.h Normal file
View File

@ -0,0 +1,30 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BASE_SPINLOCK_H_
#define XENIA_BASE_SPINLOCK_H_
#include <atomic>
namespace xe {
class spinlock {
public:
void lock() {
while (locked.test_and_set(std::memory_order_acquire)) {
;
}
}
void unlock() { locked.clear(std::memory_order_release); }
private:
std::atomic_flag locked = ATOMIC_FLAG_INIT;
};
} // namespace xe
#endif

View File

@ -402,7 +402,7 @@ class Timer : public WaitHandle {
virtual bool Cancel() = 0; virtual bool Cancel() = 0;
}; };
#if XE_PLATFORM_WINDOWS #if XE_PLATFORM_WIN32
struct ThreadPriority { struct ThreadPriority {
static const int32_t kLowest = -2; static const int32_t kLowest = -2;
static const int32_t kBelowNormal = -1; static const int32_t kBelowNormal = -1;

View File

@ -50,17 +50,52 @@ DEFINE_bool(clear_memory_page_state, false,
"for 'Team Ninja' Games to fix missing character models)", "for 'Team Ninja' Games to fix missing character models)",
"GPU"); "GPU");
DEFINE_bool(
readback_resolve, false,
"[D3D12 Only] Read render-to-texture results on the CPU. This may be "
"needed in some games, for instance, for screenshots in saved games, but "
"causes mid-frame synchronization, so it has a huge performance impact.",
"GPU");
DEFINE_bool(
readback_memexport, false,
"[D3D12 Only] Read data written by memory export in shaders on the CPU. "
"This may be needed in some games (but many only access exported data on "
"the GPU, and this flag isn't needed to handle such behavior), but causes "
"mid-frame synchronization, so it has a huge performance impact.",
"GPU");
namespace xe { namespace xe {
namespace gpu { namespace gpu {
void CommonSaveGPUSetting(CommonGPUSetting setting, uint64_t value) { // This should be written completely differently with support for different
// types.
void SaveGPUSetting(GPUSetting setting, uint64_t value) {
switch (setting) { switch (setting) {
case CommonGPUSetting::ClearMemoryPageState: case GPUSetting::ClearMemoryPageState:
OVERRIDE_bool(clear_memory_page_state, (bool)value); OVERRIDE_bool(clear_memory_page_state, static_cast<bool>(value));
break;
case GPUSetting::ReadbackResolve:
OVERRIDE_bool(readback_resolve, static_cast<bool>(value));
break;
case GPUSetting::ReadbackMemexport:
OVERRIDE_bool(readback_memexport, static_cast<bool>(value));
break; break;
} }
} }
bool GetGPUSetting(GPUSetting setting) {
switch (setting) {
case GPUSetting::ClearMemoryPageState:
return cvars::clear_memory_page_state;
case GPUSetting::ReadbackResolve:
return cvars::readback_resolve;
case GPUSetting::ReadbackMemexport:
return cvars::readback_memexport;
}
return false;
}
using namespace xe::gpu::xenos; using namespace xe::gpu::xenos;
CommandProcessor::CommandProcessor(GraphicsSystem* graphics_system, CommandProcessor::CommandProcessor(GraphicsSystem* graphics_system,

View File

@ -33,11 +33,14 @@ class ByteStream;
namespace gpu { namespace gpu {
enum class CommonGPUSetting { enum class GPUSetting {
ClearMemoryPageState, ClearMemoryPageState,
ReadbackResolve,
ReadbackMemexport
}; };
void CommonSaveGPUSetting(CommonGPUSetting setting, uint64_t value); void SaveGPUSetting(GPUSetting setting, uint64_t value);
bool GetGPUSetting(GPUSetting setting);
class GraphicsSystem; class GraphicsSystem;
class Shader; class Shader;

View File

@ -32,19 +32,7 @@ DEFINE_bool(d3d12_bindless, true,
"Use bindless resources where available - may improve performance, " "Use bindless resources where available - may improve performance, "
"but may make debugging more complicated.", "but may make debugging more complicated.",
"D3D12"); "D3D12");
DEFINE_bool(d3d12_readback_memexport, false,
"Read data written by memory export in shaders on the CPU. This "
"may be needed in some games (but many only access exported data "
"on the GPU, and this flag isn't needed to handle such behavior), "
"but causes mid-frame synchronization, so it has a huge "
"performance impact.",
"D3D12");
DEFINE_bool(d3d12_readback_resolve, false,
"Read render-to-texture results on the CPU. This may be needed in "
"some games, for instance, for screenshots in saved games, but "
"causes mid-frame synchronization, so it has a huge performance "
"impact.",
"D3D12");
DEFINE_bool(d3d12_submit_on_primary_buffer_end, true, DEFINE_bool(d3d12_submit_on_primary_buffer_end, true,
"Submit the command list when a PM4 primary buffer ends if it's " "Submit the command list when a PM4 primary buffer ends if it's "
"possible to submit immediately to try to reduce frame latency.", "possible to submit immediately to try to reduce frame latency.",
@ -54,15 +42,6 @@ DECLARE_bool(clear_memory_page_state);
namespace xe { namespace xe {
namespace gpu { namespace gpu {
void D3D12SaveGPUSetting(D3D12GPUSetting setting, uint64_t value) {
switch (setting) {
case D3D12GPUSetting::ReadbackResolve:
OVERRIDE_bool(d3d12_readback_resolve, (bool)value);
break;
}
}
namespace d3d12 { namespace d3d12 {
// Generated with `xb buildshaders`. // Generated with `xb buildshaders`.
@ -3011,7 +2990,7 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type,
memexport_range.base_address_dwords << 2, memexport_range.size_bytes, memexport_range.base_address_dwords << 2, memexport_range.size_bytes,
false); false);
} }
if (cvars::d3d12_readback_memexport) { if (GetGPUSetting(GPUSetting::ReadbackResolve)) {
// Read the exported data on the CPU. // Read the exported data on the CPU.
uint32_t memexport_total_size = 0; uint32_t memexport_total_size = 0;
for (const draw_util::MemExportRange& memexport_range : for (const draw_util::MemExportRange& memexport_range :
@ -3091,7 +3070,7 @@ bool D3D12CommandProcessor::IssueCopy() {
if (!BeginSubmission(true)) { if (!BeginSubmission(true)) {
return false; return false;
} }
if (!cvars::d3d12_readback_resolve) { if (!GetGPUSetting(GPUSetting::ReadbackResolve)) {
uint32_t written_address, written_length; uint32_t written_address, written_length;
return render_target_cache_->Resolve(*memory_, *shared_memory_, return render_target_cache_->Resolve(*memory_, *shared_memory_,
*texture_cache_, written_address, *texture_cache_, written_address,

View File

@ -710,7 +710,7 @@ void KernelState::UnloadUserModule(const object_ref<UserModule>& module,
return e->path() == module->path(); return e->path() == module->path();
}) == user_modules_.end()); }) == user_modules_.end());
object_table()->ReleaseHandleInLock(module->handle()); object_table()->ReleaseHandle(module->handle());
} }
void KernelState::TerminateTitle() { void KernelState::TerminateTitle() {

View File

@ -26,132 +26,104 @@ ObjectTable::ObjectTable() {}
ObjectTable::~ObjectTable() { Reset(); } ObjectTable::~ObjectTable() { Reset(); }
void ObjectTable::Reset() { void ObjectTable::Reset() {
auto global_lock = global_critical_region_.Acquire(); std::lock_guard<xe::spinlock> lock(spinlock_);
// Release all objects. host_object_table_.Reset();
for (uint32_t n = 0; n < table_capacity_; n++) { for (auto& [_, table] : guest_object_table_) {
ObjectTableEntry& entry = table_[n]; table.Reset();
if (entry.object) {
entry.object->Release();
}
} }
for (uint32_t n = 0; n < host_table_capacity_; n++) {
ObjectTableEntry& entry = host_table_[n];
if (entry.object) {
entry.object->Release();
}
}
table_capacity_ = 0;
host_table_capacity_ = 0;
last_free_entry_ = 0;
last_free_host_entry_ = 0;
free(table_);
table_ = nullptr;
free(host_table_);
host_table_ = nullptr;
} }
X_STATUS ObjectTable::FindFreeSlot(uint32_t* out_slot, bool host) { uint32_t ObjectTable::GetFirstFreeSlot(
// Find a free slot. ObjectTable::ObjectTableInfo* const table) {
uint32_t slot = host ? last_free_host_entry_ : last_free_entry_; // Check if
uint32_t capacity = host ? host_table_capacity_ : table_capacity_; if (!table->freed_table_slots_.empty()) {
uint32_t scan_count = 0; uint32_t slot = table->freed_table_slots_.front();
while (scan_count < capacity) { table->freed_table_slots_.erase(table->freed_table_slots_.begin());
ObjectTableEntry& entry = host ? host_table_[slot] : table_[slot]; return slot;
if (!entry.object) { }
*out_slot = slot; // Check if latest used slot is free again.
return X_STATUS_SUCCESS; ObjectTableEntry& entry = table->table_[table->previous_free_slot_];
} if (!entry.object) {
scan_count++; return table->previous_free_slot_;
slot = (slot + 1) % capacity;
if (slot == 0 && host) {
// Never allow 0 handles.
scan_count++;
slot++;
}
} }
// Table out of slots, expand. if (++table->previous_free_slot_ >= table->table_.size()) {
uint32_t new_table_capacity = std::max(16 * 1024u, capacity * 2); table->table_.reserve(table->table_.size() * 2);
if (!Resize(new_table_capacity, host)) {
return X_STATUS_NO_MEMORY;
} }
// Never allow 0 handles on host. return table->previous_free_slot_;
slot = host ? ++last_free_host_entry_ : last_free_entry_++; }
*out_slot = slot;
X_STATUS ObjectTable::FindFreeSlot(const XObject* const object,
uint32_t* out_slot) {
auto object_table = GetTableForObject(object);
*out_slot = GetFirstFreeSlot(object_table);
return X_STATUS_SUCCESS; return X_STATUS_SUCCESS;
} }
bool ObjectTable::Resize(uint32_t new_capacity, bool host) { ObjectTable::ObjectTableInfo* const ObjectTable::GetTableForObject(
uint32_t capacity = host ? host_table_capacity_ : table_capacity_; const XObject* const obj) {
uint32_t new_size = new_capacity * sizeof(ObjectTableEntry); if (obj->is_host_object()) {
uint32_t old_size = capacity * sizeof(ObjectTableEntry); return &host_object_table_;
auto new_table = reinterpret_cast<ObjectTableEntry*>(
realloc(host ? host_table_ : table_, new_size));
if (!new_table) {
return false;
} }
// Zero out new entries. if (obj->type() == XObject::Type::Thread) {
if (new_size > old_size) { // Switcharoo for title/system thread!
std::memset(reinterpret_cast<uint8_t*>(new_table) + old_size, 0, return &guest_object_table_[kGuestHandleTitleThreadBase];
new_size - old_size);
} }
if (host) { return &guest_object_table_[kGuestHandleBase];
last_free_host_entry_ = capacity; }
host_table_capacity_ = new_capacity;
host_table_ = new_table; ObjectTable::ObjectTableInfo* const ObjectTable::GetTableForObject(
} else { const X_HANDLE handle) {
last_free_entry_ = capacity; if (!handle) {
table_capacity_ = new_capacity; return nullptr;
table_ = new_table;
} }
return true; if (handle & 0x00FF0000) {
return &guest_object_table_[kGuestHandleBase];
}
const uint32_t handle_mask = handle & 0xFF000000;
if (handle_mask == kGuestHandleTitleThreadBase) {
return &guest_object_table_[kGuestHandleTitleThreadBase];
}
if (handle_mask == kGuestHandleSystemThreadBase) {
return &guest_object_table_[kGuestHandleSystemThreadBase];
}
// Only host check remains
return &host_object_table_;
} }
X_STATUS ObjectTable::AddHandle(XObject* object, X_HANDLE* out_handle) { X_STATUS ObjectTable::AddHandle(XObject* object, X_HANDLE* out_handle) {
X_STATUS result = X_STATUS_SUCCESS; X_STATUS result = X_STATUS_SUCCESS;
uint32_t handle = 0; uint32_t handle = 0;
{
auto global_lock = global_critical_region_.Acquire();
// Find a free slot. std::lock_guard<xe::spinlock> lock(spinlock_);
uint32_t slot = 0;
bool host_object = object->is_host_object();
result = FindFreeSlot(&slot, host_object);
// Stash. auto table = GetTableForObject(object);
if (XSUCCEEDED(result)) { uint32_t slot = GetFirstFreeSlot(table);
ObjectTableEntry& entry = host_object ? host_table_[slot] : table_[slot];
entry.object = object;
entry.handle_ref_count = 1;
handle = slot << 2;
if (!host_object) {
if (object->type() != XObject::Type::Socket) {
handle += XObject::kHandleBase;
}
} else {
handle += XObject::kHandleHostBase;
}
object->handles().push_back(handle);
// Retain so long as the object is in the table. ObjectTableEntry& entry = table->table_[slot];
object->Retain();
XELOGI("Added handle:{:08X} for {}", handle, typeid(*object).name()); entry.object = object;
} entry.handle_ref_count = 1;
} handle = table->GetSlotHandle(slot);
object->handles().push_back(handle);
if (XSUCCEEDED(result)) { // Retain so long as the object is in the table.
if (out_handle) { object->Retain();
*out_handle = handle;
} XELOGI("Added handle:{:08X} for {}", handle, typeid(*object).name());
if (out_handle) {
*out_handle = handle;
} }
return result; return result;
@ -161,21 +133,40 @@ X_STATUS ObjectTable::DuplicateHandle(X_HANDLE handle, X_HANDLE* out_handle) {
X_STATUS result = X_STATUS_SUCCESS; X_STATUS result = X_STATUS_SUCCESS;
handle = TranslateHandle(handle); handle = TranslateHandle(handle);
std::lock_guard<xe::spinlock> lock(spinlock_);
// For whatever reason all duplicates are going into base mask even threads.
auto table = &guest_object_table_[kGuestHandleBase];
uint32_t slot = GetFirstFreeSlot(table);
XObject* object = LookupObject(handle, false); XObject* object = LookupObject(handle, false);
if (object) { if (object) {
result = AddHandle(object, out_handle); ObjectTableEntry& entry = table->table_[slot];
object->Release(); // Release the ref that LookupObject took entry.object = object;
entry.handle_ref_count = 1;
*out_handle = table->GetSlotHandle(slot);
object->handles().push_back(*out_handle);
// Retain so long as the object is in the table.
object->Retain();
XELOGI("Duplicated handle:{:08X} to {:08X} for {}", handle, *out_handle,
typeid(*object).name());
} else { } else {
result = X_STATUS_INVALID_HANDLE; result = X_STATUS_INVALID_HANDLE;
} }
return result; return result;
} }
X_STATUS ObjectTable::RetainHandle(X_HANDLE handle) { X_STATUS ObjectTable::RetainHandle(X_HANDLE handle) {
auto global_lock = global_critical_region_.Acquire(); handle = TranslateHandle(handle);
if (!handle) {
return X_STATUS_INVALID_HANDLE;
}
ObjectTableEntry* entry = LookupTableInLock(handle); ObjectTableEntry* entry = LookupTable(handle);
std::lock_guard<xe::spinlock> lock(spinlock_);
if (!entry) { if (!entry) {
return X_STATUS_INVALID_HANDLE; return X_STATUS_INVALID_HANDLE;
} }
@ -185,17 +176,19 @@ X_STATUS ObjectTable::RetainHandle(X_HANDLE handle) {
} }
X_STATUS ObjectTable::ReleaseHandle(X_HANDLE handle) { X_STATUS ObjectTable::ReleaseHandle(X_HANDLE handle) {
auto global_lock = global_critical_region_.Acquire(); handle = TranslateHandle(handle);
if (!handle) {
return X_STATUS_INVALID_HANDLE;
}
return ReleaseHandleInLock(handle); std::lock_guard<xe::spinlock> lock(spinlock_);
} ObjectTableEntry* entry = LookupTable(handle);
X_STATUS ObjectTable::ReleaseHandleInLock(X_HANDLE handle) {
ObjectTableEntry* entry = LookupTableInLock(handle);
if (!entry) { if (!entry) {
return X_STATUS_INVALID_HANDLE; return X_STATUS_INVALID_HANDLE;
} }
if (--entry->handle_ref_count == 0) { if (--entry->handle_ref_count == 0) {
lock.~lock_guard();
// No more references. Remove it from the table. // No more references. Remove it from the table.
return RemoveHandle(handle); return RemoveHandle(handle);
} }
@ -204,6 +197,7 @@ X_STATUS ObjectTable::ReleaseHandleInLock(X_HANDLE handle) {
// (but not a failure code) // (but not a failure code)
return X_STATUS_SUCCESS; return X_STATUS_SUCCESS;
} }
X_STATUS ObjectTable::RemoveHandle(X_HANDLE handle) { X_STATUS ObjectTable::RemoveHandle(X_HANDLE handle) {
X_STATUS result = X_STATUS_SUCCESS; X_STATUS result = X_STATUS_SUCCESS;
@ -211,9 +205,10 @@ X_STATUS ObjectTable::RemoveHandle(X_HANDLE handle) {
if (!handle) { if (!handle) {
return X_STATUS_INVALID_HANDLE; return X_STATUS_INVALID_HANDLE;
} }
auto global_lock = global_critical_region_.Acquire();
ObjectTableEntry* entry = LookupTableInLock(handle); std::lock_guard<xe::spinlock> lock(spinlock_);
ObjectTableEntry* entry = LookupTable(handle);
if (!entry) { if (!entry) {
return X_STATUS_INVALID_HANDLE; return X_STATUS_INVALID_HANDLE;
} }
@ -231,6 +226,10 @@ X_STATUS ObjectTable::RemoveHandle(X_HANDLE handle) {
object->handles().erase(handle_entry); object->handles().erase(handle_entry);
} }
auto table = GetTableForObject(handle);
const uint32_t slot = table->GetHandleSlot(handle);
table->freed_table_slots_.push_back(slot);
XELOGI("Removed handle:{:08X} for {}", handle, typeid(*object).name()); XELOGI("Removed handle:{:08X} for {}", handle, typeid(*object).name());
// Remove object name from mapping to prevent naming collision. // Remove object name from mapping to prevent naming collision.
@ -240,69 +239,21 @@ X_STATUS ObjectTable::RemoveHandle(X_HANDLE handle) {
// Release now that the object has been removed from the table. // Release now that the object has been removed from the table.
object->Release(); object->Release();
} }
return X_STATUS_SUCCESS; return X_STATUS_SUCCESS;
} }
std::vector<object_ref<XObject>> ObjectTable::GetAllObjects() {
auto lock = global_critical_region_.Acquire();
std::vector<object_ref<XObject>> results;
for (uint32_t slot = 0; slot < host_table_capacity_; slot++) {
auto& entry = host_table_[slot];
if (entry.object && std::find(results.begin(), results.end(),
entry.object) == results.end()) {
entry.object->Retain();
results.push_back(object_ref<XObject>(entry.object));
}
}
for (uint32_t slot = 0; slot < table_capacity_; slot++) {
auto& entry = table_[slot];
if (entry.object && std::find(results.begin(), results.end(),
entry.object) == results.end()) {
entry.object->Retain();
results.push_back(object_ref<XObject>(entry.object));
}
}
return results;
}
void ObjectTable::PurgeAllObjects() {
auto lock = global_critical_region_.Acquire();
for (uint32_t slot = 0; slot < table_capacity_; slot++) {
auto& entry = table_[slot];
if (entry.object) {
entry.handle_ref_count = 0;
entry.object->Release();
entry.object = nullptr;
}
}
}
ObjectTable::ObjectTableEntry* ObjectTable::LookupTable(X_HANDLE handle) { ObjectTable::ObjectTableEntry* ObjectTable::LookupTable(X_HANDLE handle) {
auto global_lock = global_critical_region_.Acquire(); auto table = GetTableForObject(handle);
return LookupTableInLock(handle); if (!table) {
}
ObjectTable::ObjectTableEntry* ObjectTable::LookupTableInLock(X_HANDLE handle) {
handle = TranslateHandle(handle);
if (!handle) {
return nullptr; return nullptr;
} }
const bool is_host_object = XObject::is_handle_host_object(handle); auto* entry = &table->table_[table->GetHandleSlot(handle)];
uint32_t slot = GetHandleSlot(handle, is_host_object);
if (is_host_object) {
if (slot <= host_table_capacity_) {
return &host_table_[slot];
}
} else if (slot <= table_capacity_) {
return &table_[slot];
}
return nullptr; if (!entry->object) {
return nullptr;
}
return entry;
} }
// Generic lookup // Generic lookup
@ -321,64 +272,78 @@ XObject* ObjectTable::LookupObject(X_HANDLE handle, bool already_locked) {
} }
XObject* object = nullptr; XObject* object = nullptr;
if (!already_locked) { auto entry = LookupTable(handle);
global_critical_region_.mutex().lock(); if (!entry) {
} return nullptr;
const bool is_host_object = XObject::is_handle_host_object(handle);
uint32_t slot = GetHandleSlot(handle, is_host_object);
// Verify slot.
if (is_host_object) {
if (slot < host_table_capacity_) {
ObjectTableEntry& entry = host_table_[slot];
if (entry.object) {
object = entry.object;
}
}
} else if (slot < table_capacity_) {
ObjectTableEntry& entry = table_[slot];
if (entry.object) {
object = entry.object;
}
} }
// Retain the object pointer. // Retain the object pointer.
if (object) { if (entry->object) {
object->Retain(); entry->object->Retain();
} }
if (!already_locked) { return entry->object;
global_critical_region_.mutex().unlock(); }
}
return object; std::vector<object_ref<XObject>> ObjectTable::GetAllObjects() {
std::vector<object_ref<XObject>> results;
std::lock_guard<xe::spinlock> lock(spinlock_);
for (auto& [_, table] : guest_object_table_) {
for (uint32_t i = 0; i < table.previous_free_slot_ + 1; i++) {
auto& object = table.table_.at(i).object;
if (!object) {
continue;
}
object->Retain();
results.push_back(object_ref<XObject>(object));
}
}
return results;
}
void ObjectTable::PurgeAllObjects() {
std::lock_guard<xe::spinlock> lock(spinlock_);
for (auto& [_, table] : guest_object_table_) {
for (auto& [_, entry] : table.table_) {
if (!entry.object) {
continue;
}
entry.handle_ref_count = 0;
entry.object->Release();
entry.object = nullptr;
}
}
} }
void ObjectTable::GetObjectsByType(XObject::Type type, void ObjectTable::GetObjectsByType(XObject::Type type,
std::vector<object_ref<XObject>>* results) { std::vector<object_ref<XObject>>* results) {
auto global_lock = global_critical_region_.Acquire(); std::lock_guard<xe::spinlock> lock(spinlock_);
for (uint32_t slot = 0; slot < host_table_capacity_; ++slot) {
auto& entry = host_table_[slot]; if (type == XObject::Type::Thread) {
if (entry.object) { for (auto& [_, entry] :
if (entry.object->type() == type) { guest_object_table_[kGuestHandleTitleThreadBase].table_) {
if (entry.object) {
entry.object->Retain(); entry.object->Retain();
results->push_back(object_ref<XObject>(entry.object)); results->push_back(object_ref<XObject>(entry.object));
} }
} }
return;
} }
for (uint32_t slot = 0; slot < table_capacity_; ++slot) {
auto& entry = table_[slot]; for (auto& [_, entry] : guest_object_table_[kGuestHandleBase].table_) {
if (entry.object) { if (entry.object && entry.object->type() == type) {
if (entry.object->type() == type) { entry.object->Retain();
entry.object->Retain(); results->push_back(object_ref<XObject>(entry.object));
results->push_back(object_ref<XObject>(entry.object));
}
} }
} }
} }
X_HANDLE ObjectTable::TranslateHandle(X_HANDLE handle) { X_HANDLE ObjectTable::TranslateHandle(X_HANDLE handle) const {
// chrispy: reordered these by likelihood, most likely case is that handle is // chrispy: reordered these by likelihood, most likely case is that handle is
// not a special handle // not a special handle
XE_LIKELY_IF(handle < 0xFFFFFFFE) { return handle; } XE_LIKELY_IF(handle < 0xFFFFFFFE) { return handle; }
@ -390,31 +355,31 @@ X_HANDLE ObjectTable::TranslateHandle(X_HANDLE handle) {
} }
} }
// Name mapping is available only for guest objects!
X_STATUS ObjectTable::AddNameMapping(const std::string_view name, X_STATUS ObjectTable::AddNameMapping(const std::string_view name,
X_HANDLE handle) { X_HANDLE handle) {
auto global_lock = global_critical_region_.Acquire(); std::lock_guard<xe::spinlock> lock(spinlock_);
if (name_table_.count(string_key_case(name))) { if (guest_name_table_.count(string_key_case(name))) {
return X_STATUS_OBJECT_NAME_COLLISION; return X_STATUS_OBJECT_NAME_COLLISION;
} }
name_table_.insert({string_key_case::create(name), handle}); guest_name_table_.insert({string_key_case::create(name), handle});
return X_STATUS_SUCCESS; return X_STATUS_SUCCESS;
} }
void ObjectTable::RemoveNameMapping(const std::string_view name) { void ObjectTable::RemoveNameMapping(const std::string_view name) {
// Names are case-insensitive. // Names are case-insensitive.
auto global_lock = global_critical_region_.Acquire(); auto it = guest_name_table_.find(string_key_case(name));
auto it = name_table_.find(string_key_case(name)); if (it != guest_name_table_.end()) {
if (it != name_table_.end()) { guest_name_table_.erase(it);
name_table_.erase(it);
} }
} }
X_STATUS ObjectTable::GetObjectByName(const std::string_view name, X_STATUS ObjectTable::GetObjectByName(const std::string_view name,
X_HANDLE* out_handle) { X_HANDLE* out_handle) {
// Names are case-insensitive. // Names are case-insensitive.
auto global_lock = global_critical_region_.Acquire(); std::lock_guard<xe::spinlock> lock(spinlock_);
auto it = name_table_.find(string_key_case(name)); auto it = guest_name_table_.find(string_key_case(name));
if (it == name_table_.end()) { if (it == guest_name_table_.end()) {
*out_handle = X_INVALID_HANDLE_VALUE; *out_handle = X_INVALID_HANDLE_VALUE;
return X_STATUS_OBJECT_NAME_NOT_FOUND; return X_STATUS_OBJECT_NAME_NOT_FOUND;
} }
@ -431,6 +396,7 @@ X_STATUS ObjectTable::GetObjectByName(const std::string_view name,
} }
bool ObjectTable::Save(ByteStream* stream) { bool ObjectTable::Save(ByteStream* stream) {
/*
stream->Write<uint32_t>(host_table_capacity_); stream->Write<uint32_t>(host_table_capacity_);
for (uint32_t i = 0; i < host_table_capacity_; i++) { for (uint32_t i = 0; i < host_table_capacity_; i++) {
auto& entry = host_table_[i]; auto& entry = host_table_[i];
@ -442,11 +408,12 @@ bool ObjectTable::Save(ByteStream* stream) {
auto& entry = table_[i]; auto& entry = table_[i];
stream->Write<int32_t>(entry.handle_ref_count); stream->Write<int32_t>(entry.handle_ref_count);
} }
*/
return true; return true;
} }
bool ObjectTable::Restore(ByteStream* stream) { bool ObjectTable::Restore(ByteStream* stream) {
/*
Resize(stream->Read<uint32_t>(), true); Resize(stream->Read<uint32_t>(), true);
for (uint32_t i = 0; i < host_table_capacity_; i++) { for (uint32_t i = 0; i < host_table_capacity_; i++) {
auto& entry = host_table_[i]; auto& entry = host_table_[i];
@ -460,11 +427,12 @@ bool ObjectTable::Restore(ByteStream* stream) {
// entry.object = nullptr; // entry.object = nullptr;
entry.handle_ref_count = stream->Read<int32_t>(); entry.handle_ref_count = stream->Read<int32_t>();
} }
*/
return true; return true;
} }
X_STATUS ObjectTable::RestoreHandle(X_HANDLE handle, XObject* object) { X_STATUS ObjectTable::RestoreHandle(X_HANDLE handle, XObject* object) {
/*
const bool is_host_object = XObject::is_handle_host_object(handle); const bool is_host_object = XObject::is_handle_host_object(handle);
uint32_t slot = GetHandleSlot(handle, is_host_object); uint32_t slot = GetHandleSlot(handle, is_host_object);
uint32_t capacity = is_host_object ? host_table_capacity_ : table_capacity_; uint32_t capacity = is_host_object ? host_table_capacity_ : table_capacity_;
@ -475,7 +443,7 @@ X_STATUS ObjectTable::RestoreHandle(X_HANDLE handle, XObject* object) {
entry.object = object; entry.object = object;
object->Retain(); object->Retain();
} }
*/
return X_STATUS_SUCCESS; return X_STATUS_SUCCESS;
} }

View File

@ -15,6 +15,7 @@
#include <vector> #include <vector>
#include "xenia/base/mutex.h" #include "xenia/base/mutex.h"
#include "xenia/base/spinlock.h"
#include "xenia/base/string_key.h" #include "xenia/base/string_key.h"
#include "xenia/kernel/xobject.h" #include "xenia/kernel/xobject.h"
#include "xenia/xbox.h" #include "xenia/xbox.h"
@ -38,7 +39,6 @@ class ObjectTable {
X_STATUS DuplicateHandle(X_HANDLE orig, X_HANDLE* out_handle); X_STATUS DuplicateHandle(X_HANDLE orig, X_HANDLE* out_handle);
X_STATUS RetainHandle(X_HANDLE handle); X_STATUS RetainHandle(X_HANDLE handle);
X_STATUS ReleaseHandle(X_HANDLE handle); X_STATUS ReleaseHandle(X_HANDLE handle);
X_STATUS ReleaseHandleInLock(X_HANDLE handle);
X_STATUS RemoveHandle(X_HANDLE handle); X_STATUS RemoveHandle(X_HANDLE handle);
bool Save(ByteStream* stream); bool Save(ByteStream* stream);
@ -59,6 +59,7 @@ class ObjectTable {
X_STATUS AddNameMapping(const std::string_view name, X_HANDLE handle); X_STATUS AddNameMapping(const std::string_view name, X_HANDLE handle);
void RemoveNameMapping(const std::string_view name); void RemoveNameMapping(const std::string_view name);
X_STATUS GetObjectByName(const std::string_view name, X_HANDLE* out_handle); X_STATUS GetObjectByName(const std::string_view name, X_HANDLE* out_handle);
template <typename T> template <typename T>
std::vector<object_ref<T>> GetObjectsByType(XObject::Type type) { std::vector<object_ref<T>> GetObjectsByType(XObject::Type type) {
@ -85,28 +86,77 @@ class ObjectTable {
int handle_ref_count = 0; int handle_ref_count = 0;
XObject* object = nullptr; XObject* object = nullptr;
}; };
ObjectTableEntry* LookupTableInLock(X_HANDLE handle);
struct ObjectTableInfo {
uint32_t table_base_handle_offset_ = 0;
uint32_t previous_free_slot_ = 0;
std::unordered_map<uint32_t, ObjectTableEntry> table_ = {};
std::vector<uint32_t> freed_table_slots_ = {};
// Ctor for host objects
ObjectTableInfo() {
previous_free_slot_ = 1;
freed_table_slots_.reserve(255);
table_.reserve(4095);
};
// Ctor for guest objects
ObjectTableInfo(uint32_t base_handle_offset) {
table_base_handle_offset_ = base_handle_offset;
freed_table_slots_.reserve(255);
table_.reserve(4095);
};
X_HANDLE GetSlotHandle(uint32_t slot) {
return (slot << 2) + table_base_handle_offset_;
}
uint32_t GetHandleSlot(X_HANDLE handle) {
return (handle - table_base_handle_offset_) >> 2;
}
void Reset() {
for (auto& [_, entry] : table_) {
if (entry.object) {
entry.object->Release();
}
}
previous_free_slot_ = 1;
}
};
ObjectTableEntry* LookupTable(X_HANDLE handle); ObjectTableEntry* LookupTable(X_HANDLE handle);
XObject* LookupObject(X_HANDLE handle, bool already_locked); XObject* LookupObject(X_HANDLE handle, bool already_locked);
void GetObjectsByType(XObject::Type type, void GetObjectsByType(XObject::Type type,
std::vector<object_ref<XObject>>* results); std::vector<object_ref<XObject>>* results);
X_HANDLE TranslateHandle(X_HANDLE handle); X_HANDLE TranslateHandle(X_HANDLE handle) const;
static constexpr uint32_t GetHandleSlot(X_HANDLE handle, bool host) {
handle &= host ? ~XObject::kHandleHostBase : ~XObject::kHandleBase;
return handle >> 2;
}
X_STATUS FindFreeSlot(uint32_t* out_slot, bool host);
bool Resize(uint32_t new_capacity, bool host);
xe::global_critical_region global_critical_region_; X_STATUS FindFreeSlot(const XObject* const object, uint32_t* out_slot);
uint32_t table_capacity_ = 0;
uint32_t host_table_capacity_ = 0; ObjectTableInfo* const GetTableForObject(const XObject* const obj);
ObjectTableEntry* table_ = nullptr; ObjectTableInfo* const GetTableForObject(const X_HANDLE handle);
ObjectTableEntry* host_table_ = nullptr; uint32_t GetFirstFreeSlot(ObjectTableInfo* const table);
uint32_t last_free_entry_ = 0;
uint32_t last_free_host_entry_ = 0; xe::spinlock spinlock_;
std::unordered_map<string_key_case, X_HANDLE> name_table_;
ObjectTableInfo host_object_table_;
static constexpr uint32_t kGuestHandleBase = 0x00100000;
static constexpr uint32_t kGuestHandleTitleThreadBase = 0xF8000000;
static constexpr uint32_t kGuestHandleSystemThreadBase = 0xFB000000;
std::map<const uint32_t, ObjectTableInfo> guest_object_table_ = {
{kGuestHandleBase, ObjectTableInfo(kGuestHandleBase)},
{kGuestHandleTitleThreadBase,
ObjectTableInfo(kGuestHandleTitleThreadBase)},
{kGuestHandleSystemThreadBase,
ObjectTableInfo(kGuestHandleSystemThreadBase)}};
std::unordered_map<string_key_case, X_HANDLE> guest_name_table_;
}; };
// Generic lookup // Generic lookup

View File

@ -95,12 +95,9 @@ X_STATUS XObject::Delete() {
if (kernel_state_ == nullptr) { if (kernel_state_ == nullptr) {
// Fake return value for api-scanner // Fake return value for api-scanner
return X_STATUS_SUCCESS; return X_STATUS_SUCCESS;
} else {
if (!name_.empty()) {
kernel_state_->object_table()->RemoveNameMapping(name_);
}
return kernel_state_->object_table()->RemoveHandle(handles_[0]);
} }
return kernel_state_->object_table()->RemoveHandle(handles_[0]);
} }
bool XObject::SaveObject(ByteStream* stream) { bool XObject::SaveObject(ByteStream* stream) {

View File

@ -199,9 +199,6 @@ class XObject {
static object_ref<XObject> Restore(KernelState* kernel_state, Type type, static object_ref<XObject> Restore(KernelState* kernel_state, Type type,
ByteStream* stream); ByteStream* stream);
static constexpr bool is_handle_host_object(X_HANDLE handle) {
return handle > XObject::kHandleHostBase && handle < XObject::kHandleBase;
};
// Reference() // Reference()
// Dereference() // Dereference()