From daaffc28778f74e9b9e3ef9d79cfdbaedc8f5ad6 Mon Sep 17 00:00:00 2001 From: Mark Henderson Date: Mon, 16 Dec 2024 16:17:04 -0800 Subject: [PATCH] [REMIX-3800] cache and reuse RtSurfaceMaterial within the same frame Hash the input to the RtSurfaceMaterial creation process, and re-use the previous one if the hashes match. This also fixes several things about the runtime-only MaterialData hashing process to ensure materials actually get unique hashes. --- src/dxvk/rtx_render/rtx_material_data.h | 5 +- src/dxvk/rtx_render/rtx_scene_manager.cpp | 61 +++++++++++++++-------- src/dxvk/rtx_render/rtx_scene_manager.h | 8 +-- src/dxvk/rtx_render/rtx_texture.h | 16 ++++-- 4 files changed, 60 insertions(+), 30 deletions(-) diff --git a/src/dxvk/rtx_render/rtx_material_data.h b/src/dxvk/rtx_render/rtx_material_data.h index 4916ed038..cfc31055d 100644 --- a/src/dxvk/rtx_render/rtx_material_data.h +++ b/src/dxvk/rtx_render/rtx_material_data.h @@ -197,7 +197,10 @@ m_##name = clamp(m_##name, ranges.Min##name, ranges.Max##name); #define WRITE_TEXTURE_HASH(name, usd_attr, type, minVal, maxVal, defaultVal) \ - h ^= m_##name.getImageHash(); + { \ + XXH64_hash_t imageHash = m_##name.getImageHash(); \ + h = XXH64(&imageHash, sizeof(imageHash), h); \ + } #define WRITE_CONSTANT_HASH(name, usd_attr, type, minVal, maxVal, defaultVal) \ h = XXH64(&m_##name, sizeof(m_##name), h); diff --git a/src/dxvk/rtx_render/rtx_scene_manager.cpp b/src/dxvk/rtx_render/rtx_scene_manager.cpp index 529b77650..2455b3223 100644 --- a/src/dxvk/rtx_render/rtx_scene_manager.cpp +++ b/src/dxvk/rtx_render/rtx_scene_manager.cpp @@ -170,6 +170,7 @@ namespace dxvk { // We still need to clear caches even if the scene wasn't rendered m_bufferCache.clear(); m_surfaceMaterialCache.clear(); + m_preCreationSurfaceMaterialMap.clear(); m_surfaceMaterialExtensionCache.clear(); m_volumeMaterialCache.clear(); @@ -488,6 +489,9 @@ namespace dxvk { m_uniqueObjectSearchDistance = RtxOptions::uniqueObjectDistance(); m_drawCallCache.rebuildSpatialMaps(); } + + // Not currently safe to cache these across frames (due to texture indices and rtx options potentially changing) + m_preCreationSurfaceMaterialMap.clear(); } void SceneManager::onFrameEndNoRTX() { @@ -517,10 +521,7 @@ namespace dxvk { if (pFogReplacement->getType() != MaterialDataType::Translucent) { Logger::warn(str::format("Fog replacement materials must be translucent. Ignoring material for ", std::hex, m_fog.getHash())); } else { - std::optional surfaceMaterial {}; - - createSurfaceMaterial(ctx, surfaceMaterial, *pFogReplacement, input); - m_startInMediumMaterialIndex = m_surfaceMaterialCache.track(*surfaceMaterial); + createSurfaceMaterial(ctx, *pFogReplacement, input); } } else if (m_fog.mode == D3DFOG_NONE) { // render the first unreplaced fog. @@ -901,17 +902,8 @@ namespace dxvk { // Note: Use either the specified override Material Data or the original draw calls state's Material Data to create a Surface Material if no override is specified const auto renderMaterialDataType = renderMaterialData.getType(); - std::optional surfaceMaterial{}; - - createSurfaceMaterial(ctx, surfaceMaterial, renderMaterialData, drawCallState); - - assert(surfaceMaterial.has_value()); - assert(surfaceMaterial->validate()); - - // Cache this - m_surfaceMaterialCache.track(*surfaceMaterial); - - RtInstance* instance = m_instanceManager.processSceneObject(m_cameraManager, m_rayPortalManager, *pBlas, drawCallState, renderMaterialData, *surfaceMaterial); + const RtSurfaceMaterial& surfaceMaterial = createSurfaceMaterial(ctx, renderMaterialData, drawCallState); + RtInstance* instance = m_instanceManager.processSceneObject(m_cameraManager, m_rayPortalManager, *pBlas, drawCallState, renderMaterialData, surfaceMaterial); // Check if a light should be created for this Material if (instance && RtxOptions::Get()->shouldConvertToLight(drawCallState.getMaterialData().getHash())) { @@ -947,10 +939,9 @@ namespace dxvk { return instance ? instance->getId() : UINT64_MAX; } - void SceneManager::createSurfaceMaterial( Rc ctx, - std::optional& surfaceMaterial, - const MaterialData& renderMaterialData, - const DrawCallState& drawCallState) { + const RtSurfaceMaterial& SceneManager::createSurfaceMaterial( Rc ctx, + const MaterialData& renderMaterialData, + const DrawCallState& drawCallState) { ScopedCpuProfileZone(); const bool hasTexcoords = drawCallState.hasTextureCoordinates(); const auto renderMaterialDataType = renderMaterialData.getType(); @@ -963,7 +954,7 @@ namespace dxvk { Rc sampler = originalSampler; // If the original sampler if valid and the new rendering material is not legacy type // go ahead with patching and maybe merging the sampler states - if(originalSampler != nullptr && renderMaterialDataType != MaterialDataType::Legacy) { + if (originalSampler != nullptr && renderMaterialDataType != MaterialDataType::Legacy) { DxvkSamplerCreateInfo samplerInfo = originalSampler->info(); // Use sampler create info struct as convenience renderMaterialData.populateSamplerInfo(samplerInfo); @@ -972,6 +963,26 @@ namespace dxvk { samplerInfo.borderColor); } uint32_t samplerIndex = trackSampler(sampler); + uint32_t samplerIndex2 = UINT32_MAX; + if (renderMaterialDataType == MaterialDataType::RayPortal) { + samplerIndex2 = trackSampler(drawCallState.getMaterialData().getSampler2()); + } + + XXH64_hash_t preCreationHash = renderMaterialData.getHash(); + if (renderMaterialDataType == MaterialDataType::Legacy) { + preCreationHash = XXH64(&renderMaterialData.getLegacyMaterialData(), sizeof(LegacyMaterialData), preCreationHash); + } + preCreationHash = XXH64(&samplerIndex, sizeof(samplerIndex), preCreationHash); + preCreationHash = XXH64(&samplerIndex2, sizeof(samplerIndex2), preCreationHash); + preCreationHash = XXH64(&hasTexcoords, sizeof(hasTexcoords), preCreationHash); + preCreationHash = XXH64(&drawCallState.isUsingRaytracedRenderTarget, sizeof(drawCallState.isUsingRaytracedRenderTarget), preCreationHash); + + auto iter = m_preCreationSurfaceMaterialMap.find(preCreationHash); + if (iter != m_preCreationSurfaceMaterialMap.end()) { + return m_surfaceMaterialCache.at(iter->second); + } + + std::optional surfaceMaterial; if (renderMaterialDataType == MaterialDataType::Legacy || renderMaterialDataType == MaterialDataType::Opaque || drawCallState.isUsingRaytracedRenderTarget) { uint32_t albedoOpacityTextureIndex = kSurfaceMaterialInvalidTextureIndex; @@ -1168,8 +1179,6 @@ namespace dxvk { uint32_t maskTextureIndex2 = kSurfaceMaterialInvalidTextureIndex; trackTexture(ctx, rayPortalMaterialData.getMaskTexture2(), maskTextureIndex2, hasTexcoords, false); - uint32_t samplerIndex2 = trackSampler(drawCallState.getMaterialData().getSampler2()); - uint8_t rayPortalIndex = rayPortalMaterialData.getRayPortalIndex(); float rotationSpeed = rayPortalMaterialData.getRotationSpeed(); bool enableEmissive = rayPortalMaterialData.getEnableEmission(); @@ -1182,6 +1191,14 @@ namespace dxvk { surfaceMaterial.emplace(rayPortalSurfaceMaterial); } + + assert(surfaceMaterial.has_value()); + assert(surfaceMaterial->validate()); + + // Cache this + const uint32_t index = m_surfaceMaterialCache.track(*surfaceMaterial); + m_preCreationSurfaceMaterialMap[preCreationHash] = index; + return m_surfaceMaterialCache.at(index); } std::optional SceneManager::findLegacyTextureHashByObjectPickingValue(uint32_t objectPickingValue) { diff --git a/src/dxvk/rtx_render/rtx_scene_manager.h b/src/dxvk/rtx_render/rtx_scene_manager.h index 4134e5635..732bae8f6 100644 --- a/src/dxvk/rtx_render/rtx_scene_manager.h +++ b/src/dxvk/rtx_render/rtx_scene_manager.h @@ -76,6 +76,7 @@ class ResourceCache { }; SparseUniqueCache m_surfaceMaterialCache; SparseUniqueCache m_surfaceMaterialExtensionCache; + fast_unordered_cache m_preCreationSurfaceMaterialMap; struct VolumeMaterialHashFn { size_t operator() (const RtVolumeMaterial& mat) const { @@ -228,10 +229,9 @@ class SceneManager : public CommonDeviceObject, public ResourceCache { // Consumes a draw call state and updates the scene state accordingly uint64_t processDrawCallState(Rc ctx, const DrawCallState& blasInput, const MaterialData* replacementMaterialData); - void createSurfaceMaterial( Rc ctx, - std::optional& surfaceMaterial, - const MaterialData& renderMaterialData, - const DrawCallState& drawCallState); + const RtSurfaceMaterial& createSurfaceMaterial( Rc ctx, + const MaterialData& renderMaterialData, + const DrawCallState& drawCallState); // Updates ref counts for new buffers void updateBufferCache(RaytraceGeometry& newGeoData); diff --git a/src/dxvk/rtx_render/rtx_texture.h b/src/dxvk/rtx_render/rtx_texture.h index 606d47945..254d88e46 100644 --- a/src/dxvk/rtx_render/rtx_texture.h +++ b/src/dxvk/rtx_render/rtx_texture.h @@ -144,11 +144,21 @@ namespace dxvk { XXH64_hash_t getImageHash() const { const DxvkImageView* resolvedImageView = getImageView(); + XXH64_hash_t result = 0; + if (resolvedImageView) { + result = resolvedImageView->image()->getHash(); + } - if (resolvedImageView) - return resolvedImageView->image()->getHash(); + if (result == 0 && m_managedTexture.ptr() != nullptr) { + // NOTE: only replacement textures should have an m_managedTexture pointer. To avoid changing game texture + // hashes, all ImageHash modifications should be inside this block. + const XXH64_hash_t assetDataHash = m_managedTexture->assetData->hash(); + result = XXH64(&assetDataHash, sizeof(assetDataHash), result); + // Needed to distinguish materials that load the same file different ways (i.e. raw vs sRGB) + result = XXH64(&m_uniqueKey, sizeof(m_uniqueKey), result); + } - return 0; + return result; } size_t getUniqueKey() const {