Skip to content

Commit 85418b5

Browse files
Merge branch 'ref-count-buffers' into 'main'
[REMIX-5807] Refactor the buffer cache update to not require per-frame work. See merge request lightspeedrtx/dxvk-remix-nv!2251
2 parents 26af660 + 4f8239a commit 85418b5

7 files changed

Lines changed: 283 additions & 130 deletions

File tree

src/dxvk/dxvk_stats.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ namespace dxvk {
3030
// NV-DXVK begin: RTX Remix counters
3131
CmdTraceRaysCalls, ///< Number of traceRays calls
3232
RtxBlasCount, ///< Number of unique BLAS's in the scene/geometry cache
33-
RtxBufferCount, ///< Number of unique buffers being tracked for RT rendering
33+
RtxBufferCount, ///< Number of unique (buffer, offset, length) slices tracked for RT rendering.
34+
///< Interleaved attributes share one slot; slots are retained until the
35+
///< owning BlasEntry is GC'd, so this is not a per-frame peak.
3436
RtxTextureCount, ///< Number of unique textures being tracked for RT rendering
3537
RtxReplacementTextureCount, ///< Number of replacement textures currently used by at least one live instance
3638
RtxInstanceCount, ///< Number of surfaces and TLAS instance nodes in the scene

src/dxvk/rtx_render/rtx_instance_manager.cpp

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021-2023, NVIDIA CORPORATION. All rights reserved.
2+
* Copyright (c) 2021-2026, NVIDIA CORPORATION. All rights reserved.
33
*
44
* Permission is hereby granted, free of charge, to any person obtaining a
55
* copy of this software and associated documentation files (the "Software"),
@@ -164,6 +164,30 @@ namespace dxvk {
164164

165165
void RtInstance::setBlas(BlasEntry& blas) {
166166
m_linkedBlas = &blas;
167+
syncBufferIndicesFromBlas();
168+
}
169+
170+
void RtInstance::syncBufferIndicesFromBlas() {
171+
if (m_linkedBlas == nullptr) {
172+
return;
173+
}
174+
const RaytraceGeometry& geo = m_linkedBlas->modifiedGeometryData;
175+
surface.positionBufferIndex = geo.positionBufferIndex;
176+
surface.positionOffset = geo.positionBuffer.offsetFromSlice();
177+
surface.positionStride = geo.positionBuffer.stride();
178+
surface.normalBufferIndex = geo.normalBufferIndex;
179+
surface.normalOffset = geo.normalBuffer.offsetFromSlice();
180+
surface.normalStride = geo.normalBuffer.stride();
181+
surface.normalFormat = geo.normalBuffer.vertexFormat();
182+
surface.color0BufferIndex = geo.color0BufferIndex;
183+
surface.color0Offset = geo.color0Buffer.offsetFromSlice();
184+
surface.color0Stride = geo.color0Buffer.stride();
185+
surface.texcoordBufferIndex = geo.texcoordBufferIndex;
186+
surface.texcoordOffset = geo.texcoordBuffer.offsetFromSlice();
187+
surface.texcoordStride = geo.texcoordBuffer.stride();
188+
surface.previousPositionBufferIndex = geo.previousPositionBufferIndex;
189+
surface.indexBufferIndex = geo.indexBufferIndex;
190+
surface.indexStride = geo.indexBuffer.stride();
167191
}
168192

169193
void RtInstance::copyInstanceDataFrom(const RtInstance& src) {
@@ -228,10 +252,14 @@ namespace dxvk {
228252
m_vkInstance.transform = savedVkTransform;
229253
}
230254

255+
// Clones are not linked into BlasEntry::m_linkedInstances (see createInstanceCopy), so
256+
// updateBufferCache's push never reaches them. Re-derive from the BLAS rather than relying on
257+
// the reference instance having been refreshed first.
258+
syncBufferIndicesFromBlas();
259+
231260
// Mark dirty so the incremental BLAS cache treats this instance as changed.
232261
m_blasDirty = true;
233262
m_billboardGeometryDirty = true;
234-
235263
}
236264

237265
void RtInstance::onTransformChanged() {
@@ -922,26 +950,13 @@ namespace dxvk {
922950
m_instances.push_back(newInstance);
923951
notifySceneChanged();
924952

925-
return newInstance;
926-
}
953+
// Renderer-created clones (view model, player model, ray-portal virtual instances) deliberately
954+
// skip onInstanceAdded, so BlasEntry::linkInstance is never called for them and
955+
// updateBufferCache's propagation will not reach them. Derive the indices straight from the BLAS
956+
// instead of trusting the indices copied from the reference instance.
957+
newInstance->syncBufferIndicesFromBlas();
927958

928-
void InstanceManager::processInstanceBuffers(const BlasEntry& blas, RtInstance& currentInstance) const {
929-
currentInstance.surface.positionBufferIndex = blas.modifiedGeometryData.positionBufferIndex;
930-
currentInstance.surface.positionOffset = blas.modifiedGeometryData.positionBuffer.offsetFromSlice();
931-
currentInstance.surface.positionStride = blas.modifiedGeometryData.positionBuffer.stride();
932-
currentInstance.surface.normalBufferIndex = blas.modifiedGeometryData.normalBufferIndex;
933-
currentInstance.surface.normalOffset = blas.modifiedGeometryData.normalBuffer.offsetFromSlice();
934-
currentInstance.surface.normalStride = blas.modifiedGeometryData.normalBuffer.stride();
935-
currentInstance.surface.normalFormat = blas.modifiedGeometryData.normalBuffer.vertexFormat();
936-
currentInstance.surface.color0BufferIndex = blas.modifiedGeometryData.color0BufferIndex;
937-
currentInstance.surface.color0Offset = blas.modifiedGeometryData.color0Buffer.offsetFromSlice();
938-
currentInstance.surface.color0Stride = blas.modifiedGeometryData.color0Buffer.stride();
939-
currentInstance.surface.texcoordBufferIndex = blas.modifiedGeometryData.texcoordBufferIndex;
940-
currentInstance.surface.texcoordOffset = blas.modifiedGeometryData.texcoordBuffer.offsetFromSlice();
941-
currentInstance.surface.texcoordStride = blas.modifiedGeometryData.texcoordBuffer.stride();
942-
currentInstance.surface.previousPositionBufferIndex = blas.modifiedGeometryData.previousPositionBufferIndex;
943-
currentInstance.surface.indexBufferIndex = blas.modifiedGeometryData.indexBufferIndex;
944-
currentInstance.surface.indexStride = blas.modifiedGeometryData.indexBuffer.stride();
959+
return newInstance;
945960
}
946961

947962
// Returns true if the instance was modified
@@ -1048,8 +1063,6 @@ namespace dxvk {
10481063
if (isFirstUpdateThisFrame || overridePreviousCameraUpdate) {
10491064

10501065
if (isFirstUpdateThisFrame) {
1051-
processInstanceBuffers(blas, currentInstance);
1052-
10531066
currentInstance.m_materialType = materialData->getType();
10541067

10551068
const XXH64_hash_t materialInstanceHash = materialData->getHash();
@@ -1139,7 +1152,14 @@ namespace dxvk {
11391152
|| currentInstance.testCategoryFlags(InstanceCategories::Particle)
11401153
|| currentInstance.testCategoryFlags(InstanceCategories::WorldUI);
11411154

1142-
hasPreviousPositions = blas.modifiedGeometryData.previousPositionBuffer.defined() && !isMotionUnstable;
1155+
// previousPositionBuffer is only re-pointed at historyBuffer[1] by processGeometryInfo on a
1156+
// kUpdateBVH frame. On any later frame it still holds that older slice - e.g. a preserved
1157+
// BLAS, or the kUpdateInstance early-out in onSceneObjectUpdated when a sibling draw already
1158+
// touched this BlasEntry. Gate on frameLastUpdated so stale vertices never feed motion vectors.
1159+
const bool previousPositionsValidThisFrame = blas.frameLastUpdated == m_device->getCurrentFrameId();
1160+
hasPreviousPositions = previousPositionsValidThisFrame
1161+
&& blas.modifiedGeometryData.previousPositionBuffer.defined()
1162+
&& !isMotionUnstable;
11431163
const bool isFirstUpdateAfterCreation = currentInstance.isCreatedThisFrame(m_device->getCurrentFrameId()) && isFirstUpdateThisFrame;
11441164

11451165
// Note: objectToView is aliased on updates, since findSimilarInstance() doesn't discern it

src/dxvk/rtx_render/rtx_instance_manager.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021-2023, NVIDIA CORPORATION. All rights reserved.
2+
* Copyright (c) 2021-2026, NVIDIA CORPORATION. All rights reserved.
33
*
44
* Permission is hereby granted, free of charge, to any person obtaining a
55
* copy of this software and associated documentation files (the "Software"),
@@ -100,9 +100,14 @@ class RtInstance {
100100
// Leave it false before relative transforms, such as portal teleports.
101101
void updateFromReference(const RtInstance& src, bool preserveTransforms = true);
102102

103-
// Bind a BLAS object to this instance
103+
// Bind a BLAS object to this instance and sync buffer indices/strides from its geometry data.
104104
void setBlas(BlasEntry& blas);
105105

106+
// Syncs surface buffer indices and strides from the currently bound BLAS.
107+
// Called by setBlas() on initial bind or re-link, and by updateBufferCache()
108+
// when geometry buffer slots change mid-scene.
109+
void syncBufferIndicesFromBlas();
110+
106111
// Sets current and previous transforms explicitly
107112
bool teleport(const Matrix4& objectToWorld);
108113
bool teleport(const Matrix4& objectToWorld, const Matrix4& prevObjectToWorld);
@@ -357,9 +362,6 @@ class InstanceManager : public CommonDeviceObject {
357362
// Binds a raytracing material to the specified instance.
358363
void bindMaterial(RtInstance& instance, const RtSurfaceMaterial& material);
359364

360-
// Copies buffer indices from the BlasEntry's geometry data to the instance's surface.
361-
void processInstanceBuffers(const BlasEntry& blas, RtInstance& currentInstance) const;
362-
363365
// Per-frame finalization shared by the dynamic and preserve paths:
364366
// re-registers the player-model / view-model candidate lists (cleared every onFrameEnd) and
365367
// dispatches onInstanceUpdated to listeners.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a
5+
* copy of this software and associated documentation files (the "Software"),
6+
* to deal in the Software without restriction, including without limitation
7+
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
8+
* and/or sell copies of the Software, and to permit persons to whom the
9+
* Software is furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17+
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20+
* DEALINGS IN THE SOFTWARE.
21+
*/
22+
#pragma once
23+
24+
#include <cassert>
25+
#include <cstdint>
26+
#include <unordered_map>
27+
#include <vector>
28+
29+
#include "../util/util_struct_hash.h"
30+
31+
// Retained buffer registration table. Each distinct (buffer ptr, offset, length) triple
32+
// gets a stable slot index that persists across frames. acquire() is ref-counted; the slot
33+
// is returned to a free-list when the last release() fires. Slots left in the table by
34+
// free-listed entries are default-constructed (not defined()), which causes
35+
// BindlessResourceManager to write a dummy descriptor for that slot — safe.
36+
template<typename BufferType>
37+
class RetainedBufferTable {
38+
struct Key {
39+
void* ptr;
40+
size_t offset;
41+
size_t length;
42+
bool operator==(const Key& o) const noexcept {
43+
return ptr == o.ptr && offset == o.offset && length == o.length;
44+
}
45+
};
46+
struct KeyHash {
47+
size_t operator()(const Key& k) const noexcept {
48+
return dxvk::hashStructByMemory<Key, &Key::ptr, &Key::offset, &Key::length>(k);
49+
}
50+
};
51+
52+
static Key makeKey(const BufferType& buffer) {
53+
return { static_cast<void*>(buffer.buffer().ptr()), buffer.offset(), buffer.length() };
54+
}
55+
56+
public:
57+
// Register buffer and return its stable slot index. buffer must be defined().
58+
// Increments the ref count if the buffer is already registered.
59+
uint32_t acquire(const BufferType& buffer) {
60+
assert(buffer.defined());
61+
auto [it, inserted] = m_indexMap.emplace(makeKey(buffer), uint32_t(0));
62+
if (!inserted) {
63+
++m_refCounts[it->second];
64+
return it->second;
65+
}
66+
uint32_t slot;
67+
if (!m_freeSlots.empty()) {
68+
slot = m_freeSlots.back();
69+
m_freeSlots.pop_back();
70+
m_table[slot] = buffer;
71+
m_refCounts[slot] = 1;
72+
} else {
73+
slot = static_cast<uint32_t>(m_table.size());
74+
m_table.push_back(buffer);
75+
m_refCounts.push_back(1);
76+
}
77+
it->second = slot;
78+
return slot;
79+
}
80+
81+
// Decrement the ref count for slot. Frees the slot when it reaches zero.
82+
// slot must be a value previously returned by acquire() (not a sentinel).
83+
void release(uint32_t slot) {
84+
if (slot >= m_table.size()) {
85+
assert(false && "RetainedBufferTable::release: invalid slot");
86+
return;
87+
}
88+
if (m_refCounts[slot] == 0) {
89+
assert(false && "RetainedBufferTable::release: over-release of slot");
90+
return;
91+
}
92+
if (--m_refCounts[slot] == 0) {
93+
m_indexMap.erase(makeKey(m_table[slot]));
94+
m_table[slot] = BufferType{};
95+
m_freeSlots.push_back(slot);
96+
}
97+
}
98+
99+
// Returns true if slot is still registered for exactly buffer (same buffer slice).
100+
// Returns false if slot is out of range (e.g. kSurfaceInvalidBufferIndex).
101+
bool isRegistered(uint32_t slot, const BufferType& buffer) const {
102+
if (slot >= m_table.size()) {
103+
return false;
104+
}
105+
return buffer.matches(m_table[slot]);
106+
}
107+
108+
// Reset all registrations. Only call after GPU idle (full scene clear).
109+
void clear() {
110+
m_indexMap.clear();
111+
m_table.clear();
112+
m_refCounts.clear();
113+
m_freeSlots.clear();
114+
}
115+
116+
const std::vector<BufferType>& getObjectTable() const { return m_table; }
117+
uint32_t getActiveCount() const { return static_cast<uint32_t>(m_indexMap.size()); }
118+
uint32_t getTotalCount() const { return static_cast<uint32_t>(m_table.size()); }
119+
120+
private:
121+
std::unordered_map<Key, uint32_t, KeyHash> m_indexMap;
122+
std::vector<BufferType> m_table;
123+
std::vector<uint32_t> m_refCounts;
124+
std::vector<uint32_t> m_freeSlots;
125+
};

0 commit comments

Comments
 (0)