fixed wine crash from use of NtSetEventPriorityBoost
add xe::clear_lowest_bit, use it in place of shift-andnot in some bit iteration code make is_allocated_ and is_enabled_ volatile in xma_context preallocate avpacket buffer in XMAContext::Setup, the reallocations of the buffer in ffmpeg were showing up on profiles check is_enabled and is_allocated BEFORE locking an xmacontext. XMA worker was spending most of its time locking and unlocking contexts Removed XeDMAC, dma:: namespace. It was a bad idea and I couldn't make it work in the end. Kept vastcpy and moved it to the memory namespace instead Made the rest of global_critical_region's members static. They never needed an instance. Removed ifdef'ed out code from ring_buffer.h Added EventInfo struct to threading, added Event::Query to aid with implementing NtQueryEvent. Removed vector from WaitMultiple, instead use a fixed array of 64 handles that we populate. WaitForMultipleObjects cannot handle more than 64 objects. Remove XE_MSVC_OPTIMIZE_SMALL() use in x64_sequences, x64 backend is now always size optimized because of premake Make global_critical_region_ static constexpr in shared_memory.h to get rid of wasteage of 8 bytes (empty class=1byte, +alignment for next member=8) Move trace-related data to the tail of SharedMemory to keep more important data together In IssueDraw build an array of fetch constant addresses/sizes, then pre-lock the global lock before doing requestrange for each instead of individually locking within requestrange for each of them Consistent access specifier protected for pm4_command_processor_declare Devirtualize WriteOneRegisterFromRing. Move ExecutePacket and ExecutePrimaryBuffer to pm4_command_buffer_x Remove many redundant header inclusions access xenia-gpu Minor microoptimization of ExecutePacketType0 Add TextureCache::RequestTextures for batch invocation of LoadTexturesData Add TextureCache::LoadTexturesData for reducing the number of times we release and reacquire the global lock. Ideally you should hold the global lock for as little time as possible, but if you are constantly acquiring and releasing it you are actually more likely to have contention Add already_locked param to ObjectTable::LookupObject to help with reducing lock acquire/release pairs Add missing checks to XAudioRegisterRenderDriverClient_entry. this is unlikely to fix anything, it was just an easy thing to do Add NtQueryEvent system call implementation. I don't actually know of any games that need it. Instead of using std::vector + push_back in KeWaitForMultipleObjects and xeNtWaitForMultipleObjectsEx use a fixed size array of 64 and track the count. More than 64 objects is not permitted by the kernel. The repeated reallocations from push_back were appearing unusually high on the profiler, but were masked until now by waitformultipleobjects natural overhead Pre-lock the global lock before looking up each handle for xeNtWaitForMultipleObjectsEx and KeWaitForMultipleObjects. Pre-lock before looking up the signal and waiter in NtSignalAndWaitForSingleObjectEx add missing checks to NtWaitForMultipleObjectsEx Support pre-locking in XObject::GetNativeObject
This commit is contained in:
@@ -264,8 +264,9 @@ ObjectTable::ObjectTableEntry* ObjectTable::LookupTable(X_HANDLE handle) {
|
||||
|
||||
// Generic lookup
|
||||
template <>
|
||||
object_ref<XObject> ObjectTable::LookupObject<XObject>(X_HANDLE handle) {
|
||||
auto object = ObjectTable::LookupObject(handle, false);
|
||||
object_ref<XObject> ObjectTable::LookupObject<XObject>(
|
||||
X_HANDLE handle, bool already_locked) {
|
||||
auto object = ObjectTable::LookupObject(handle, already_locked);
|
||||
auto result = object_ref<XObject>(reinterpret_cast<XObject*>(object));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -46,10 +46,9 @@ class ObjectTable {
|
||||
// Restores a XObject reference with a handle. Mainly for internal use - do
|
||||
// not use.
|
||||
X_STATUS RestoreHandle(X_HANDLE handle, XObject* object);
|
||||
|
||||
template <typename T>
|
||||
object_ref<T> LookupObject(X_HANDLE handle) {
|
||||
auto object = LookupObject(handle, false);
|
||||
object_ref<T> LookupObject(X_HANDLE handle, bool already_locked = false) {
|
||||
auto object = LookupObject(handle, already_locked);
|
||||
if (T::kObjectType == XObject::Type::Socket) {
|
||||
object = LookupObject((handle | 0xF8000000), false);
|
||||
}
|
||||
@@ -110,7 +109,8 @@ class ObjectTable {
|
||||
|
||||
// Generic lookup
|
||||
template <>
|
||||
object_ref<XObject> ObjectTable::LookupObject<XObject>(X_HANDLE handle);
|
||||
object_ref<XObject> ObjectTable::LookupObject<XObject>(
|
||||
X_HANDLE handle, bool already_locked);
|
||||
|
||||
} // namespace util
|
||||
} // namespace kernel
|
||||
|
||||
@@ -54,7 +54,15 @@ DECLARE_XBOXKRNL_EXPORT1(XAudioEnableDucker, kAudio, kStub);
|
||||
|
||||
dword_result_t XAudioRegisterRenderDriverClient_entry(lpdword_t callback_ptr,
|
||||
lpdword_t driver_ptr) {
|
||||
if (!callback_ptr) {
|
||||
return X_E_INVALIDARG;
|
||||
}
|
||||
|
||||
uint32_t callback = callback_ptr[0];
|
||||
|
||||
if (!callback) {
|
||||
return X_E_INVALIDARG;
|
||||
}
|
||||
uint32_t callback_arg = callback_ptr[1];
|
||||
|
||||
auto audio_system = kernel_state()->emulator()->audio_system();
|
||||
|
||||
@@ -515,7 +515,26 @@ dword_result_t NtPulseEvent_entry(dword_t handle,
|
||||
}
|
||||
DECLARE_XBOXKRNL_EXPORT2(NtPulseEvent, kThreading, kImplemented,
|
||||
kHighFrequency);
|
||||
dword_result_t NtQueryEvent_entry(dword_t handle, lpdword_t out_struc) {
|
||||
X_STATUS result = X_STATUS_SUCCESS;
|
||||
|
||||
auto ev = kernel_state()->object_table()->LookupObject<XEvent>(handle);
|
||||
if (ev) {
|
||||
uint32_t type_tmp, state_tmp;
|
||||
|
||||
ev->Query(&type_tmp, &state_tmp);
|
||||
|
||||
out_struc[0] = type_tmp;
|
||||
out_struc[1] = state_tmp;
|
||||
|
||||
} else {
|
||||
result = X_STATUS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
DECLARE_XBOXKRNL_EXPORT2(NtQueryEvent, kThreading, kImplemented,
|
||||
kHighFrequency);
|
||||
uint32_t xeNtClearEvent(uint32_t handle) {
|
||||
X_STATUS result = X_STATUS_SUCCESS;
|
||||
|
||||
@@ -832,23 +851,25 @@ dword_result_t KeWaitForMultipleObjects_entry(
|
||||
lpqword_t timeout_ptr, lpvoid_t wait_block_array_ptr) {
|
||||
assert_true(wait_type <= 1);
|
||||
|
||||
std::vector<object_ref<XObject>> objects;
|
||||
for (uint32_t n = 0; n < count; n++) {
|
||||
auto object_ptr = kernel_memory()->TranslateVirtual(objects_ptr[n]);
|
||||
auto object_ref =
|
||||
XObject::GetNativeObject<XObject>(kernel_state(), object_ptr);
|
||||
if (!object_ref) {
|
||||
return X_STATUS_INVALID_PARAMETER;
|
||||
assert_true(count <= 64);
|
||||
object_ref<XObject> objects[64];
|
||||
{
|
||||
auto crit = global_critical_region::AcquireDirect();
|
||||
for (uint32_t n = 0; n < count; n++) {
|
||||
auto object_ptr = kernel_memory()->TranslateVirtual(objects_ptr[n]);
|
||||
auto object_ref = XObject::GetNativeObject<XObject>(kernel_state(),
|
||||
object_ptr, -1, true);
|
||||
if (!object_ref) {
|
||||
return X_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
objects[n] = std::move(object_ref);
|
||||
}
|
||||
|
||||
objects.push_back(std::move(object_ref));
|
||||
}
|
||||
|
||||
uint64_t timeout = timeout_ptr ? static_cast<uint64_t>(*timeout_ptr) : 0u;
|
||||
return XObject::WaitMultiple(uint32_t(objects.size()),
|
||||
reinterpret_cast<XObject**>(objects.data()),
|
||||
wait_type, wait_reason, processor_mode,
|
||||
alertable, timeout_ptr ? &timeout : nullptr);
|
||||
return XObject::WaitMultiple(
|
||||
uint32_t(count), reinterpret_cast<XObject**>(&objects[0]), wait_type,
|
||||
wait_reason, processor_mode, alertable, timeout_ptr ? &timeout : nullptr);
|
||||
}
|
||||
DECLARE_XBOXKRNL_EXPORT3(KeWaitForMultipleObjects, kThreading, kImplemented,
|
||||
kBlocking, kHighFrequency);
|
||||
@@ -859,19 +880,32 @@ uint32_t xeNtWaitForMultipleObjectsEx(uint32_t count, xe::be<uint32_t>* handles,
|
||||
uint64_t* timeout_ptr) {
|
||||
assert_true(wait_type <= 1);
|
||||
|
||||
std::vector<object_ref<XObject>> objects;
|
||||
for (uint32_t n = 0; n < count; n++) {
|
||||
uint32_t object_handle = handles[n];
|
||||
auto object =
|
||||
kernel_state()->object_table()->LookupObject<XObject>(object_handle);
|
||||
if (!object) {
|
||||
return X_STATUS_INVALID_PARAMETER;
|
||||
assert_true(count <= 64);
|
||||
object_ref<XObject> objects[64];
|
||||
|
||||
/*
|
||||
Reserving to squash the constant reallocations, in a benchmark of one
|
||||
particular game over a period of five minutes roughly 11% of CPU time was
|
||||
spent inside a helper function to Windows' heap allocation function. 7% of
|
||||
that time was traced back to here
|
||||
|
||||
edit: actually switched to fixed size array, as there can never be more
|
||||
than 64 events specified
|
||||
*/
|
||||
{
|
||||
auto crit = global_critical_region::AcquireDirect();
|
||||
for (uint32_t n = 0; n < count; n++) {
|
||||
uint32_t object_handle = handles[n];
|
||||
auto object = kernel_state()->object_table()->LookupObject<XObject>(
|
||||
object_handle, true);
|
||||
if (!object) {
|
||||
return X_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
objects[n] = std::move(object);
|
||||
}
|
||||
objects.push_back(std::move(object));
|
||||
}
|
||||
|
||||
return XObject::WaitMultiple(count,
|
||||
reinterpret_cast<XObject**>(objects.data()),
|
||||
return XObject::WaitMultiple(count, reinterpret_cast<XObject**>(&objects[0]),
|
||||
wait_type, 6, wait_mode, alertable, timeout_ptr);
|
||||
}
|
||||
|
||||
@@ -879,6 +913,9 @@ dword_result_t NtWaitForMultipleObjectsEx_entry(
|
||||
dword_t count, lpdword_t handles, dword_t wait_type, dword_t wait_mode,
|
||||
dword_t alertable, lpqword_t timeout_ptr) {
|
||||
uint64_t timeout = timeout_ptr ? static_cast<uint64_t>(*timeout_ptr) : 0u;
|
||||
if (!count || count > 64 || wait_type != 1 && wait_type) {
|
||||
return X_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
return xeNtWaitForMultipleObjectsEx(count, handles, wait_type, wait_mode,
|
||||
alertable,
|
||||
timeout_ptr ? &timeout : nullptr);
|
||||
@@ -892,11 +929,14 @@ dword_result_t NtSignalAndWaitForSingleObjectEx_entry(dword_t signal_handle,
|
||||
dword_t r6,
|
||||
lpqword_t timeout_ptr) {
|
||||
X_STATUS result = X_STATUS_SUCCESS;
|
||||
// pre-lock for these two handle lookups
|
||||
global_critical_region::mutex().lock();
|
||||
|
||||
auto signal_object =
|
||||
kernel_state()->object_table()->LookupObject<XObject>(signal_handle);
|
||||
auto signal_object = kernel_state()->object_table()->LookupObject<XObject>(
|
||||
signal_handle, true);
|
||||
auto wait_object =
|
||||
kernel_state()->object_table()->LookupObject<XObject>(wait_handle);
|
||||
kernel_state()->object_table()->LookupObject<XObject>(wait_handle, true);
|
||||
global_critical_region::mutex().unlock();
|
||||
if (signal_object && wait_object) {
|
||||
uint64_t timeout = timeout_ptr ? static_cast<uint64_t>(*timeout_ptr) : 0u;
|
||||
result =
|
||||
|
||||
@@ -71,7 +71,12 @@ int32_t XEvent::Reset() {
|
||||
event_->Reset();
|
||||
return 1;
|
||||
}
|
||||
void XEvent::Query(uint32_t* out_type, uint32_t* out_state) {
|
||||
auto [type, state] = event_->Query();
|
||||
|
||||
*out_type = type;
|
||||
*out_state = state;
|
||||
}
|
||||
void XEvent::Clear() { event_->Reset(); }
|
||||
|
||||
bool XEvent::Save(ByteStream* stream) {
|
||||
|
||||
@@ -36,6 +36,7 @@ class XEvent : public XObject {
|
||||
int32_t Set(uint32_t priority_increment, bool wait);
|
||||
int32_t Pulse(uint32_t priority_increment, bool wait);
|
||||
int32_t Reset();
|
||||
void Query(uint32_t* out_type, uint32_t* out_state);
|
||||
void Clear();
|
||||
|
||||
bool Save(ByteStream* stream) override;
|
||||
|
||||
@@ -255,7 +255,8 @@ X_STATUS XObject::WaitMultiple(uint32_t count, XObject** objects,
|
||||
uint32_t wait_type, uint32_t wait_reason,
|
||||
uint32_t processor_mode, uint32_t alertable,
|
||||
uint64_t* opt_timeout) {
|
||||
std::vector<xe::threading::WaitHandle*> wait_handles(count);
|
||||
xe::threading::WaitHandle* wait_handles[64];
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
wait_handles[i] = objects[i]->GetWaitHandle();
|
||||
assert_not_null(wait_handles[i]);
|
||||
@@ -267,7 +268,7 @@ X_STATUS XObject::WaitMultiple(uint32_t count, XObject** objects,
|
||||
: std::chrono::milliseconds::max();
|
||||
|
||||
if (wait_type) {
|
||||
auto result = xe::threading::WaitAny(std::move(wait_handles),
|
||||
auto result = xe::threading::WaitAny(wait_handles, count,
|
||||
alertable ? true : false, timeout_ms);
|
||||
switch (result.first) {
|
||||
case xe::threading::WaitResult::kSuccess:
|
||||
@@ -287,7 +288,7 @@ X_STATUS XObject::WaitMultiple(uint32_t count, XObject** objects,
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
} else {
|
||||
auto result = xe::threading::WaitAll(std::move(wait_handles),
|
||||
auto result = xe::threading::WaitAll(wait_handles, count,
|
||||
alertable ? true : false, timeout_ms);
|
||||
switch (result) {
|
||||
case xe::threading::WaitResult::kSuccess:
|
||||
@@ -360,8 +361,8 @@ void XObject::SetNativePointer(uint32_t native_ptr, bool uninitialized) {
|
||||
}
|
||||
|
||||
object_ref<XObject> XObject::GetNativeObject(KernelState* kernel_state,
|
||||
void* native_ptr,
|
||||
int32_t as_type) {
|
||||
void* native_ptr, int32_t as_type,
|
||||
bool already_locked) {
|
||||
assert_not_null(native_ptr);
|
||||
|
||||
// Unfortunately the XDK seems to inline some KeInitialize calls, meaning
|
||||
@@ -373,10 +374,12 @@ object_ref<XObject> XObject::GetNativeObject(KernelState* kernel_state,
|
||||
// We identify this by setting wait_list_flink to a magic value. When set,
|
||||
// wait_list_blink will hold a handle to our object.
|
||||
|
||||
auto global_lock = xe::global_critical_region::AcquireDirect();
|
||||
if (!already_locked) {
|
||||
global_critical_region::mutex().lock();
|
||||
}
|
||||
|
||||
auto header = reinterpret_cast<X_DISPATCH_HEADER*>(native_ptr);
|
||||
|
||||
XObject* result;
|
||||
if (as_type == -1) {
|
||||
as_type = header->type;
|
||||
}
|
||||
@@ -385,10 +388,12 @@ object_ref<XObject> XObject::GetNativeObject(KernelState* kernel_state,
|
||||
// Already initialized.
|
||||
// TODO: assert if the type of the object != as_type
|
||||
uint32_t handle = header->wait_list_blink;
|
||||
auto object = kernel_state->object_table()->LookupObject<XObject>(handle);
|
||||
|
||||
result = kernel_state->object_table()
|
||||
->LookupObject<XObject>(handle, true)
|
||||
.release();
|
||||
goto return_result;
|
||||
// TODO(benvanik): assert nothing has been changed in the struct.
|
||||
return object;
|
||||
// return object;
|
||||
} else {
|
||||
// First use, create new.
|
||||
// https://www.nirsoft.net/kernel_struct/vista/KOBJECTS.html
|
||||
@@ -430,14 +435,22 @@ object_ref<XObject> XObject::GetNativeObject(KernelState* kernel_state,
|
||||
case 24: // ThreadedDpcObject
|
||||
default:
|
||||
assert_always();
|
||||
return NULL;
|
||||
result = nullptr;
|
||||
goto return_result;
|
||||
|
||||
// return NULL;
|
||||
}
|
||||
|
||||
// Stash pointer in struct.
|
||||
// FIXME: This assumes the object contains a dispatch header (some don't!)
|
||||
StashHandle(header, object->handle());
|
||||
result = object;
|
||||
|
||||
return object_ref<XObject>(object);
|
||||
return_result:
|
||||
if (!already_locked) {
|
||||
global_critical_region::mutex().unlock();
|
||||
}
|
||||
return object_ref<XObject>(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -192,10 +192,12 @@ class XObject {
|
||||
|
||||
static object_ref<XObject> GetNativeObject(KernelState* kernel_state,
|
||||
void* native_ptr,
|
||||
int32_t as_type = -1);
|
||||
int32_t as_type = -1,
|
||||
bool already_locked = false);
|
||||
template <typename T>
|
||||
static object_ref<T> GetNativeObject(KernelState* kernel_state,
|
||||
void* native_ptr, int32_t as_type = -1);
|
||||
void* native_ptr, int32_t as_type = -1,
|
||||
bool already_locked = false);
|
||||
|
||||
protected:
|
||||
bool SaveObject(ByteStream* stream);
|
||||
@@ -362,9 +364,11 @@ object_ref<T> retain_object(T* ptr) {
|
||||
|
||||
template <typename T>
|
||||
object_ref<T> XObject::GetNativeObject(KernelState* kernel_state,
|
||||
void* native_ptr, int32_t as_type) {
|
||||
void* native_ptr, int32_t as_type,
|
||||
bool already_locked) {
|
||||
return object_ref<T>(reinterpret_cast<T*>(
|
||||
GetNativeObject(kernel_state, native_ptr, as_type).release()));
|
||||
GetNativeObject(kernel_state, native_ptr, as_type, already_locked)
|
||||
.release()));
|
||||
}
|
||||
|
||||
} // namespace kernel
|
||||
|
||||
Reference in New Issue
Block a user