Skip to content

Commit 0d088f2

Browse files
Merge branch 'ref-count-textures' into 'main'
[REMIX-5779] Refactor textures to not require per-frame keepAlive See merge request lightspeedrtx/dxvk-remix-nv!2237
2 parents 89baba8 + 1cc61d4 commit 0d088f2

7 files changed

Lines changed: 269 additions & 163 deletions

File tree

src/dxvk/dxvk_stats.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ namespace dxvk {
3232
RtxBlasCount, ///< Number of unique BLAS's in the scene/geometry cache
3333
RtxBufferCount, ///< Number of unique buffers being tracked for RT rendering
3434
RtxTextureCount, ///< Number of unique textures being tracked for RT rendering
35+
RtxReplacementTextureCount, ///< Number of replacement textures currently used by at least one live instance
3536
RtxInstanceCount, ///< Number of surfaces and TLAS instance nodes in the scene
3637
RtxSurfaceMaterialCount, ///< Number of surface materials in the scene
3738
RtxSurfaceMaterialExtensionCount, ///< Number of surface material extensions in the scene

src/dxvk/hud/dxvk_hud_item.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -813,22 +813,24 @@ namespace dxvk::hud {
813813
HudPos position) {
814814
const DxvkStatCounters counters = m_device->getStatCounters();
815815

816-
const std::string labels[] = { "# Presents:" ,
816+
const std::string labels[] = { "# Presents:" ,
817817
"# BLAS:" ,
818-
"# Buffers:" ,
819-
"# Textures:" ,
820-
"# Instances/Surfaces:" ,
821-
"# Surface Materials:" ,
818+
"# Buffers:" ,
819+
"# Textures:" ,
820+
"# Active Replacement Textures:",
821+
"# Instances/Surfaces:" ,
822+
"# Surface Materials:" ,
822823
"# Surface Material Extensions:" ,
823-
"# Volume Materials:" ,
824+
"# Volume Materials:" ,
824825
"# Lights:",
825826
"# Samplers:",
826827
"# Textures in-flight:",
827-
"# Last tex. batch (ms):"};
828+
"# Last tex. batch (ms):"};
828829
const uint64_t values[] = { counters.getCtr(DxvkStatCounter::QueuePresentCount),
829830
counters.getCtr(DxvkStatCounter::RtxBlasCount),
830831
counters.getCtr(DxvkStatCounter::RtxBufferCount),
831832
counters.getCtr(DxvkStatCounter::RtxTextureCount),
833+
counters.getCtr(DxvkStatCounter::RtxReplacementTextureCount),
832834
counters.getCtr(DxvkStatCounter::RtxInstanceCount),
833835
counters.getCtr(DxvkStatCounter::RtxSurfaceMaterialCount),
834836
counters.getCtr(DxvkStatCounter::RtxSurfaceMaterialExtensionCount),

src/dxvk/rtx_render/rtx_scene_manager.cpp

Lines changed: 128 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,9 @@ namespace dxvk {
256256

257257
// We still need to clear caches even if the scene wasn't rendered
258258
m_bufferCache.clear();
259-
m_surfaceMaterialCache.clear();
260259
m_preCreationSurfaceMaterialMap.clear();
261-
m_surfaceMaterialExtensionCache.clear();
262260
m_volumeMaterialCache.clear();
263-
261+
264262
// Clear ReplacementInstances first: their destructors call clear() which
265263
// accesses prims[] to mark entities for GC and clear back-pointers.
266264
// Entities must still be alive at this point.
@@ -278,7 +276,34 @@ namespace dxvk {
278276
// map, not the vectors, so a bulk reset must drop the cache wholesale.
279277
m_accelManager.clear();
280278

279+
// Instance destruction fires onInstanceDestroyed -> releaseSurfaceMaterial for every
280+
// game-submitted instance, decrementing texture ref counts and feature counts.
281+
// The surface material cache must still be valid here so those releases are not
282+
// silently skipped by the bounds check in releaseSurfaceMaterial.
281283
m_instanceManager.clear();
284+
285+
// After all instances are destroyed their retain/release pairs must be balanced.
286+
// If any count is non-zero here there is a retain/release mismatch.
287+
if (m_activePOMCount != 0) {
288+
Logger::err(str::format("[RTX] SceneManager::clear: POM count is ", m_activePOMCount, " after instance destruction (retain/release mismatch)"));
289+
assert(false && "SceneManager::clear: POM count non-zero after instance destruction");
290+
m_activePOMCount = 0;
291+
}
292+
if (m_sssCount != 0) {
293+
Logger::err(str::format("[RTX] SceneManager::clear: SSS count is ", m_sssCount, " after instance destruction (retain/release mismatch)"));
294+
assert(false && "SceneManager::clear: SSS count non-zero after instance destruction");
295+
m_sssCount = 0;
296+
}
297+
if (m_thinOpaqueCount != 0) {
298+
Logger::err(str::format("[RTX] SceneManager::clear: thin-opaque count is ", m_thinOpaqueCount, " after instance destruction (retain/release mismatch)"));
299+
assert(false && "SceneManager::clear: thin-opaque count non-zero after instance destruction");
300+
m_thinOpaqueCount = 0;
301+
}
302+
textureManager.assertAllRefCountsZero();
303+
304+
// Now safe to clear material caches; all ref counts have been released.
305+
m_surfaceMaterialCache.clear();
306+
m_surfaceMaterialExtensionCache.clear();
282307
m_lightManager.clear();
283308
m_graphManager.clear();
284309
m_rayPortalManager.clear();
@@ -299,7 +324,7 @@ namespace dxvk {
299324
ScopedCpuProfileZone();
300325

301326
// BlasEntry GC: remove entries not touched recently.
302-
// Only GC entries with no linked instances instances still reference the BlasEntry
327+
// Only GC entries with no linked instances -- instances still reference the BlasEntry
303328
// for TLAS build, and destroying it would cause a one-frame visibility gap.
304329
if (m_device->getCurrentFrameId() > RtxOptions::numFramesToKeepGeometryData()) {
305330
const size_t oldestFrame = m_device->getCurrentFrameId() - RtxOptions::numFramesToKeepGeometryData();
@@ -540,7 +565,6 @@ namespace dxvk {
540565
m_opacityMicromapManager->onFrameEnd();
541566
}
542567

543-
m_activePOMCount = 0;
544568
m_startInMediumMaterialIndex = SURFACE_INDEX_INVALID;
545569
m_fogStartInMediumMaterialIndex_inCache = UINT32_MAX;
546570
m_startInMediumMaterialIndex_inCache = UINT32_MAX;
@@ -553,8 +577,6 @@ namespace dxvk {
553577
// Not currently safe to cache these across frames (due to texture indices and rtx options potentially changing)
554578
m_preCreationSurfaceMaterialMap.clear();
555579

556-
m_thinOpaqueMaterialExist = false;
557-
m_sssMaterialExist = false;
558580

559581
// execute graph updates after all garbage collection is complete (to avoid updating graphs that will just be deleted)
560582
// RtxOptions will still be pending, so any changes to them will apply next frame.
@@ -656,11 +678,11 @@ namespace dxvk {
656678
// Preserve path: L1 means this draw still matches the same ReplacementInstance by identity; replacer reload is
657679
// expected to clear the draw-call tracker (see SceneManager::clear), so we do not re-verify mesh prims or
658680
// activeReplacements pointers every frame. If a path exists where replacements bind without a clear, use dynamic
659-
// (drawReplacements) for that transition drawReplacements already reconciles activeReplacements and prims.
681+
// (drawReplacements) for that transition -- drawReplacements already reconciles activeReplacements and prims.
660682
//
661683
// Static path reuses each prim's BlasEntry::modifiedGeometryData as-is. If another draw earlier this frame
662684
// already entered DrawCallCache::get and re-bound a sibling-topology BlasEntry to its own data (kUpdateBVH),
663-
// the cached buffers no longer correspond to this draw fall back to dynamic so DrawCallCache::get's
685+
// the cached buffers no longer correspond to this draw -- fall back to dynamic so DrawCallCache::get's
664686
// "frameLastTouched skip" allocates a fresh BlasEntry and processSceneObject re-links the instance.
665687
auto blasAlreadyTouchedByOtherDraw = [replacementInstance, currentFrameId]() -> bool {
666688
for (const auto& prim : replacementInstance->prims) {
@@ -1095,56 +1117,7 @@ namespace dxvk {
10951117

10961118
pBlas->frameLastTouched = m_device->getCurrentFrameId();
10971119

1098-
// Surface material and texture indices are generally stable across frames.
1099-
// If textureManager::clear() is called, the texture cache generation will change,
1100-
// and the draw calls will take the dynamic path the next frame.
1101-
// Refresh texture streaming on the preserve path: fetchNoisyMipCounts clears m_related each
1102-
// GC, so we must repeat preserveTexture(TextureRef,...) (not just associate). Opaque subsurface-extension maps are
1103-
// not in RtSurfaceMaterial::forEachTextureIndex — include those explicitly. Material graph and
1104-
// extension cache are scene-owned; leader stamp resolution stays here (RtxTextureManager::preserveTexture).
11051120
const uint32_t surfaceMatIdx = instance.surface.surfaceMaterialIndex;
1106-
if (surfaceMatIdx < m_surfaceMaterialCache.getTotalCount()) {
1107-
auto& textureManager = m_device->getCommon()->getTextureManager();
1108-
const RtSurfaceMaterial& surfaceMat = m_surfaceMaterialCache.getObjectTable()[surfaceMatIdx];
1109-
const RtOpaqueSurfaceMaterial* pOpaqueMat = nullptr;
1110-
uint16_t leaderStamp = SAMPLER_FEEDBACK_INVALID;
1111-
if (surfaceMat.getType() == RtSurfaceMaterialType::Opaque) {
1112-
pOpaqueMat = &surfaceMat.getOpaqueSurfaceMaterial();
1113-
leaderStamp = pOpaqueMat->getSamplerFeedbackStamp();
1114-
if (leaderStamp == SAMPLER_FEEDBACK_INVALID) {
1115-
const uint32_t albedoIdx = pOpaqueMat->getAlbedoOpacityTextureIndex();
1116-
const auto& textureTable = textureManager.getTextureTable();
1117-
if (albedoIdx < textureTable.size()) {
1118-
const Rc<ManagedTexture>& albedoMt = textureTable[albedoIdx].getManagedTexture();
1119-
if (albedoMt != nullptr) {
1120-
leaderStamp = albedoMt->m_samplerFeedbackStamp;
1121-
}
1122-
}
1123-
}
1124-
}
1125-
1126-
const auto touchTexture = [&](uint32_t texIdx) {
1127-
textureManager.preserveTexture(texIdx, leaderStamp);
1128-
};
1129-
1130-
surfaceMat.forEachTextureIndex(touchTexture);
1131-
1132-
if (pOpaqueMat != nullptr) {
1133-
// Per-frame aggregate flags/counts that createSurfaceMaterial sets on the
1134-
// dynamic path are reset each frame in onFrameEnd. The preserve path skips
1135-
// createSurfaceMaterial, so without this call a frame where every POM /
1136-
// SSS / thin-opaque draw is preserved would leave the aggregates at their
1137-
// reset value and silently disable POM (constants.pomMode is gated on
1138-
// getActivePOMCount() > 0) or the SSS pipeline branches.
1139-
accumulateOpaqueMaterialAggregates(*pOpaqueMat);
1140-
1141-
const uint32_t subsurfaceIdx = pOpaqueMat->getSubsurfaceMaterialIndex();
1142-
if (subsurfaceIdx != SURFACE_INDEX_INVALID &&
1143-
subsurfaceIdx < m_surfaceMaterialExtensionCache.getTotalCount()) {
1144-
m_surfaceMaterialExtensionCache.getObjectTable()[subsurfaceIdx].forEachTextureIndex(touchTexture);
1145-
}
1146-
}
1147-
}
11481121

11491122
// Ray Portal refresh on the preserve path. RayPortalManager::clear() wipes m_rayPortalInfos
11501123
// every frame in endFrame, so processRayPortalData must repopulate the slot for any portal
@@ -1306,10 +1279,16 @@ namespace dxvk {
13061279
}
13071280

13081281
// Create and bind the RT material
1309-
const RtSurfaceMaterial& surfaceMaterial = createSurfaceMaterial(*material, drawCall);
1282+
uint32_t newMatIdx = kInvalidMaterialCacheIndex;
1283+
const RtSurfaceMaterial& surfaceMaterial = createSurfaceMaterial(*material, drawCall, &newMatIdx);
13101284

13111285
if(isFirstUpdateThisFrame) {
1286+
const uint32_t oldMatIdx = instance.surface.surfaceMaterialIndex;
13121287
m_instanceManager.bindMaterial(instance, surfaceMaterial);
1288+
if (newMatIdx != oldMatIdx) {
1289+
retainSurfaceMaterial(newMatIdx);
1290+
releaseSurfaceMaterial(oldMatIdx);
1291+
}
13131292
}
13141293

13151294
// Update portal
@@ -1318,7 +1297,96 @@ namespace dxvk {
13181297
}
13191298
}
13201299

1300+
void SceneManager::retainSurfaceMaterial(uint32_t matIdx) {
1301+
// Retain is always called with newMatIdx from createSurfaceMaterial, which must return
1302+
// a valid in-bounds index. An out-of-bounds index here indicates a logic error upstream.
1303+
if (matIdx >= m_surfaceMaterialCache.getTotalCount()) {
1304+
Logger::err(str::format("[RTX] retainSurfaceMaterial: matIdx ", matIdx,
1305+
" out of bounds (cache size ", m_surfaceMaterialCache.getTotalCount(),
1306+
"; createSurfaceMaterial returned invalid index"));
1307+
assert(false && "retainSurfaceMaterial: matIdx out of bounds");
1308+
return;
1309+
}
1310+
auto& textureManager = m_device->getCommon()->getTextureManager();
1311+
const RtSurfaceMaterial& mat = m_surfaceMaterialCache.getObjectTable()[matIdx];
1312+
mat.forEachTextureIndex([&](uint32_t texIdx) {
1313+
textureManager.retainTexture(texIdx);
1314+
});
1315+
if (mat.getType() == RtSurfaceMaterialType::Opaque) {
1316+
const RtOpaqueSurfaceMaterial& opaque = mat.getOpaqueSurfaceMaterial();
1317+
if (opaque.hasValidDisplacement()) {
1318+
++m_activePOMCount;
1319+
}
1320+
const uint32_t subsurfaceIdx = opaque.getSubsurfaceMaterialIndex();
1321+
if (subsurfaceIdx != SURFACE_INDEX_INVALID &&
1322+
subsurfaceIdx < m_surfaceMaterialExtensionCache.getTotalCount()) {
1323+
const RtSurfaceMaterial& extMat = m_surfaceMaterialExtensionCache.getObjectTable()[subsurfaceIdx];
1324+
extMat.forEachTextureIndex([&](uint32_t texIdx) {
1325+
textureManager.retainTexture(texIdx);
1326+
});
1327+
if (extMat.getType() == RtSurfaceMaterialType::Subsurface) {
1328+
const float radiusScale = extMat.getSubsurfaceMaterial().getSubsurfaceRadiusScale();
1329+
if (radiusScale > 0.0f) ++m_sssCount;
1330+
else if (radiusScale < 0.0f) ++m_thinOpaqueCount;
1331+
}
1332+
}
1333+
}
1334+
}
1335+
1336+
void SceneManager::releaseSurfaceMaterial(uint32_t matIdx) {
1337+
// Out-of-bounds covers instances that were never bound (surfaceMaterialIndex ==
1338+
// kSurfaceInvalidSurfaceMaterialIndex). Renderer-created instances skip
1339+
// onInstanceDestroyedCallback entirely so they never reach here.
1340+
if (matIdx >= m_surfaceMaterialCache.getTotalCount()) {
1341+
return;
1342+
}
1343+
auto& textureManager = m_device->getCommon()->getTextureManager();
1344+
const RtSurfaceMaterial& mat = m_surfaceMaterialCache.getObjectTable()[matIdx];
1345+
mat.forEachTextureIndex([&](uint32_t texIdx) {
1346+
textureManager.releaseTexture(texIdx);
1347+
});
1348+
if (mat.getType() == RtSurfaceMaterialType::Opaque) {
1349+
const RtOpaqueSurfaceMaterial& opaque = mat.getOpaqueSurfaceMaterial();
1350+
if (opaque.hasValidDisplacement()) {
1351+
if (m_activePOMCount == 0) {
1352+
Logger::err("[RTX] releaseSurfaceMaterial: POM count underflow (mismatched retain/release)");
1353+
assert(false && "releaseSurfaceMaterial: POM count underflow");
1354+
} else {
1355+
--m_activePOMCount;
1356+
}
1357+
}
1358+
const uint32_t subsurfaceIdx = opaque.getSubsurfaceMaterialIndex();
1359+
if (subsurfaceIdx != SURFACE_INDEX_INVALID &&
1360+
subsurfaceIdx < m_surfaceMaterialExtensionCache.getTotalCount()) {
1361+
const RtSurfaceMaterial& extMat = m_surfaceMaterialExtensionCache.getObjectTable()[subsurfaceIdx];
1362+
extMat.forEachTextureIndex([&](uint32_t texIdx) {
1363+
textureManager.releaseTexture(texIdx);
1364+
});
1365+
if (extMat.getType() == RtSurfaceMaterialType::Subsurface) {
1366+
const float radiusScale = extMat.getSubsurfaceMaterial().getSubsurfaceRadiusScale();
1367+
if (radiusScale > 0.0f) {
1368+
if (m_sssCount == 0) {
1369+
Logger::err("[RTX] releaseSurfaceMaterial: SSS count underflow (mismatched retain/release)");
1370+
assert(false && "releaseSurfaceMaterial: SSS count underflow");
1371+
} else {
1372+
--m_sssCount;
1373+
}
1374+
} else if (radiusScale < 0.0f) {
1375+
if (m_thinOpaqueCount == 0) {
1376+
Logger::err("[RTX] releaseSurfaceMaterial: thin-opaque count underflow (mismatched retain/release)");
1377+
assert(false && "releaseSurfaceMaterial: thin-opaque count underflow");
1378+
} else {
1379+
--m_thinOpaqueCount;
1380+
}
1381+
}
1382+
}
1383+
}
1384+
}
1385+
}
1386+
13211387
void SceneManager::onInstanceDestroyed(RtInstance& instance) {
1388+
releaseSurfaceMaterial(instance.surface.surfaceMaterialIndex);
1389+
13221390
// Evict from the AccelManager bucket cache to prevent stale pointer ABA issues.
13231391
m_accelManager.removeInstanceFromBucketCache(&instance);
13241392

@@ -1680,8 +1748,6 @@ namespace dxvk {
16801748
secondaryTextureIndex
16811749
};
16821750

1683-
accumulateOpaqueMaterialAggregates(opaqueSurfaceMaterial);
1684-
16851751
surfaceMaterial.emplace(opaqueSurfaceMaterial);
16861752
} else if (renderMaterialDataType == MaterialDataType::Translucent) {
16871753
surfaceMaterial.emplace(createTranslucentSurfaceMaterial(renderMaterialData.getTranslucentMaterialData(), samplerIndex, hasTexcoords));
@@ -1718,35 +1784,6 @@ namespace dxvk {
17181784
return m_surfaceMaterialCache.at(index);
17191785
}
17201786

1721-
void SceneManager::accumulateOpaqueMaterialAggregates(const RtOpaqueSurfaceMaterial& opaqueMat) {
1722-
if (opaqueMat.hasValidDisplacement()) {
1723-
++m_activePOMCount;
1724-
}
1725-
1726-
const uint32_t subsurfaceIdx = opaqueMat.getSubsurfaceMaterialIndex();
1727-
if (subsurfaceIdx == SURFACE_INDEX_INVALID ||
1728-
subsurfaceIdx >= m_surfaceMaterialExtensionCache.getTotalCount()) {
1729-
return;
1730-
}
1731-
const RtSurfaceMaterial& extMat = m_surfaceMaterialExtensionCache.getObjectTable()[subsurfaceIdx];
1732-
if (extMat.getType() != RtSurfaceMaterialType::Subsurface) {
1733-
return;
1734-
}
1735-
// createSurfaceMaterial's SSS/thin-opaque branch encodes
1736-
// isSubsurfaceScatteringDiffusionProfile into the sign of radiusScale:
1737-
// true → radiusScale clamped to >= 1e-5f (strictly > 0) → SSS material
1738-
// false → radiusScale = -1 (strictly < 0) → thin-opaque material
1739-
// (the asserts there enforce the sign, and the GPU side in rtx_materials.h
1740-
// branches on the same convention). The sign of radiusScale is therefore the
1741-
// cached form of the SSS-vs-thin-opaque selector and can be inspected here
1742-
// without re-reading the original MaterialData.
1743-
const float radiusScale = extMat.getSubsurfaceMaterial().getSubsurfaceRadiusScale();
1744-
if (radiusScale > 0.0f) {
1745-
m_sssMaterialExist = true;
1746-
} else if (radiusScale < 0.0f) {
1747-
m_thinOpaqueMaterialExist = true;
1748-
}
1749-
}
17501787

17511788
RtTranslucentSurfaceMaterial SceneManager::createTranslucentSurfaceMaterial(const TranslucentMaterialData& translucentMaterialData,
17521789
uint32_t samplerIndex,
@@ -2357,6 +2394,7 @@ namespace dxvk {
23572394
m_device->statCounters().setCtr(DxvkStatCounter::RtxBlasCount, AccelManager::getBlasCount());
23582395
m_device->statCounters().setCtr(DxvkStatCounter::RtxBufferCount, m_bufferCache.getActiveCount());
23592396
m_device->statCounters().setCtr(DxvkStatCounter::RtxTextureCount, textureManager.getTextureTable().size());
2397+
m_device->statCounters().setCtr(DxvkStatCounter::RtxReplacementTextureCount, textureManager.getActiveReplacementTextures());
23602398
m_device->statCounters().setCtr(DxvkStatCounter::RtxInstanceCount, m_instanceManager.getActiveCount());
23612399
m_device->statCounters().setCtr(DxvkStatCounter::RtxSurfaceMaterialCount, m_surfaceMaterialCache.getActiveCount());
23622400
m_device->statCounters().setCtr(DxvkStatCounter::RtxSurfaceMaterialExtensionCount, m_surfaceMaterialExtensionCache.getActiveCount());

0 commit comments

Comments
 (0)