Resolve texture states in queue order across DLSS command lists

This commit is contained in:
Codex committed 2026-10-01 16:41:52 +03:00
1 parent e14b4cb54c
commit e4c1c87b66
10 files changed
+167 -21

No files matched your search

+94 -7
View File
@@ -15,6 +15,22 @@ std::string last_error;
bool installed = false;
struct Event { UINT metadata; std::vector<uint8_t> data; };
using State = std::function<void(ID3D12GraphicsCommandList*)>;
struct TrackedResource {
ComPtr<ID3D12Resource> owner;
ResourceAccess state;
};
using ResourceStates = std::unordered_map<ID3D12Resource*, TrackedResource>;
static constexpr GUID submitted_access_tag = {0x741b92df,0x9b13,0x4fc9,{0xb0,0x02,0x2c,0xe3,0x5a,0x68,0x92,0x41}};
void publish_states(const ResourceStates& states) {
for (const auto& entry : states) {
// Buffers/simultaneous-access textures have automatic legacy state
// decay at ExecuteCommandLists completion. Only ordinary textures
// retain these explicit transitions across submission boundaries.
const auto desc = resource_desc(entry.second.owner.Get());
if (desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER || (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS)) continue;
dx(entry.second.owner->SetPrivateData(submitted_access_tag, sizeof(ResourceAccess), &entry.second.state), "Publish submitted texture access");
}
}
struct Segment {
// The application's original list is a weak pointer; holding it in its
// own routing map would prevent final Release and leak the entire feature.
@@ -22,6 +38,7 @@ struct Segment {
ComPtr<ID3D12GraphicsCommandList> owner;
ComPtr<ID3D12CommandAllocator> allocator;
Boundary boundary;
ResourceStates resource_states;
};
struct Recording {
std::recursive_mutex mutex;
@@ -31,7 +48,7 @@ struct Recording {
std::vector<Segment> segments;
std::vector<State> state;
std::vector<Event> events;
std::unordered_map<ID3D12Resource*, ResourceAccess> resource_states;
ResourceStates resource_states;
unsigned active_queries = 0;
bool render_pass = false, predication = false, indirect_state_unknown = false, closed = false;
explicit Recording(ID3D12GraphicsCommandList* list) : original(list) {}
@@ -187,7 +204,9 @@ void STDMETHODCALLTYPE hook_ResourceBarrier(ID3D12GraphicsCommandList* self, UIN
if (access.recording) for (UINT i = 0; i < barrier_count; ++i)
if (barriers[i].Type == D3D12_RESOURCE_BARRIER_TYPE_TRANSITION &&
(barriers[i].Transition.Subresource == 0 || barriers[i].Transition.Subresource == D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES)) {
auto& state = access.recording->resource_states[barriers[i].Transition.pResource];
auto& tracked = access.recording->resource_states[barriers[i].Transition.pResource];
tracked.owner = retain(barriers[i].Transition.pResource);
auto& state = tracked.state;
state = ResourceAccess(barriers[i].Transition.StateAfter);
state.pending_split = barriers[i].Flags == D3D12_RESOURCE_BARRIER_FLAG_BEGIN_ONLY;
}
@@ -201,7 +220,8 @@ void STDMETHODCALLTYPE hook_Barrier(ID3D12GraphicsCommandList* self, UINT32 barr
const bool containsZero = range.NumMipLevels == 0 ? (range.IndexOrFirstMipLevel == 0 || range.IndexOrFirstMipLevel == UINT32_MAX) :
range.IndexOrFirstMipLevel == 0 && range.FirstArraySlice == 0 && range.FirstPlane == 0;
if (containsZero) {
auto& state = access.recording->resource_states[barrier.pResource]; state.enhanced = true;
auto& tracked = access.recording->resource_states[barrier.pResource]; tracked.owner = retain(barrier.pResource);
auto& state = tracked.state; state.enhanced = true; state.inherited = false;
state.layout = barrier.LayoutAfter; state.access = barrier.AccessAfter; state.sync = barrier.SyncAfter;
state.pending_split = barrier.SyncAfter == D3D12_BARRIER_SYNC_SPLIT;
}
@@ -308,6 +328,48 @@ void STDMETHODCALLTYPE hook_ExecuteIndirect(ID3D12GraphicsCommandList* self, ID3
using ExecuteFn = void(STDMETHODCALLTYPE*)(ID3D12CommandQueue*, UINT, ID3D12CommandList* const*);
ExecuteFn original_execute = nullptr;
// Queue-owned synchronization metadata has no reference back to the queue.
// It serializes a split submission against other threads using the same queue.
static constexpr GUID submission_lock_tag = {0x6c6158ba,0x209f,0x4a09,{0x97,0x10,0xa7,0x03,0x52,0x32,0xa0,0x69}};
std::mutex queue_metadata_mutex;
struct SubmissionLock final : IUnknown {
std::atomic<ULONG> references{1};
std::recursive_mutex mutex;
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** out) override {
if (!out) return E_POINTER; *out = nullptr;
if (iid != IID_IUnknown) return E_NOINTERFACE;
*out = static_cast<IUnknown*>(this); AddRef(); return S_OK;
}
ULONG STDMETHODCALLTYPE AddRef() override { return ++references; }
ULONG STDMETHODCALLTYPE Release() override { const auto n = --references; if (!n) delete this; return n; }
};
ComPtr<SubmissionLock> submission_lock(ID3D12CommandQueue* queue) {
std::lock_guard<std::mutex> lock(queue_metadata_mutex);
ComPtr<SubmissionLock> result; UINT bytes = sizeof(IUnknown*);
if (SUCCEEDED(queue->GetPrivateData(submission_lock_tag, &bytes, result.GetAddressOf()))) return result;
result.Attach(new SubmissionLock);
dx(queue->SetPrivateDataInterface(submission_lock_tag, result.Get()), "Queue submission metadata");
return result;
}
using QueueFenceFn = HRESULT(STDMETHODCALLTYPE*)(ID3D12CommandQueue*, ID3D12Fence*, UINT64);
QueueFenceFn original_queue_signal = nullptr, original_queue_wait = nullptr;
HRESULT queue_fence(QueueFenceFn function, ID3D12CommandQueue* queue, ID3D12Fence* fence, UINT64 value) noexcept {
if (internal) return function(queue, fence, value);
try {
auto queueLock = submission_lock(queue);
std::lock_guard<std::recursive_mutex> ordered(queueLock->mutex);
return function(queue, fence, value);
} catch (const std::exception& failure) {
{ std::lock_guard<std::mutex> lock(error_mutex); last_error = failure.what(); }
std::fprintf(stderr, "D4R_QUEUE_FENCE_FAILURE %s\n", failure.what()); return E_FAIL;
}
}
HRESULT STDMETHODCALLTYPE hook_queue_signal(ID3D12CommandQueue* queue, ID3D12Fence* fence, UINT64 value) {
return queue_fence(original_queue_signal, queue, fence, value);
}
HRESULT STDMETHODCALLTYPE hook_queue_wait(ID3D12CommandQueue* queue, ID3D12Fence* fence, UINT64 value) {
return queue_fence(original_queue_wait, queue, fence, value);
}
void complete(ID3D12CommandQueue* queue) {
ComPtr<ID3D12Device> device; dx(queue->GetDevice(IID_PPV_ARGS(device.GetAddressOf())), "Queue device(submit drain)");
ComPtr<ID3D12Fence> fence; dx(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(fence.GetAddressOf())), "Queue fence(submit drain)");
@@ -323,24 +385,39 @@ void STDMETHODCALLTYPE hook_execute(ID3D12CommandQueue* queue, UINT count, ID3D1
if (internal) { original_execute(queue, count, lists); return; }
std::vector<std::shared_ptr<Recording>> owners;
std::vector<ID3D12CommandList*> pending;
std::vector<ResourceStates> pending_states;
InternalScope scope;
auto flush = [&] { if (!pending.empty()) { original_execute(queue, UINT(pending.size()), pending.data()); pending.clear(); } };
auto flush = [&] {
if (!pending.empty()) {
original_execute(queue, UINT(pending.size()), pending.data());
// Publish in the actual submitted order, never while recording.
// The callback's external fence waits for these producers on GPU.
for (const auto& states : pending_states) publish_states(states);
pending.clear(); pending_states.clear();
}
};
bool boundaries = false;
try {
auto queueLock = submission_lock(queue);
std::lock_guard<std::recursive_mutex> ordered(queueLock->mutex);
for (UINT i = 0; i < count; ++i) {
std::shared_ptr<Recording> recording;
{ std::lock_guard<std::mutex> lock(map_mutex);
auto found = recordings.find(static_cast<ID3D12GraphicsCommandList*>(lists[i]));
if (found != recordings.end()) recording = found->second; }
if (!recording || recording->segments.empty()) { pending.push_back(lists[i]); continue; }
if (!recording) { pending.push_back(lists[i]); pending_states.emplace_back(); continue; }
std::lock_guard<std::recursive_mutex> lock(recording->mutex);
if (recording->segments.empty()) {
pending.push_back(lists[i]); pending_states.push_back(recording->resource_states); continue;
}
if (!recording->closed) throw std::runtime_error("Logical command list submitted before Close");
owners.push_back(recording); boundaries = true;
for (const auto& segment : recording->segments) {
pending.push_back(segment.list); flush();
pending.push_back(segment.list); pending_states.push_back(segment.resource_states); flush();
segment.boundary(queue);
}
pending.push_back(recording->current.Get());
pending_states.push_back(recording->resource_states);
}
flush();
if (boundaries) complete(queue);
@@ -364,10 +441,17 @@ D3D12_RESOURCE_STATES resource_state(ID3D12GraphicsCommandList* list, ID3D12Reso
}
ResourceAccess resource_access(ID3D12GraphicsCommandList* list, ID3D12Resource* resource, ResourceAccess fallback) {
auto recording = get_recording(list, false);
fallback.inherited = true;
if (!recording) return fallback;
std::lock_guard<std::recursive_mutex> lock(recording->mutex);
auto found = recording->resource_states.find(resource);
return found == recording->resource_states.end() ? fallback : found->second;
return found == recording->resource_states.end() ? fallback : found->second.state;
}
ResourceAccess submitted_resource_access(ID3D12Resource* resource, ResourceAccess fallback) {
if (!resource) return fallback;
ResourceAccess submitted; UINT size = sizeof(submitted);
if (SUCCEEDED(resource->GetPrivateData(submitted_access_tag, &size, &submitted)) && size == sizeof(submitted)) return submitted;
return fallback;
}
void install(ID3D12Device* device) {
std::lock_guard<std::mutex> lock(installation_mutex);
@@ -405,6 +489,8 @@ void install(ID3D12Device* device) {
// Queue inherits IUnknown, Object, DeviceChild; UpdateTileMappings,
// CopyTileMappings precede ExecuteCommandLists in the public SDK.
attach_method(queueTable[10], reinterpret_cast<void*>(hook_execute), reinterpret_cast<void**>(&original_execute), "ExecuteCommandLists");
attach_method(queueTable[queue_signal_slot], reinterpret_cast<void*>(hook_queue_signal), reinterpret_cast<void**>(&original_queue_signal), "QueueSignal");
attach_method(queueTable[queue_wait_slot], reinterpret_cast<void*>(hook_queue_wait), reinterpret_cast<void**>(&original_queue_wait), "QueueWait");
auto deviceTable = *reinterpret_cast<void***>(device);
attach_method(deviceTable[device_command_list_slot], reinterpret_cast<void*>(hook_create_list), reinterpret_cast<void**>(&original_create_list), "CreateCommandList");
attach_method(deviceTable[device_signature_slot], reinterpret_cast<void*>(hook_signature), reinterpret_cast<void**>(&original_signature), "CreateCommandSignature");
@@ -435,6 +521,7 @@ void record_boundary(ID3D12GraphicsCommandList* list, Boundary callback) {
ComPtr<ID3D12Device> device; dx(list->GetDevice(IID_PPV_ARGS(device.GetAddressOf())), "Boundary device");
Segment segment; segment.list = access.target(); segment.owner = recording.current;
segment.allocator = recording.current_allocator; segment.boundary = std::move(callback);
segment.resource_states = recording.resource_states;
ComPtr<ID3D12CommandAllocator> nextAllocator; ComPtr<ID3D12GraphicsCommandList> next;
dx(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(nextAllocator.GetAddressOf())), "Suffix allocator");
dx(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, nextAllocator.Get(), nullptr, IID_PPV_ARGS(next.GetAddressOf())), "Suffix command list");
@@ -699,4 +699,6 @@ static void attach_recording_methods(void** table, unsigned version) {
}
static constexpr unsigned device_signature_slot = 41;
static constexpr unsigned device_command_list_slot = 12;
static constexpr unsigned device_command_list_slot = 12;
static constexpr unsigned queue_signal_slot = 14;
static constexpr unsigned queue_wait_slot = 15;
+1
View File
@@ -14,6 +14,7 @@ std::string error();
size_t live_recordings();
D3D12_RESOURCE_STATES resource_state(ID3D12GraphicsCommandList* list, ID3D12Resource* resource, D3D12_RESOURCE_STATES fallback);
ResourceAccess resource_access(ID3D12GraphicsCommandList* list, ID3D12Resource* resource, ResourceAccess fallback);
ResourceAccess submitted_resource_access(ID3D12Resource* resource, ResourceAccess fallback);
// Internal copies and callback submissions must bypass logical list routing.
struct InternalScope {
bool previous;
+18 -5
View File
@@ -19,7 +19,9 @@ int main(int argc, char** argv) {
const std::filesystem::path directory(diagDir ? wide(diagDir) : std::filesystem::current_path().wstring());
std::filesystem::create_directories(directory);
const bool commandBackend = args.interop_mode == "command-list";
const bool enhanced = args.barrier_mode == "enhanced";
const bool inherited = args.barrier_mode.rfind("inherited-", 0) == 0;
const bool enhanced = args.barrier_mode == "enhanced" || args.barrier_mode == "inherited-enhanced";
if (inherited && !commandBackend) throw std::runtime_error("Inherited state diagnostic requires the command-list backend");
if (args.interop_mode == "images" && args.pixel_profile != "baseline") throw std::runtime_error("Raw CUDA image diagnostic requires baseline formats");
std::printf("D3D12_PIXEL_PROFILE %s\n", args.pixel_profile.c_str());
if (commandBackend && _putenv_s("D4R_D3D12_COMMAND_BACKEND", "1")) throw std::runtime_error("Cannot enable command backend");
@@ -117,10 +119,10 @@ int main(int argc, char** argv) {
dst.pResource = texture.image.Get(); dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
transition(list.Get(), texture.image.Get(), D3D12_RESOURCE_STATE_COPY_DEST,
enhanced ? D3D12_RESOURCE_STATE_COMMON : i == 3 ? D3D12_RESOURCE_STATE_UNORDERED_ACCESS : D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
if (enhanced) enhancedBarrier(list.Get(), texture.image.Get(), i, true);
(enhanced || inherited) ? D3D12_RESOURCE_STATE_COMMON : i == 3 ? D3D12_RESOURCE_STATE_UNORDERED_ACCESS : D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
if (enhanced && !inherited) enhancedBarrier(list.Get(), texture.image.Get(), i, true);
}
dx(list->Close(), "Close(synthetic producers)"); ID3D12CommandList* producer[] = {list.Get()};
if (!inherited) dx(list->Close(), "Close(synthetic producers)"); ID3D12CommandList* producer[] = {list.Get()};
if (!commandBackend) { queue->ExecuteCommandLists(1, producer); drain(); }
if (args.interop_mode == "images") {
CudaApi cuda(args.cuda_dll); cuda.check(cuda.cuInit(0), "cuInit(image interop)");
@@ -218,6 +220,17 @@ int main(int argc, char** argv) {
ComPtr<ID3D12GraphicsCommandList> predecessor;
ComPtr<ID3D12CommandAllocator> predecessorAllocator;
if (commandBackend) {
if (inherited) {
// Record state only in predecessor A after the backend has
// initialized. List B's Evaluate must resolve it at submission,
// after A, and retain it across subsequent frames/resets.
for (unsigned i = 0; i < 5; ++i) {
if (enhanced) enhancedBarrier(list.Get(), textures[i].image.Get(), i, true);
else transition(list.Get(), textures[i].image.Get(), D3D12_RESOURCE_STATE_COMMON,
i == 3 ? D3D12_RESOURCE_STATE_UNORDERED_ACCESS : D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
}
dx(list->Close(), "Close tracked predecessor A");
}
// Keep initial synthetic producers unsubmitted until the first
// batch, before the list which contains the NGX evaluation.
predecessor = list; predecessorAllocator = allocator;
@@ -230,7 +243,7 @@ int main(int argc, char** argv) {
d4r_ngx_set_int(parameters, "Reset", frame == 0 ? 1 : 0);
if (commandBackend) {
dx(allocator->Reset(), "Reset(game-style allocator)"); dx(list->Reset(allocator.Get(), nullptr), "Reset(game-style list)");
if (enhanced) for (unsigned i = 0; i < 5; ++i) enhancedBarrier(list.Get(), textures[i].image.Get(), i, false);
if (enhanced && !inherited) for (unsigned i = 0; i < 5; ++i) enhancedBarrier(list.Get(), textures[i].image.Get(), i, false);
using RecordedEvaluate = unsigned(*)(ID3D12GraphicsCommandList*, void*, void*, void*);
check(shim.symbol<RecordedEvaluate>("NVSDK_NGX_D3D12_EvaluateFeature")(list.Get(), handle, parameters, nullptr), "D3D12 recorded same-frame Evaluate");
// The caller may reuse/mutate its parameter object as soon as
+1 -1
View File
@@ -67,7 +67,7 @@ inline void transition(ID3D12GraphicsCommandList* list, ID3D12Resource* resource
}
struct ResourceAccess {
D3D12_RESOURCE_STATES legacy = D3D12_RESOURCE_STATE_COMMON;
bool enhanced = false, pending_split = false;
bool enhanced = false, pending_split = false, inherited = false;
D3D12_BARRIER_LAYOUT layout = D3D12_BARRIER_LAYOUT_COMMON;
D3D12_BARRIER_ACCESS access = D3D12_BARRIER_ACCESS_COMMON;
D3D12_BARRIER_SYNC sync = D3D12_BARRIER_SYNC_ALL;
+18 -1
View File
@@ -126,6 +126,7 @@ API unsigned NVSDK_NGX_D3D12_EvaluateFeature(ID3D12GraphicsCommandList* list, co
struct Snapshot {
void* parameters = d4r_ngx_parameters_create();
std::vector<d4r::win::ComPtr<ID3D12Resource>> resources;
d4r::win::ResourceAccess access[5];
~Snapshot() { if (parameters) d4r_ngx_parameters_destroy(parameters); }
};
auto snapshot = std::make_shared<Snapshot>();
@@ -139,6 +140,7 @@ API unsigned NVSDK_NGX_D3D12_EvaluateFeature(ID3D12GraphicsCommandList* list, co
d4r_ngx_set_d3d12_resource(snapshot->parameters, names[i], resource);
const auto state = d4r::win::commands::resource_access(list, resource, d4r::win::ngx_resource_access(p, states[i],
i == 3 ? D3D12_RESOURCE_STATE_UNORDERED_ACCESS : D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE));
snapshot->access[i] = state;
if (state.pending_split) throw std::runtime_error("NGX resource has an unfinished split barrier");
d4r_ngx_set_uint(snapshot->parameters, states[i], unsigned(state.legacy));
const std::string prefix(states[i]);
@@ -148,7 +150,22 @@ API unsigned NVSDK_NGX_D3D12_EvaluateFeature(ID3D12GraphicsCommandList* list, co
std::printf("D4R_RESOURCE_ACCESS plane=%s enhanced=%u legacy=0x%x layout=%u access=0x%x\n",
names[i], state.enhanced, unsigned(state.legacy), unsigned(state.layout), unsigned(state.access));
}
d4r::win::commands::record_boundary(list, [feature, snapshot](ID3D12CommandQueue* queue) { feature->evaluate_boundary(queue, snapshot->parameters); });
d4r::win::commands::record_boundary(list, [feature, snapshot](ID3D12CommandQueue* queue) {
const char* names[] = {"Color", "Depth", "MotionVectors", "Output", "ExposureTexture"};
const char* states[] = {"D4R.Color.State", "D4R.Depth.State", "D4R.Motion.State", "D4R.Output.State", "D4R.Exposure.State"};
for (unsigned i = 0; i < 5; ++i) if (snapshot->access[i].inherited) {
const auto resolved = d4r::win::commands::submitted_resource_access(snapshot->resources[i].Get(), snapshot->access[i]);
if (resolved.pending_split) throw std::runtime_error("Inherited NGX texture has an unfinished split barrier");
const std::string prefix(states[i]);
d4r_ngx_set_uint(snapshot->parameters, states[i], unsigned(resolved.legacy));
d4r_ngx_set_uint(snapshot->parameters, (prefix + ".Enhanced").c_str(), resolved.enhanced);
d4r_ngx_set_uint(snapshot->parameters, (prefix + ".Layout").c_str(), unsigned(resolved.layout));
d4r_ngx_set_uint(snapshot->parameters, (prefix + ".Access").c_str(), unsigned(resolved.access));
std::printf("D4R_INHERITED_ACCESS plane=%s tracked=%u enhanced=%u layout=%u legacy=0x%x\n",
names[i], !resolved.inherited, resolved.enhanced, unsigned(resolved.layout), unsigned(resolved.legacy));
}
feature->evaluate_boundary(queue, snapshot->parameters);
});
return 1u;
});
}
+1 -1
View File
@@ -145,7 +145,7 @@ struct Args {
pixel_profile = value;
}
else if (key == "--barrier-mode") {
if (value != "legacy" && value != "enhanced") throw std::runtime_error("barrier-mode must be legacy or enhanced");
if (value != "legacy" && value != "enhanced" && value != "inherited-legacy" && value != "inherited-enhanced") throw std::runtime_error("Invalid barrier-mode");
barrier_mode = value;
}
else if (key == "--ngx-core") ngx_core = value;
+4
View File
@@ -121,6 +121,10 @@ def generate(source):
fields = list(dict.fromkeys(re.findall(r'\(STDMETHODCALLTYPE \*(\w+)\)', device)))
lines.append(f'static constexpr unsigned device_signature_slot = {fields.index("CreateCommandSignature")};')
lines.append(f'static constexpr unsigned device_command_list_slot = {fields.index("CreateCommandList")};')
queue = re.search(r'typedef struct ID3D12CommandQueueVtbl \{(.*?)\n\} ID3D12CommandQueueVtbl;', source, re.S)[1]
queue_fields = list(dict.fromkeys(re.findall(r'\(STDMETHODCALLTYPE \*(\w+)\)', queue)))
for name in ('Signal', 'Wait'):
lines.append(f'static constexpr unsigned queue_{name.lower()}_slot = {queue_fields.index(name)};')
return '\n'.join(lines)