mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
fix(nvidia): retain readback, resize and bounded completion fixes
Rebuild the remaining NVIDIA changes on current main, preserving the merged visual, draw-buffer, descriptor ownership and mixed Metal-tree fixes. Scope native-thread forwarding to resize notifications and make completion polling per device, bounded and part of the caller timeout budget. Co-authored-by: Pirrandi <[email protected]>
This commit is contained in:
1 parent
1125666cf2
commit
a5ceabb8e8
8 files changed
+384
-39
No files matched your search
@@ -252,6 +252,30 @@ A non-zero error or `FAILED` means the frames cannot reach the window. Before 20
|
||||
|
||||
An additional NVIDIA report on `68f816d` showed successful optimal-tiling import but repeated `Metal Device timed out giving us a Drawable to present` messages. Its [presentation trace](https://github.com/lucannez64/darling-lol/issues/3#issuecomment-6214732591) shows all three drawables queued and all three main-thread presentation callbacks running, followed by timeouts with three frames still queued. The compositor only prepared a Metal layer when it was the root layer, skipping nested Metal layers. It now prepares Metal layers throughout the tree. A standalone nested-layer reproduction exhausts the pool before this correction and passes ten presentations with pixel readback after it; the root-layer probe also passes. Confirmation on the reporter's NVIDIA system is still needed.
|
||||
|
||||
### NVIDIA resize signals, CPU readback and completion latency
|
||||
|
||||
The EGL-compatible window visual, compositor back buffer and collision-safe RPC
|
||||
socket allocation fixes are already part of the runtime. The remaining NVIDIA
|
||||
work covers these independent paths:
|
||||
|
||||
- A terminal resize can deliver `SIGWINCH` to a native driver thread without a
|
||||
Darling RPC socket. The runtime forwards that resize notification to the guest
|
||||
main thread; it keeps the existing handling for other signals and faults.
|
||||
- With `RCLOL_GPU_DIRECT_PRESENT=0`, Chromium frames arrive through CPU readback.
|
||||
The local compositor skips non-Metal trees to avoid covering the live client
|
||||
with stale cached textures. Mixed trees still prepare and present Metal layers.
|
||||
- NVIDIA timeline waits can wake late after GPU completion. Each device now polls
|
||||
briefly while completions are pending before blocking: 2000 microseconds by
|
||||
default on NVIDIA and zero on other vendors. An idle device blocks immediately.
|
||||
The guest file `/tmp/metal-wait-spin-us` overrides the budget when the device is
|
||||
created; `0` disables polling. Values are capped at 10000 microseconds and
|
||||
malformed values use the vendor default. Restart Darling to change the budget.
|
||||
|
||||
The contributor reported custom matches on a single-GPU RTX 3060, driver 615.71,
|
||||
with sway/Xwayland using CPU readback. The initial polling change improved their
|
||||
reported rate from about 20 to 27 FPS. Those observations predate the narrowed
|
||||
integration; they do not establish hybrid-GPU or general NVIDIA support.
|
||||
|
||||
### Host-side checks
|
||||
|
||||
These are host settings, not game settings. Run on AC power, and use a performance power profile (`powerprofilesctl set performance`, or `asusctl profile -P Performance` on ASUS laptops). On the tested desktop the game is limited mostly by its own main thread on the CPU, not by the GPU (see [status](STATUS.md#game-fps-2026-10-1314)), so laptop CPU power limits matter as much as the GPU choice.
|
||||
|
||||
+18
-12
@@ -8756,10 +8756,10 @@ index 5dcac115..5de4f03f 100644
|
||||
+
|
||||
@end
|
||||
diff --git a/QuartzCore/CALayerContext.m b/QuartzCore/CALayerContext.m
|
||||
index 5dce8c56..d325ba5f 100644
|
||||
index 5dce8c5..3a7f937 100644
|
||||
--- a/QuartzCore/CALayerContext.m
|
||||
+++ b/QuartzCore/CALayerContext.m
|
||||
@@ -1,47 +1,69 @@
|
||||
@@ -1,47 +1,77 @@
|
||||
#import <Foundation/NSString.h>
|
||||
+#import <Foundation/NSMutableArray.h>
|
||||
#import <QuartzCore/CALayer.h>
|
||||
@@ -8799,6 +8799,14 @@ index 5dce8c56..d325ba5f 100644
|
||||
+ for(CALayer *child in layer.sublayers)
|
||||
+ if(hasFullWindowSurface(child,size))return YES;
|
||||
+ return NO;
|
||||
+}
|
||||
+/* CPU readback already presents non-Metal trees remotely. Locally
|
||||
+ * compositing their cached textures can leave a frozen child over the client.
|
||||
+ * Metal trees must always prepare their drawables, including mixed trees. */
|
||||
+static BOOL skipLocalRender(CALayer *layer, CGSize size) {
|
||||
+ return layer && getenv("DARLING_CA_SKIP_LOCAL_IOSURFACE_RENDER") &&
|
||||
+ !hasMetalLayer(layer) &&
|
||||
+ (!getenv("RCLOL_GPU_DIRECT_PRESENT") || hasFullWindowSurface(layer, size));
|
||||
+}
|
||||
@implementation CALayerContext
|
||||
|
||||
@@ -8848,7 +8856,7 @@ index 5dce8c56..d325ba5f 100644
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -51,12 +73,22 @@
|
||||
@@ -51,12 +81,22 @@
|
||||
}
|
||||
|
||||
- (void) setLayer: (CALayer *) layer {
|
||||
@@ -8871,7 +8879,7 @@ index 5dce8c56..d325ba5f 100644
|
||||
}
|
||||
|
||||
- (void) setSubwindow: (CGSubWindow*) subwindow
|
||||
@@ -68,16 +100,50 @@
|
||||
@@ -68,16 +108,50 @@
|
||||
}
|
||||
|
||||
_subwindow = [subwindow retain];
|
||||
@@ -8923,7 +8931,7 @@ index 5dce8c56..d325ba5f 100644
|
||||
}
|
||||
|
||||
- (void) assignTextureIdsToLayerTree: (CALayer *) layer {
|
||||
@@ -93,8 +159,27 @@
|
||||
@@ -93,8 +167,27 @@
|
||||
[self assignTextureIdsToLayerTree: child];
|
||||
}
|
||||
|
||||
@@ -8951,7 +8959,7 @@ index 5dce8c56..d325ba5f 100644
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glShadeModel(GL_SMOOTH);
|
||||
@@ -118,27 +203,63 @@
|
||||
@@ -118,27 +211,62 @@
|
||||
|
||||
[_deleteTextureIds removeAllObjects];
|
||||
|
||||
@@ -8975,8 +8983,7 @@ index 5dce8c56..d325ba5f 100644
|
||||
- (void) render {
|
||||
+ if (_cglWindow == NULL || _layer == nil)
|
||||
+ return;
|
||||
+ if(getenv("DARLING_CA_SKIP_LOCAL_IOSURFACE_RENDER") &&
|
||||
+ !hasMetalLayer(_layer) && hasFullWindowSurface(_layer,_frame.size))return;
|
||||
+ if(skipLocalRender(_layer, _frame.size))return;
|
||||
[self renderLayer: _layer];
|
||||
}
|
||||
|
||||
@@ -9020,7 +9027,7 @@ index 5dce8c56..d325ba5f 100644
|
||||
}
|
||||
|
||||
- (void) startTimerIfNeeded {
|
||||
@@ -147,7 +268,7 @@
|
||||
@@ -147,7 +275,7 @@
|
||||
target: self
|
||||
selector: @selector(timer:)
|
||||
userInfo: nil
|
||||
@@ -9029,13 +9036,12 @@ index 5dce8c56..d325ba5f 100644
|
||||
}
|
||||
|
||||
- (void) deleteTextureId: (NSNumber *) textureId {
|
||||
@@ -155,7 +276,10 @@
|
||||
@@ -155,7 +283,9 @@
|
||||
}
|
||||
|
||||
- (void) flush {
|
||||
- CGLFlushDrawable(_glContext);
|
||||
+ if(getenv("DARLING_CA_SKIP_LOCAL_IOSURFACE_RENDER") && _layer &&
|
||||
+ !hasMetalLayer(_layer) && hasFullWindowSurface(_layer,_frame.size))return;
|
||||
+ if(skipLocalRender(_layer, _frame.size))return;
|
||||
+ if (_glContext != NULL && _cglWindow != NULL)
|
||||
+ CGLFlushDrawable(_glContext);
|
||||
}
|
||||
|
||||
@@ -10706,3 +10706,19 @@ index 0000000..1f3c473
|
||||
+ }
|
||||
+}
|
||||
+#endif
|
||||
diff --git a/src/startup/mldr/elfcalls/threads.c b/src/startup/mldr/elfcalls/threads.c
|
||||
index e789986..0d56b4e 100644
|
||||
--- a/src/startup/mldr/elfcalls/threads.c
|
||||
+++ b/src/startup/mldr/elfcalls/threads.c
|
||||
@@ -387,8 +387,9 @@ int __darling_thread_rpc_socket(void) {
|
||||
// this is the main thread
|
||||
t_server_socket = __dserver_main_thread_socket_fd;
|
||||
} else {
|
||||
- // threads should already have a per-thread socket assigned when they're created
|
||||
- abort();
|
||||
+ // Native host-library threads do not have a Darling RPC connection.
|
||||
+ // Let the caller report EPIPE, including the resize-signal bridge.
|
||||
+ return -1;
|
||||
}
|
||||
}
|
||||
return t_server_socket;
|
||||
@@ -781,7 +781,7 @@ index 8546daa..7e4993c 100644
|
||||
void updateBindings();
|
||||
|
||||
diff --git a/private-include/indium/device.private.hpp b/private-include/indium/device.private.hpp
|
||||
index 58f1794..be7dbdc 100644
|
||||
index 58f1794..1f4bd5a 100644
|
||||
--- a/private-include/indium/device.private.hpp
|
||||
+++ b/private-include/indium/device.private.hpp
|
||||
@@ -13,6 +13,8 @@
|
||||
@@ -793,7 +793,7 @@ index 58f1794..be7dbdc 100644
|
||||
|
||||
namespace Indium {
|
||||
class PrivateDevice;
|
||||
@@ -25,6 +27,8 @@ namespace Indium {
|
||||
@@ -25,8 +27,11 @@ namespace Indium {
|
||||
class PrivateDevice: public Device, public std::enable_shared_from_this<PrivateDevice> {
|
||||
private:
|
||||
|
||||
@@ -801,8 +801,11 @@ index 58f1794..be7dbdc 100644
|
||||
+ std::vector<std::shared_ptr<Buffer>> _scratchFree;
|
||||
std::mutex _eventLoopMutex;
|
||||
std::mutex _pollingMutex;
|
||||
+ uint64_t _completionSpinNanoseconds = 0;
|
||||
|
||||
@@ -59,6 +63,8 @@ namespace Indium {
|
||||
// these are stored separately (rather than in a single structure)
|
||||
// so that we can easily copy them individually in the event loop polling function.
|
||||
@@ -59,6 +64,8 @@ namespace Indium {
|
||||
}
|
||||
|
||||
virtual std::string name() const override;
|
||||
@@ -811,7 +814,7 @@ index 58f1794..be7dbdc 100644
|
||||
virtual std::shared_ptr<CommandQueue> newCommandQueue() override;
|
||||
virtual std::shared_ptr<RenderPipelineState> newRenderPipelineState(const RenderPipelineDescriptor& descriptor) override;
|
||||
virtual std::shared_ptr<ComputePipelineState> newComputePipelineState(const ComputePipelineDescriptor& descriptor, PipelineOption options, std::shared_ptr<ComputePipelineReflection> reflection) override;
|
||||
@@ -73,6 +79,12 @@ namespace Indium {
|
||||
@@ -73,6 +80,12 @@ namespace Indium {
|
||||
virtual void pollEvents(uint64_t timeoutNanoseconds) override;
|
||||
virtual void wakeupEventLoop() override;
|
||||
|
||||
@@ -824,7 +827,7 @@ index 58f1794..be7dbdc 100644
|
||||
void waitForSemaphore(VkSemaphore semaphore, uint64_t targetValue, std::function<void()> callback);
|
||||
|
||||
/**
|
||||
@@ -86,6 +98,11 @@ namespace Indium {
|
||||
@@ -86,6 +99,11 @@ namespace Indium {
|
||||
* Similar to getTimelineSemaphore(), but wraps it in a shared pointer
|
||||
* that automatically releases the semaphore when all references to it die out.
|
||||
*/
|
||||
@@ -836,7 +839,7 @@ index 58f1794..be7dbdc 100644
|
||||
std::shared_ptr<TimelineSemaphore> getWrappedTimelineSemaphore();
|
||||
|
||||
BinarySemaphore getBinarySemaphore(bool exportable = false);
|
||||
@@ -111,5 +128,34 @@ namespace Indium {
|
||||
@@ -111,5 +129,34 @@ namespace Indium {
|
||||
|
||||
INDIUM_PROPERTY(VkPhysicalDeviceMemoryProperties, m, M,emoryProperties);
|
||||
INDIUM_PROPERTY_READONLY(Feature, f, F,eatures);
|
||||
@@ -2835,17 +2838,20 @@ index 5f62ad0..5957bec 100644
|
||||
DynamicVK::vkCmdBindDescriptorSets(buf->commandBuffer(), VK_PIPELINE_BIND_POINT_COMPUTE, _pso->layout(), 0, descriptorSets.size(), descriptorSets.data(), 0, nullptr);
|
||||
};
|
||||
diff --git a/src/indium/device.cpp b/src/indium/device.cpp
|
||||
index 322e186..78b77ae 100644
|
||||
index 322e186..4da420a 100644
|
||||
--- a/src/indium/device.cpp
|
||||
+++ b/src/indium/device.cpp
|
||||
@@ -1,4 +1,6 @@
|
||||
@@ -1,4 +1,9 @@
|
||||
+#include <chrono>
|
||||
+#include <cstdio>
|
||||
+#include <indium/completion-spin.private.hpp>
|
||||
#include <indium/device.private.hpp>
|
||||
+#include <indium/submit-thread.private.hpp>
|
||||
+#include <indium/command-encoder.private.hpp>
|
||||
#include <indium/instance.private.hpp>
|
||||
#include <indium/command-queue.private.hpp>
|
||||
#include <indium/render-pipeline.private.hpp>
|
||||
@@ -12,39 +14,207 @@
|
||||
@@ -12,39 +17,207 @@
|
||||
|
||||
#include <iridium/iridium.hpp>
|
||||
|
||||
@@ -3059,7 +3065,7 @@ index 322e186..78b77ae 100644
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -67,12 +237,105 @@ void Indium::initGlobalDeviceList() {
|
||||
@@ -67,12 +240,105 @@ void Indium::initGlobalDeviceList() {
|
||||
DynamicVK::vkGetPhysicalDeviceFeatures2(device, &features);
|
||||
|
||||
if (!features12.timelineSemaphore) {
|
||||
@@ -3155,11 +3161,11 @@ index 322e186..78b77ae 100644
|
||||
+ }
|
||||
+ creationOrder.push_back(i);
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
+
|
||||
+ for (size_t index: creationOrder) {
|
||||
+ globalDeviceList.push_back(std::make_shared<PrivateDevice>(candidates[index].device));
|
||||
}
|
||||
+ }
|
||||
+
|
||||
+ appendDeviceLog("created VkDevice for " + std::to_string(globalDeviceList.size()) + " of "
|
||||
+ + std::to_string(candidates.size()) + " supported device(s)"
|
||||
@@ -3167,7 +3173,7 @@ index 322e186..78b77ae 100644
|
||||
};
|
||||
|
||||
void Indium::finitGlobalDeviceList() {
|
||||
@@ -83,7 +346,8 @@ Indium::Device::~Device() {};
|
||||
@@ -83,8 +349,16 @@ Indium::Device::~Device() {};
|
||||
|
||||
Indium::PrivateDevice::PrivateDevice(VkPhysicalDevice physicalDevice):
|
||||
_physicalDevice(physicalDevice)
|
||||
@@ -3175,9 +3181,17 @@ index 322e186..78b77ae 100644
|
||||
+{ Indium::SubmitThread::ensureStarted();
|
||||
+
|
||||
DynamicVK::vkGetPhysicalDeviceProperties(_physicalDevice, &_properties);
|
||||
+ _completionSpinNanoseconds = completionSpinBudget(_properties.vendorID, nullptr);
|
||||
+ if (FILE* file = fopen("/tmp/metal-wait-spin-us", "r")) {
|
||||
+ char value[64] = {};
|
||||
+ if (fscanf(file, "%63s", value) == 1)
|
||||
+ _completionSpinNanoseconds = completionSpinBudget(_properties.vendorID, value);
|
||||
+ fclose(file);
|
||||
+ }
|
||||
|
||||
std::vector<VkQueueFamilyProperties> queueFamilies;
|
||||
@@ -233,6 +497,36 @@ Indium::PrivateDevice::PrivateDevice(VkPhysicalDevice physicalDevice):
|
||||
uint32_t count = 0;
|
||||
@@ -233,6 +507,36 @@ Indium::PrivateDevice::PrivateDevice(VkPhysicalDevice physicalDevice):
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3214,7 +3228,7 @@ index 322e186..78b77ae 100644
|
||||
VkDeviceCreateInfo deviceCreateInfo {};
|
||||
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||
deviceCreateInfo.pQueueCreateInfos = queueCreateInfos.data();
|
||||
@@ -246,6 +540,7 @@ Indium::PrivateDevice::PrivateDevice(VkPhysicalDevice physicalDevice):
|
||||
@@ -246,6 +550,7 @@ Indium::PrivateDevice::PrivateDevice(VkPhysicalDevice physicalDevice):
|
||||
}
|
||||
|
||||
_features = indiumFeatures;
|
||||
@@ -3222,7 +3236,7 @@ index 322e186..78b77ae 100644
|
||||
|
||||
for (const auto& index: queueFamilyIndices) {
|
||||
VkQueue queue;
|
||||
@@ -297,6 +592,9 @@ Indium::PrivateDevice::PrivateDevice(VkPhysicalDevice physicalDevice):
|
||||
@@ -297,6 +602,9 @@ Indium::PrivateDevice::PrivateDevice(VkPhysicalDevice physicalDevice):
|
||||
};
|
||||
|
||||
Indium::PrivateDevice::~PrivateDevice() {
|
||||
@@ -3232,7 +3246,7 @@ index 322e186..78b77ae 100644
|
||||
if (_oneshotCommandPool) {
|
||||
DynamicVK::vkDestroyCommandPool(_device, _oneshotCommandPool, nullptr);
|
||||
}
|
||||
@@ -308,6 +606,23 @@ std::string Indium::PrivateDevice::name() const {
|
||||
@@ -308,6 +616,23 @@ std::string Indium::PrivateDevice::name() const {
|
||||
return _properties.deviceName;
|
||||
};
|
||||
|
||||
@@ -3256,7 +3270,7 @@ index 322e186..78b77ae 100644
|
||||
std::shared_ptr<Indium::CommandQueue> Indium::PrivateDevice::newCommandQueue() {
|
||||
return std::make_shared<PrivateCommandQueue>(shared_from_this());
|
||||
};
|
||||
@@ -317,9 +632,9 @@ std::shared_ptr<Indium::RenderPipelineState> Indium::PrivateDevice::newRenderPip
|
||||
@@ -317,9 +642,9 @@ std::shared_ptr<Indium::RenderPipelineState> Indium::PrivateDevice::newRenderPip
|
||||
};
|
||||
|
||||
std::shared_ptr<Indium::ComputePipelineState> Indium::PrivateDevice::newComputePipelineState(const ComputePipelineDescriptor& descriptor, PipelineOption options, std::shared_ptr<ComputePipelineReflection> reflection) {
|
||||
@@ -3269,7 +3283,7 @@ index 322e186..78b77ae 100644
|
||||
return std::make_shared<PrivateComputePipelineState>(shared_from_this(), descriptor);
|
||||
};
|
||||
|
||||
@@ -357,6 +672,7 @@ std::shared_ptr<Indium::Library> Indium::PrivateDevice::newLibrary(const void* d
|
||||
@@ -357,6 +682,7 @@ std::shared_ptr<Indium::Library> Indium::PrivateDevice::newLibrary(const void* d
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3277,7 +3291,34 @@ index 322e186..78b77ae 100644
|
||||
funcInfo.bindings.insert(funcInfo.bindings.end(), info.bindings.begin(), info.bindings.end());
|
||||
funcInfo.embeddedSamplers.insert(funcInfo.embeddedSamplers.end(), info.embeddedSamplers.begin(), info.embeddedSamplers.end());
|
||||
}
|
||||
@@ -480,9 +796,18 @@ void Indium::PrivateDevice::waitForSemaphore(VkSemaphore semaphore, uint64_t tar
|
||||
@@ -396,6 +722,26 @@ void Indium::PrivateDevice::pollEvents(uint64_t timeoutNanoseconds) {
|
||||
|
||||
lock.unlock();
|
||||
|
||||
+ // NVIDIA's blocking timeline wait can wake 1-2 ms after completion. Poll
|
||||
+ // briefly while work is pending; an idle device should block immediately.
|
||||
+ if (_completionSpinNanoseconds && timeoutNanoseconds && semaphores.size() > 1) {
|
||||
+ const auto started = std::chrono::steady_clock::now();
|
||||
+ const auto deadline = started + std::chrono::nanoseconds(
|
||||
+ std::min(_completionSpinNanoseconds, timeoutNanoseconds));
|
||||
+ bool anyReady = false;
|
||||
+ do {
|
||||
+ for (size_t i = 0; i < semaphores.size() && !anyReady; ++i) {
|
||||
+ uint64_t count = 0;
|
||||
+ anyReady = DynamicVK::vkGetSemaphoreCounterValue(_device, semaphores[i], &count) == VK_SUCCESS && count >= values[i];
|
||||
+ }
|
||||
+ if (anyReady) break;
|
||||
+ __builtin_ia32_pause();
|
||||
+ } while (std::chrono::steady_clock::now() < deadline);
|
||||
+ const uint64_t elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
+ std::chrono::steady_clock::now() - started).count();
|
||||
+ timeoutNanoseconds = anyReady ? 0 : timeoutNanoseconds - std::min(timeoutNanoseconds, elapsed);
|
||||
+ }
|
||||
+
|
||||
VkSemaphoreWaitInfo info {};
|
||||
info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO;
|
||||
info.semaphoreCount = semaphores.size();
|
||||
@@ -480,9 +826,18 @@ void Indium::PrivateDevice::waitForSemaphore(VkSemaphore semaphore, uint64_t tar
|
||||
wakeupEventLoop();
|
||||
};
|
||||
|
||||
@@ -3297,7 +3338,7 @@ index 322e186..78b77ae 100644
|
||||
VkSemaphoreTypeCreateInfo typeInfo {};
|
||||
typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO;
|
||||
typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE;
|
||||
@@ -505,7 +830,14 @@ Indium::TimelineSemaphore Indium::PrivateDevice::getTimelineSemaphore() {
|
||||
@@ -505,7 +860,14 @@ Indium::TimelineSemaphore Indium::PrivateDevice::getTimelineSemaphore() {
|
||||
};
|
||||
|
||||
void Indium::PrivateDevice::putTimelineSemaphore(const TimelineSemaphore& semaphore) {
|
||||
@@ -3313,7 +3354,7 @@ index 322e186..78b77ae 100644
|
||||
};
|
||||
|
||||
std::shared_ptr<Indium::TimelineSemaphore> Indium::PrivateDevice::getWrappedTimelineSemaphore() {
|
||||
@@ -551,3 +883,160 @@ std::shared_ptr<Indium::BinarySemaphore> Indium::PrivateDevice::getWrappedBinary
|
||||
@@ -551,3 +913,160 @@ std::shared_ptr<Indium::BinarySemaphore> Indium::PrivateDevice::getWrappedBinary
|
||||
delete ptr;
|
||||
});
|
||||
};
|
||||
@@ -8767,3 +8808,27 @@ index 1b5876e..03af560 100644
|
||||
};
|
||||
|
||||
void Iridium::DynamicLLVM::finit() {
|
||||
diff --git a/private-include/indium/completion-spin.private.hpp b/private-include/indium/completion-spin.private.hpp
|
||||
new file mode 100644
|
||||
index 0000000..962fdad
|
||||
--- /dev/null
|
||||
+++ b/private-include/indium/completion-spin.private.hpp
|
||||
@@ -0,0 +1,18 @@
|
||||
+#pragma once
|
||||
+#include <cstdint>
|
||||
+
|
||||
+namespace Indium {
|
||||
+// A malformed override uses the vendor default. Saturation bounds both CPU use
|
||||
+// and the microseconds-to-nanoseconds conversion, even for arbitrarily long input.
|
||||
+inline uint64_t completionSpinBudget(uint32_t vendorID, const char* overrideValue) {
|
||||
+ const uint64_t fallback = vendorID == 0x10de ? 2000000 : 0;
|
||||
+ if (!overrideValue || !*overrideValue) return fallback;
|
||||
+ uint64_t microseconds = 0;
|
||||
+ for (const char* p = overrideValue; *p; ++p) {
|
||||
+ if (*p < '0' || *p > '9') return fallback;
|
||||
+ microseconds = microseconds * 10 + (*p - '0');
|
||||
+ if (microseconds > 10000) microseconds = 10000;
|
||||
+ }
|
||||
+ return microseconds * 1000;
|
||||
+}
|
||||
+}
|
||||
+18
-3
@@ -1576,7 +1576,7 @@ index dc8fbb8..171746a 100644
|
||||
+ __linux_sched_yield_site("SWTYLD");
|
||||
+}
|
||||
diff --git a/darling/src/libsystem_kernel/emulation/src/linux_premigration/signal/sigexc.c b/darling/src/libsystem_kernel/emulation/src/linux_premigration/signal/sigexc.c
|
||||
index 206e92e..0646cb7 100644
|
||||
index 206e92e..1a763fe 100644
|
||||
--- a/darling/src/libsystem_kernel/emulation/src/linux_premigration/signal/sigexc.c
|
||||
+++ b/darling/src/libsystem_kernel/emulation/src/linux_premigration/signal/sigexc.c
|
||||
@@ -151,8 +151,13 @@ void sigrt_handler(int signum, struct linux_siginfo* info, struct linux_ucontext
|
||||
@@ -1595,7 +1595,7 @@ index 206e92e..0646cb7 100644
|
||||
}
|
||||
|
||||
if (signum == SIGNAL_SIGEXC_SUSPEND) {
|
||||
@@ -267,13 +272,150 @@ static void state_from_kernel(struct linux_ucontext* ctxt, const void* tstate, c
|
||||
@@ -267,13 +272,165 @@ static void state_from_kernel(struct linux_ucontext* ctxt, const void* tstate, c
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1672,10 +1672,25 @@ index 206e92e..0646cb7 100644
|
||||
+}
|
||||
+
|
||||
+#endif
|
||||
+
|
||||
+/* Native driver threads have no Darling RPC connection. Forward resize
|
||||
+ * notifications to the guest main thread without allocating or using RPC.
|
||||
+ * Other signals, including synchronous faults and queued payloads, retain
|
||||
+ * their existing handling. Never swallow a failed forward. */
|
||||
+static int forward_native_resize_signal(int linux_signum, int status) {
|
||||
+ if (status != -32 /* -LINUX_EPIPE */ || linux_signum != LINUX_SIGWINCH)
|
||||
+ return 0;
|
||||
+ long pid = LINUX_SYSCALL(__NR_getpid);
|
||||
+ if (LINUX_SYSCALL(__NR_gettid) == pid)
|
||||
+ return 0;
|
||||
+ return LINUX_SYSCALL(__NR_tgkill, pid, pid, linux_signum) == 0;
|
||||
+}
|
||||
+
|
||||
void sigexc_handler(int linux_signum, struct linux_siginfo* info, struct linux_ucontext* ctxt)
|
||||
{
|
||||
int status = dserver_rpc_interrupt_enter();
|
||||
+ if (forward_native_resize_signal(linux_signum, status))
|
||||
+ return;
|
||||
+ int relaxed = 0;
|
||||
+
|
||||
+#ifdef __x86_64__
|
||||
@@ -1747,7 +1762,7 @@ index 206e92e..0646cb7 100644
|
||||
}
|
||||
|
||||
kern_printf("sigexc_handler(%d, %p, %p)\n", linux_signum, info, ctxt);
|
||||
@@ -359,7 +501,13 @@ void sigexc_handler(int linux_signum, struct linux_siginfo* info, struct linux_u
|
||||
@@ -359,7 +516,13 @@ void sigexc_handler(int linux_signum, struct linux_siginfo* info, struct linux_u
|
||||
sizeof(sa.sa_mask));
|
||||
|
||||
// Resend signal to self
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
"""Compile the patched native-thread signal bridge and completion budget policy."""
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
import test_runtime_compatibility as sources
|
||||
|
||||
|
||||
class NativeThreadChecks(unittest.TestCase):
|
||||
patched = sources.CompatibilityChecks.patched
|
||||
run_c = sources.CompatibilityChecks.run_c
|
||||
|
||||
def test_resize_forwarding_is_limited_to_foreign_threads_and_successful_delivery(self):
|
||||
source = self.patched('xnu', 'darling/src/libsystem_kernel/emulation/src/linux_premigration/signal/sigexc.c')
|
||||
bridge = sources.function(source, 'forward_native_resize_signal')
|
||||
self.run_c('''
|
||||
#include <assert.h>
|
||||
#define LINUX_SIGWINCH 28
|
||||
#define __NR_getpid 1
|
||||
#define __NR_gettid 2
|
||||
#define __NR_tgkill 3
|
||||
static long tid=101, result=0;
|
||||
static int sends;
|
||||
static long fake_syscall(int number, ...) {
|
||||
if (number==__NR_getpid) return 100;
|
||||
if (number==__NR_gettid) return tid;
|
||||
++sends; return result;
|
||||
}
|
||||
#define LINUX_SYSCALL(...) fake_syscall(__VA_ARGS__)
|
||||
''' + bridge + '''
|
||||
int main(void) {
|
||||
assert(forward_native_resize_signal(28,-32)==1 && sends==1);
|
||||
assert(forward_native_resize_signal(28,0)==0 && sends==1);
|
||||
assert(forward_native_resize_signal(28,-9)==0 && sends==1);
|
||||
for(int sig=1;sig<=64;++sig)
|
||||
if(sig!=28) assert(forward_native_resize_signal(sig,-32)==0);
|
||||
assert(sends==1);
|
||||
tid=100; assert(forward_native_resize_signal(28,-32)==0 && sends==1);
|
||||
tid=101; result=-3;
|
||||
assert(forward_native_resize_signal(28,-32)==0 && sends==2);
|
||||
}
|
||||
''')
|
||||
|
||||
def test_native_thread_without_rpc_socket_returns_an_error_instead_of_aborting(self):
|
||||
source = self.patched('darling', 'src/startup/mldr/elfcalls/threads.c')
|
||||
self.run_c('''
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#define SYS_gettid 1
|
||||
static int t_server_socket=-1, __dserver_main_thread_socket_fd=71;
|
||||
static int tid=101;
|
||||
static int getpid(void) { return 100; }
|
||||
static int syscall(int number) { return tid; }
|
||||
''' + sources.function(source, '__darling_thread_rpc_socket') + '''
|
||||
int main(void) {
|
||||
assert(__darling_thread_rpc_socket()==-1);
|
||||
t_server_socket=73; assert(__darling_thread_rpc_socket()==73);
|
||||
t_server_socket=-1; tid=100; assert(__darling_thread_rpc_socket()==71);
|
||||
}
|
||||
''')
|
||||
|
||||
def test_cpu_readback_skips_cached_surfaces_but_preserves_mixed_metal_trees(self):
|
||||
source = self.patched('cocotron', 'QuartzCore/CALayerContext.m')
|
||||
self.run_c('''#define _POSIX_C_SOURCE 200809L
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
typedef int BOOL;
|
||||
typedef struct { int metal, full; } CALayer;
|
||||
typedef struct { int width, height; } CGSize;
|
||||
static BOOL hasMetalLayer(CALayer* layer) { return layer->metal; }
|
||||
static BOOL hasFullWindowSurface(CALayer* layer, CGSize size) { return layer->full; }
|
||||
''' + sources.function(source, 'skipLocalRender') + '''
|
||||
int main(void) {
|
||||
CGSize size={16,16};
|
||||
assert(!skipLocalRender(NULL,size));
|
||||
for(int shortcut=0;shortcut<=1;++shortcut)
|
||||
for(int direct=0;direct<=1;++direct)
|
||||
for(int metal=0;metal<=1;++metal)
|
||||
for(int full=0;full<=1;++full) {
|
||||
CALayer layer={metal,full};
|
||||
if(shortcut) setenv("DARLING_CA_SKIP_LOCAL_IOSURFACE_RENDER","1",1);
|
||||
else unsetenv("DARLING_CA_SKIP_LOCAL_IOSURFACE_RENDER");
|
||||
if(direct) setenv("RCLOL_GPU_DIRECT_PRESENT","1",1);
|
||||
else unsetenv("RCLOL_GPU_DIRECT_PRESENT");
|
||||
assert(!!skipLocalRender(&layer,size)==(shortcut && !metal && (!direct || full)));
|
||||
}
|
||||
}
|
||||
''')
|
||||
|
||||
|
||||
@unittest.skipUnless(shutil.which('c++'), 'C++ compiler required')
|
||||
class CompletionBudgetChecks(unittest.TestCase):
|
||||
patched = sources.CompatibilityChecks.patched
|
||||
|
||||
def test_vendor_defaults_disable_clamping_and_malformed_overrides(self):
|
||||
header = self.patched('metal--deps--indium', 'private-include/indium/completion-spin.private.hpp')
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
path = Path(folder)
|
||||
(path / 'budget.hpp').write_text(header)
|
||||
(path / 'check.cpp').write_text('''
|
||||
#include "budget.hpp"
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
int main() {
|
||||
using Indium::completionSpinBudget;
|
||||
assert(completionSpinBudget(0x1002,nullptr)==0);
|
||||
assert(completionSpinBudget(0x10de,nullptr)==2000000);
|
||||
assert(completionSpinBudget(0x1002,nullptr)==0);
|
||||
assert(completionSpinBudget(0x10de,"0")==0);
|
||||
assert(completionSpinBudget(0x1002,"15")==15000);
|
||||
assert(completionSpinBudget(0x10de,"10001")==10000000);
|
||||
const std::string huge(10000,'9');
|
||||
assert(completionSpinBudget(0x10de,huge.c_str())==10000000);
|
||||
for(const char* invalid : {"", "-1", "1ms", "18446744073709551616x"}) {
|
||||
assert(completionSpinBudget(0x10de,invalid)==2000000);
|
||||
assert(completionSpinBudget(0x1002,invalid)==0);
|
||||
}
|
||||
}
|
||||
''')
|
||||
subprocess.run(['c++','-std=c++17',str(path/'check.cpp'),'-o',str(path/'check')], check=True, capture_output=True)
|
||||
subprocess.run([str(path/'check')], check=True, capture_output=True, timeout=10)
|
||||
|
||||
def test_polling_preserves_idle_blocking_callbacks_and_timeout_budget(self):
|
||||
source = self.patched('metal--deps--indium', 'src/indium/device.cpp')
|
||||
poll = sources.function(source, 'Indium::PrivateDevice::pollEvents')
|
||||
harness = '''
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
using VkSemaphore = int;
|
||||
struct VkSemaphoreWaitInfo {
|
||||
int sType, flags;
|
||||
size_t semaphoreCount;
|
||||
const VkSemaphore* pSemaphores;
|
||||
const uint64_t* pValues;
|
||||
};
|
||||
constexpr int VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO=1, VK_SEMAPHORE_WAIT_ANY_BIT=1;
|
||||
constexpr int VK_SUCCESS=0, VK_TIMEOUT=1;
|
||||
static int counterQueries, waitResult=VK_TIMEOUT;
|
||||
static uint64_t lastTimeout;
|
||||
static bool ready;
|
||||
namespace DynamicVK {
|
||||
int vkGetSemaphoreCounterValue(int, int semaphore, uint64_t* count) {
|
||||
++counterQueries; *count=ready && semaphore==2 ? 1 : 0; return VK_SUCCESS;
|
||||
}
|
||||
int vkWaitSemaphores(int, const VkSemaphoreWaitInfo*, uint64_t timeout) {
|
||||
lastTimeout=timeout; return waitResult;
|
||||
}
|
||||
}
|
||||
namespace Indium {
|
||||
class PrivateDevice {
|
||||
public:
|
||||
std::mutex _pollingMutex, _eventLoopMutex;
|
||||
int _device=0;
|
||||
uint64_t _completionSpinNanoseconds=1000000;
|
||||
std::vector<VkSemaphore> _eventLoopSemaphores={1};
|
||||
std::vector<uint64_t> _eventLoopWaitValues={1};
|
||||
std::vector<std::function<void()>> _eventLoopCallbacks={nullptr};
|
||||
void pollEvents(uint64_t timeoutNanoseconds);
|
||||
};
|
||||
}
|
||||
''' + poll + '''
|
||||
int main() {
|
||||
Indium::PrivateDevice device;
|
||||
device.pollEvents(2000000);
|
||||
assert(counterQueries==0 && lastTimeout==2000000);
|
||||
int callbacks=0;
|
||||
device._eventLoopSemaphores.push_back(2);
|
||||
device._eventLoopWaitValues.push_back(1);
|
||||
device._eventLoopCallbacks.push_back([&]{++callbacks;});
|
||||
device.pollEvents(2000000);
|
||||
assert(counterQueries>0 && lastTimeout<2000000 && callbacks==0);
|
||||
ready=true; waitResult=VK_SUCCESS;
|
||||
device.pollEvents(2000000);
|
||||
assert(lastTimeout==0 && callbacks==1);
|
||||
assert(device._eventLoopSemaphores.size()==1);
|
||||
counterQueries=0; waitResult=VK_TIMEOUT;
|
||||
device._eventLoopSemaphores.push_back(2);
|
||||
device._eventLoopWaitValues.push_back(1);
|
||||
device._eventLoopCallbacks.push_back(nullptr);
|
||||
device.pollEvents(0);
|
||||
assert(counterQueries==0 && lastTimeout==0);
|
||||
device._completionSpinNanoseconds=0;
|
||||
device.pollEvents(2000000);
|
||||
assert(counterQueries==0 && lastTimeout==2000000);
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
path = Path(folder)
|
||||
(path / 'poll.cpp').write_text(harness)
|
||||
subprocess.run(['c++', '-std=c++17', '-pthread', str(path/'poll.cpp'), '-o', str(path/'poll')],
|
||||
check=True, capture_output=True)
|
||||
subprocess.run([str(path/'poll')], check=True, capture_output=True, timeout=10)
|
||||
@@ -38,6 +38,7 @@ int main() {
|
||||
// No NSApplication, window, graphics context, GPU work, or published IOSurface.
|
||||
// Objects intentionally live until process exit; the sentinel is never passed to CGL.
|
||||
[NSAutoreleasePool new];
|
||||
unsetenv("RCLOL_GPU_DIRECT_PRESENT");
|
||||
int dimensions[]={16,16,4,64};
|
||||
const void* keys[]={kIOSurfaceWidth,kIOSurfaceHeight,kIOSurfaceBytesPerElement,kIOSurfaceBytesPerRow};
|
||||
const void* values[4];
|
||||
@@ -77,6 +78,24 @@ int main() {
|
||||
[surfaceOnly render];
|
||||
printf("surface_only_shortcut_enabled: renderCalls=%u expected=0\n",renderCalls);
|
||||
if(renderCalls!=0) return 6;
|
||||
// Partial IOSurfaces bypass local rendering only in CPU-readback mode.
|
||||
int partialDimensions[]={8,8,4,32};
|
||||
for(unsigned i=0;i<4;++i) values[i]=CFNumberCreate(NULL,kCFNumberIntType,&partialDimensions[i]);
|
||||
CFDictionaryRef partialProperties=CFDictionaryCreate(NULL,keys,values,4,&kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks);
|
||||
IOSurfaceRef partial=IOSurfaceCreate(partialProperties);
|
||||
if(!partial) return 8;
|
||||
RenderPolicySurfaceLayer* partialLayer=[[RenderPolicySurfaceLayer alloc] initWithSurface:partial];
|
||||
RenderPolicyContext* partialContext=[[RenderPolicyContext alloc] initWithRoot:partialLayer];
|
||||
renderCalls=0;
|
||||
[partialContext render];
|
||||
if(renderCalls!=0) return 9;
|
||||
setenv("RCLOL_GPU_DIRECT_PRESENT","1",1);
|
||||
[partialContext render];
|
||||
if(renderCalls!=1) return 10;
|
||||
unsetenv("DARLING_CA_SKIP_LOCAL_IOSURFACE_RENDER");
|
||||
unsetenv("RCLOL_GPU_DIRECT_PRESENT");
|
||||
[partialContext render];
|
||||
if(renderCalls!=2) return 11;
|
||||
printf("PASS: mixed-tree Metal preparation survives IOSurface shortcut\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
"revision": "60ba801decee7a00782f74f6be4c8ffb013f79ff",
|
||||
"repository": "https://github.com/darlinghq/darling.git",
|
||||
"patch": "patches/darling.patch",
|
||||
"modified_files": 88,
|
||||
"modified_files": 89,
|
||||
"new_files": [
|
||||
"src/CoreAudio/CoreAudio/AudioConverterShim.c",
|
||||
"src/CoreAudio/AudioTrace.h",
|
||||
@@ -307,7 +307,8 @@
|
||||
"private-include/indium/submission-batch.private.hpp",
|
||||
"private-include/indium/submit-batch.private.hpp",
|
||||
"private-include/indium/submit-thread.private.hpp",
|
||||
"private-include/indium/waits.private.hpp"
|
||||
"private-include/indium/waits.private.hpp",
|
||||
"private-include/indium/completion-spin.private.hpp"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user