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
+166 -20

No files matched your search

+26 -4
View File
@@ -137,10 +137,32 @@ FP16 baselines, max absolute/relative error 0. Frame age and shim CPU image
copy counts remain zero. Add `-BarrierMode enhanced` to the packed-run command copy counts remain zero. Add `-BarrierMode enhanced` to the packed-run command
below to reproduce. The explicit harness can also supply layout metadata. below to reproduce. The explicit harness can also supply layout metadata.
Tracking currently observes the evaluated list's barriers. A texture whose Tracking now also publishes explicit ordinary texture state/layout metadata
state was established only in a different list uses the caller-supplied state in the actual queue submission order. Each split segment retains its own
or the legacy NGX default; queue-wide state inheritance remains an integration resource-state snapshot and COM references. An Evaluate input with no barrier
gate. Unknown pre-initialization indirect signatures are still rejected. in its own list resolves the most recently submitted state at the callback,
after preceding lists in the batch. The metadata belongs to the resource, with
no global pointer cache or ownership cycle. Legacy buffer/simultaneous-access
decay is not treated as persistent texture state. Unknown states retain the
explicit caller metadata or NGX legacy default and are logged as tracked=0.
ExecuteCommandLists, Signal and Wait on one queue use a queue-owned submission
mutex: another thread cannot insert a submission/fence into prefix -> DLSS ->
suffix. Internal callback operations bypass that lock while the outer batch
owns it. The lock metadata has no reference back to its owning queue.
The backend now installs 83 public-method hooks.
`optiscaler-m-inherited-final.zip` passes four complete enhanced-layout M frames
with the transitions recorded only in predecessor A, none in the evaluated B.
`optiscaler-k-inherited.zip` covers legacy state inheritance. Both have all
five planes tracked=1 on all frames (M layouts 21/20; K states 0x40/0x8), full
native network hits, zero NaN/Inf and exact RGB baseline agreement, max
absolute/relative error 0. Full build/CTest still pass 16/16
(`inherited-queue-final-ctest.log`). Use `-BarrierMode inherited-enhanced` or
`inherited-legacy` to reproduce. Tracking still requires observing creation
or a transition after hook installation; initial enhanced states outside
that observation remain a game gate. Unknown pre-initialization indirect
signatures are still rejected.
OptiScaler's default restore hotfixes are false; integration will keep OptiScaler's default restore hotfixes are false; integration will keep
RestoreComputeSignature, RestoreGraphicSignature and ExtendedStateRestore RestoreComputeSignature, RestoreGraphicSignature and ExtendedStateRestore
false because their original-method trampolines can bypass logical-list routing. false because their original-method trampolines can bypass logical-list routing.
+1 -1
View File
@@ -17,7 +17,7 @@ param(
[switch]$CommandListBackend, [switch]$CommandListBackend,
[switch]$CaptureExceptions, [switch]$CaptureExceptions,
[ValidateSet('baseline','packed','unorm')][string]$PixelProfile = 'baseline', [ValidateSet('baseline','packed','unorm')][string]$PixelProfile = 'baseline',
[ValidateSet('legacy','enhanced')][string]$BarrierMode = 'legacy', [ValidateSet('legacy','enhanced','inherited-legacy','inherited-enhanced')][string]$BarrierMode = 'legacy',
[int]$Iterations = 32, [int]$Iterations = 32,
[int]$TimeoutSeconds = 180 [int]$TimeoutSeconds = 180
) )
+94 -7
View File
@@ -15,6 +15,22 @@ std::string last_error;
bool installed = false; bool installed = false;
struct Event { UINT metadata; std::vector<uint8_t> data; }; struct Event { UINT metadata; std::vector<uint8_t> data; };
using State = std::function<void(ID3D12GraphicsCommandList*)>; 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 { struct Segment {
// The application's original list is a weak pointer; holding it in its // 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. // own routing map would prevent final Release and leak the entire feature.
@@ -22,6 +38,7 @@ struct Segment {
ComPtr<ID3D12GraphicsCommandList> owner; ComPtr<ID3D12GraphicsCommandList> owner;
ComPtr<ID3D12CommandAllocator> allocator; ComPtr<ID3D12CommandAllocator> allocator;
Boundary boundary; Boundary boundary;
ResourceStates resource_states;
}; };
struct Recording { struct Recording {
std::recursive_mutex mutex; std::recursive_mutex mutex;
@@ -31,7 +48,7 @@ struct Recording {
std::vector<Segment> segments; std::vector<Segment> segments;
std::vector<State> state; std::vector<State> state;
std::vector<Event> events; std::vector<Event> events;
std::unordered_map<ID3D12Resource*, ResourceAccess> resource_states; ResourceStates resource_states;
unsigned active_queries = 0; unsigned active_queries = 0;
bool render_pass = false, predication = false, indirect_state_unknown = false, closed = false; bool render_pass = false, predication = false, indirect_state_unknown = false, closed = false;
explicit Recording(ID3D12GraphicsCommandList* list) : original(list) {} 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 (access.recording) for (UINT i = 0; i < barrier_count; ++i)
if (barriers[i].Type == D3D12_RESOURCE_BARRIER_TYPE_TRANSITION && if (barriers[i].Type == D3D12_RESOURCE_BARRIER_TYPE_TRANSITION &&
(barriers[i].Transition.Subresource == 0 || barriers[i].Transition.Subresource == D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES)) { (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 = ResourceAccess(barriers[i].Transition.StateAfter);
state.pending_split = barriers[i].Flags == D3D12_RESOURCE_BARRIER_FLAG_BEGIN_ONLY; 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) : const bool containsZero = range.NumMipLevels == 0 ? (range.IndexOrFirstMipLevel == 0 || range.IndexOrFirstMipLevel == UINT32_MAX) :
range.IndexOrFirstMipLevel == 0 && range.FirstArraySlice == 0 && range.FirstPlane == 0; range.IndexOrFirstMipLevel == 0 && range.FirstArraySlice == 0 && range.FirstPlane == 0;
if (containsZero) { 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.layout = barrier.LayoutAfter; state.access = barrier.AccessAfter; state.sync = barrier.SyncAfter;
state.pending_split = barrier.SyncAfter == D3D12_BARRIER_SYNC_SPLIT; 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*); using ExecuteFn = void(STDMETHODCALLTYPE*)(ID3D12CommandQueue*, UINT, ID3D12CommandList* const*);
ExecuteFn original_execute = nullptr; 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) { void complete(ID3D12CommandQueue* queue) {
ComPtr<ID3D12Device> device; dx(queue->GetDevice(IID_PPV_ARGS(device.GetAddressOf())), "Queue device(submit drain)"); 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)"); 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; } if (internal) { original_execute(queue, count, lists); return; }
std::vector<std::shared_ptr<Recording>> owners; std::vector<std::shared_ptr<Recording>> owners;
std::vector<ID3D12CommandList*> pending; std::vector<ID3D12CommandList*> pending;
std::vector<ResourceStates> pending_states;
InternalScope scope; 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; bool boundaries = false;
try { try {
auto queueLock = submission_lock(queue);
std::lock_guard<std::recursive_mutex> ordered(queueLock->mutex);
for (UINT i = 0; i < count; ++i) { for (UINT i = 0; i < count; ++i) {
std::shared_ptr<Recording> recording; std::shared_ptr<Recording> recording;
{ std::lock_guard<std::mutex> lock(map_mutex); { std::lock_guard<std::mutex> lock(map_mutex);
auto found = recordings.find(static_cast<ID3D12GraphicsCommandList*>(lists[i])); auto found = recordings.find(static_cast<ID3D12GraphicsCommandList*>(lists[i]));
if (found != recordings.end()) recording = found->second; } 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); 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"); if (!recording->closed) throw std::runtime_error("Logical command list submitted before Close");
owners.push_back(recording); boundaries = true; owners.push_back(recording); boundaries = true;
for (const auto& segment : recording->segments) { 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); segment.boundary(queue);
} }
pending.push_back(recording->current.Get()); pending.push_back(recording->current.Get());
pending_states.push_back(recording->resource_states);
} }
flush(); flush();
if (boundaries) complete(queue); 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) { ResourceAccess resource_access(ID3D12GraphicsCommandList* list, ID3D12Resource* resource, ResourceAccess fallback) {
auto recording = get_recording(list, false); auto recording = get_recording(list, false);
fallback.inherited = true;
if (!recording) return fallback; if (!recording) return fallback;
std::lock_guard<std::recursive_mutex> lock(recording->mutex); std::lock_guard<std::recursive_mutex> lock(recording->mutex);
auto found = recording->resource_states.find(resource); 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) { void install(ID3D12Device* device) {
std::lock_guard<std::mutex> lock(installation_mutex); std::lock_guard<std::mutex> lock(installation_mutex);
@@ -405,6 +489,8 @@ void install(ID3D12Device* device) {
// Queue inherits IUnknown, Object, DeviceChild; UpdateTileMappings, // Queue inherits IUnknown, Object, DeviceChild; UpdateTileMappings,
// CopyTileMappings precede ExecuteCommandLists in the public SDK. // 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[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); 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_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"); 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"); ComPtr<ID3D12Device> device; dx(list->GetDevice(IID_PPV_ARGS(device.GetAddressOf())), "Boundary device");
Segment segment; segment.list = access.target(); segment.owner = recording.current; Segment segment; segment.list = access.target(); segment.owner = recording.current;
segment.allocator = recording.current_allocator; segment.boundary = std::move(callback); segment.allocator = recording.current_allocator; segment.boundary = std::move(callback);
segment.resource_states = recording.resource_states;
ComPtr<ID3D12CommandAllocator> nextAllocator; ComPtr<ID3D12GraphicsCommandList> next; ComPtr<ID3D12CommandAllocator> nextAllocator; ComPtr<ID3D12GraphicsCommandList> next;
dx(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(nextAllocator.GetAddressOf())), "Suffix allocator"); 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"); dx(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, nextAllocator.Get(), nullptr, IID_PPV_ARGS(next.GetAddressOf())), "Suffix command list");
@@ -700,3 +700,5 @@ static void attach_recording_methods(void** table, unsigned version) {
static constexpr unsigned device_signature_slot = 41; 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(); size_t live_recordings();
D3D12_RESOURCE_STATES resource_state(ID3D12GraphicsCommandList* list, ID3D12Resource* resource, D3D12_RESOURCE_STATES fallback); D3D12_RESOURCE_STATES resource_state(ID3D12GraphicsCommandList* list, ID3D12Resource* resource, D3D12_RESOURCE_STATES fallback);
ResourceAccess resource_access(ID3D12GraphicsCommandList* list, ID3D12Resource* resource, ResourceAccess 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. // Internal copies and callback submissions must bypass logical list routing.
struct InternalScope { struct InternalScope {
bool previous; 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()); const std::filesystem::path directory(diagDir ? wide(diagDir) : std::filesystem::current_path().wstring());
std::filesystem::create_directories(directory); std::filesystem::create_directories(directory);
const bool commandBackend = args.interop_mode == "command-list"; 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"); 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()); 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"); 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; dst.pResource = texture.image.Get(); dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr); list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
transition(list.Get(), texture.image.Get(), D3D12_RESOURCE_STATE_COPY_DEST, 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); (enhanced || inherited) ? 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); 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 (!commandBackend) { queue->ExecuteCommandLists(1, producer); drain(); }
if (args.interop_mode == "images") { if (args.interop_mode == "images") {
CudaApi cuda(args.cuda_dll); cuda.check(cuda.cuInit(0), "cuInit(image interop)"); 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<ID3D12GraphicsCommandList> predecessor;
ComPtr<ID3D12CommandAllocator> predecessorAllocator; ComPtr<ID3D12CommandAllocator> predecessorAllocator;
if (commandBackend) { 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 // Keep initial synthetic producers unsubmitted until the first
// batch, before the list which contains the NGX evaluation. // batch, before the list which contains the NGX evaluation.
predecessor = list; predecessorAllocator = allocator; predecessor = list; predecessorAllocator = allocator;
@@ -230,7 +243,7 @@ int main(int argc, char** argv) {
d4r_ngx_set_int(parameters, "Reset", frame == 0 ? 1 : 0); d4r_ngx_set_int(parameters, "Reset", frame == 0 ? 1 : 0);
if (commandBackend) { if (commandBackend) {
dx(allocator->Reset(), "Reset(game-style allocator)"); dx(list->Reset(allocator.Get(), nullptr), "Reset(game-style list)"); 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*); 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"); 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 // 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 { struct ResourceAccess {
D3D12_RESOURCE_STATES legacy = D3D12_RESOURCE_STATE_COMMON; 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_LAYOUT layout = D3D12_BARRIER_LAYOUT_COMMON;
D3D12_BARRIER_ACCESS access = D3D12_BARRIER_ACCESS_COMMON; D3D12_BARRIER_ACCESS access = D3D12_BARRIER_ACCESS_COMMON;
D3D12_BARRIER_SYNC sync = D3D12_BARRIER_SYNC_ALL; 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 { struct Snapshot {
void* parameters = d4r_ngx_parameters_create(); void* parameters = d4r_ngx_parameters_create();
std::vector<d4r::win::ComPtr<ID3D12Resource>> resources; std::vector<d4r::win::ComPtr<ID3D12Resource>> resources;
d4r::win::ResourceAccess access[5];
~Snapshot() { if (parameters) d4r_ngx_parameters_destroy(parameters); } ~Snapshot() { if (parameters) d4r_ngx_parameters_destroy(parameters); }
}; };
auto snapshot = std::make_shared<Snapshot>(); 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); 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], 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)); 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"); 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)); d4r_ngx_set_uint(snapshot->parameters, states[i], unsigned(state.legacy));
const std::string prefix(states[i]); 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", 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)); 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; return 1u;
}); });
} }
+1 -1
View File
@@ -145,7 +145,7 @@ struct Args {
pixel_profile = value; pixel_profile = value;
} }
else if (key == "--barrier-mode") { 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; barrier_mode = value;
} }
else if (key == "--ngx-core") ngx_core = 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))) 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_signature_slot = {fields.index("CreateCommandSignature")};')
lines.append(f'static constexpr unsigned device_command_list_slot = {fields.index("CreateCommandList")};') 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) return '\n'.join(lines)