diff --git a/src/dxvk/rtx_render/rtx_camera_manager.cpp b/src/dxvk/rtx_render/rtx_camera_manager.cpp index cd8b79a1..e810b7b9 100644 --- a/src/dxvk/rtx_render/rtx_camera_manager.cpp +++ b/src/dxvk/rtx_render/rtx_camera_manager.cpp @@ -21,8 +21,6 @@ */ #include "rtx_camera_manager.h" -#include "rtx_matrix_helpers.h" - #include "dxvk_device.h" namespace { @@ -44,6 +42,7 @@ namespace dxvk { void CameraManager::onFrameEnd() { m_lastSetCameraType = CameraType::Unknown; + m_decompositionCache.clear(); } CameraType::Enum CameraManager::processCameraData(const DrawCallState& input) { @@ -75,10 +74,7 @@ namespace dxvk { } // Get camera params - float fov, aspectRatio, nearPlane, farPlane, shearX, shearY; - bool isLHS; - bool isReverseZ; - decomposeProjection(input.getTransformData().viewToProjection, aspectRatio, fov, nearPlane, farPlane, shearX, shearY, isLHS, isReverseZ); + DecomposeProjectionParams decomposeProjectionParams = getOrDecomposeProjection(input.getTransformData().viewToProjection); // Filter invalid cameras, extreme shearing static auto isFovValid = [](float fovA) { @@ -88,7 +84,7 @@ namespace dxvk { return std::abs(fovA - cameraB.getFov()) < kFovToleranceRadians; }; - if (std::abs(shearX) > 0.01f || !isFovValid(fov)) { + if (std::abs(decomposeProjectionParams.shearX) > 0.01f || !isFovValid(decomposeProjectionParams.fov)) { ONCE(Logger::warn("[RTX] CameraManager: rejected an invalid camera")); return input.getCategoryFlags().test(InstanceCategories::Sky) ? CameraType::Sky : CameraType::Unknown; } @@ -117,13 +113,13 @@ namespace dxvk { cameraType = CameraType::RenderToTexture; } else if (input.testCategoryFlags(InstanceCategories::Sky)) { cameraType = CameraType::Sky; - } else if (isViewModel(fov, input.maxZ, frameId)) { + } else if (isViewModel(decomposeProjectionParams.fov, input.maxZ, frameId)) { cameraType = CameraType::ViewModel; } // Check fov consistency across frames if (frameId > 0) { - if (getCamera(cameraType).isValid(frameId - 1) && !areFovsClose(fov, getCamera(cameraType))) { + if (getCamera(cameraType).isValid(frameId - 1) && !areFovsClose(decomposeProjectionParams.fov, getCamera(cameraType))) { ONCE(Logger::warn("[RTX] CameraManager: FOV of a camera changed between frames")); } } @@ -148,7 +144,15 @@ namespace dxvk { } } else { isCameraCut = camera.update( - frameId, worldToView, viewToProjection, fov, aspectRatio, nearPlane,farPlane, isLHS); + frameId, + worldToView, + viewToProjection, + decomposeProjectionParams.fov, + decomposeProjectionParams.aspectRatio, + decomposeProjectionParams.nearPlane, + decomposeProjectionParams.farPlane, + decomposeProjectionParams.isLHS + ); } @@ -173,13 +177,29 @@ namespace dxvk { void CameraManager::processExternalCamera(CameraType::Enum type, const Matrix4& worldToView, const Matrix4& viewToProjection) { - float fov, aspectRatio, nearPlane, farPlane, shearX, shearY; - bool isLHS; - bool isReverseZ; - decomposeProjection(viewToProjection, aspectRatio, fov, nearPlane, farPlane, shearX, shearY, isLHS, isReverseZ); + DecomposeProjectionParams decomposeProjectionParams = getOrDecomposeProjection(viewToProjection); getCamera(type).update( m_device->getCurrentFrameId(), - worldToView, viewToProjection, fov, aspectRatio, nearPlane, farPlane, isLHS); + worldToView, + viewToProjection, + decomposeProjectionParams.fov, + decomposeProjectionParams.aspectRatio, + decomposeProjectionParams.nearPlane, + decomposeProjectionParams.farPlane, + decomposeProjectionParams.isLHS); } + + DecomposeProjectionParams CameraManager::getOrDecomposeProjection(const Matrix4& viewToProjection) { + XXH64_hash_t projectionHash = XXH64(&viewToProjection, sizeof(viewToProjection), 0); + auto iter = m_decompositionCache.find(projectionHash); + if (iter != m_decompositionCache.end()) { + return iter->second; + } + + DecomposeProjectionParams decomposeProjectionParams; + decomposeProjection(viewToProjection, decomposeProjectionParams); + m_decompositionCache.emplace(projectionHash, decomposeProjectionParams); + return decomposeProjectionParams; + } } // namespace dxvk diff --git a/src/dxvk/rtx_render/rtx_camera_manager.h b/src/dxvk/rtx_render/rtx_camera_manager.h index 6641f29d..599f5ed9 100644 --- a/src/dxvk/rtx_render/rtx_camera_manager.h +++ b/src/dxvk/rtx_render/rtx_camera_manager.h @@ -25,6 +25,7 @@ #include "rtx_option.h" #include "rtx_types.h" #include "rtx_common_object.h" +#include "rtx_matrix_helpers.h" namespace dxvk { class DxvkDevice; @@ -81,6 +82,9 @@ namespace dxvk { std::array m_cameras; CameraType::Enum m_lastSetCameraType = CameraType::Unknown; uint32_t m_lastCameraCutFrameId = -1; + fast_unordered_cache m_decompositionCache; + + DecomposeProjectionParams getOrDecomposeProjection(const Matrix4& viewToProjection); RTX_OPTION("rtx", bool, rayPortalEnabled, false, "Enables ray portal support. Note this requires portal texture hashes to be set for the ray portal geometries in rtx.rayPortalModelTextureHashes."); }; diff --git a/src/dxvk/rtx_render/rtx_instance_manager.cpp b/src/dxvk/rtx_render/rtx_instance_manager.cpp index f2f17998..85bdb476 100644 --- a/src/dxvk/rtx_render/rtx_instance_manager.cpp +++ b/src/dxvk/rtx_render/rtx_instance_manager.cpp @@ -134,7 +134,7 @@ namespace dxvk { m_frameLastUpdated m_frameCreated m_isCreatedByRenderer - m_spatialCachePos + m_spatialCacheHash buildGeometries buildRanges billboardIndices @@ -170,8 +170,7 @@ namespace dxvk { // Cache based on current position. const Vector3 newPos = getBlas()->input.getGeometryData().boundingBox.getTransformedCentroid(surface.objectToWorld); - m_linkedBlas->getSpatialMap().move(m_spatialCachePos, newPos, this); - m_spatialCachePos = newPos; + m_spatialCacheHash = m_linkedBlas->getSpatialMap().move(m_spatialCacheHash, newPos, surface.objectToWorld, this); } } @@ -180,8 +179,8 @@ namespace dxvk { surface.normalObjectToWorld = transpose(inverse(Matrix3(surface.objectToWorld))); surface.prevObjectToWorld = objectToWorld; if (!m_isCreatedByRenderer) { - m_spatialCachePos = getBlas()->input.getGeometryData().boundingBox.getTransformedCentroid(surface.objectToWorld); - m_linkedBlas->getSpatialMap().insert(m_spatialCachePos, this); + const Vector3 centroid = getBlas()->input.getGeometryData().boundingBox.getTransformedCentroid(surface.objectToWorld); + m_spatialCacheHash = m_linkedBlas->getSpatialMap().insert(centroid, surface.objectToWorld, this); } // The D3D matrix on input, needs to be transposed before feeding to the VK API (left/right handed conversion) @@ -653,38 +652,21 @@ namespace dxvk { // Search the BLAS for an instance matching ours { // Search for an exact match - const std::vector* matchingCell = blas.getSpatialMap().getDataAtPos(worldPosition); - if (matchingCell != nullptr) { - for (const RtInstance* instance : *matchingCell) { - const Matrix4 instanceTransform = instance->getTransform(); - // Note: this may be the second call this frame targetting the same instance. - if (memcmp(&transform, &instanceTransform, sizeof(instanceTransform)) == 0) { - return const_cast(instance); - } - } + result = const_cast(blas.getSpatialMap().getDataAtTransform(transform)); + if (result != nullptr) { + return result; } // No exact match, so find the closest match in the region // (need to check a 2x2x2 patch of cells to account for positions close to a border) - const auto adjacentCells = blas.getSpatialMap().getDataNearPos(worldPosition); - for (const std::vector* cellPtr : adjacentCells){ - for (const RtInstance* instance : *cellPtr) { - if (instance->m_frameLastUpdated != currentFrameIdx && instance->m_materialHash == material.getHash()) { - // Instance hasn't been touched yet this frame. - - const Vector3& prevInstanceWorldPosition = instance->getSpatialCachePosition(); - - const float distSqr = lengthSqr(prevInstanceWorldPosition - worldPosition); - if (distSqr <= uniqueObjectDistanceSqr && distSqr < nearestDistSqr) { - if (distSqr == 0.0f) { - // Not going to find anything closer. - return const_cast(instance); - } - nearestDistSqr = distSqr; - result = const_cast(instance); - } - } + result = const_cast(blas.getSpatialMap().getNearestData(worldPosition, uniqueObjectDistanceSqr, nearestDistSqr, + [&] (const RtInstance* instance) { + return instance->m_frameLastUpdated != currentFrameIdx && instance->m_materialHash == material.getHash(); } + )); + if (nearestDistSqr == 0.0f && result != nullptr) { + // Not going to find anything closer + return result; } } diff --git a/src/dxvk/rtx_render/rtx_instance_manager.h b/src/dxvk/rtx_render/rtx_instance_manager.h index 165b79b4..dde0452f 100644 --- a/src/dxvk/rtx_render/rtx_instance_manager.h +++ b/src/dxvk/rtx_render/rtx_instance_manager.h @@ -65,12 +65,11 @@ class RtInstance { Vector3 getWorldPosition() const { return Vector3{ m_vkInstance.transform.matrix[0][3], m_vkInstance.transform.matrix[1][3], m_vkInstance.transform.matrix[2][3] }; } const Vector3& getPrevWorldPosition() const { return surface.prevObjectToWorld.data[3].xyz(); } - const Vector3& getSpatialCachePosition() const { return m_spatialCachePos; } void removeFromSpatialCache() const { if (m_isCreatedByRenderer) { return; } - m_linkedBlas->getSpatialMap().erase(m_spatialCachePos, this); + m_linkedBlas->getSpatialMap().erase(m_spatialCacheHash); } bool isCreatedThisFrame(uint32_t frameIndex) const { return frameIndex == m_frameCreated; } @@ -203,7 +202,7 @@ class RtInstance { CategoryFlags m_categoryFlags; - Vector3 m_spatialCachePos = Vector3(0.f); + XXH64_hash_t m_spatialCacheHash = 0; public: bool isFrontFaceFlipped = false; diff --git a/src/dxvk/rtx_render/rtx_matrix_helpers.h b/src/dxvk/rtx_render/rtx_matrix_helpers.h index 1193ebd4..0f09d90b 100644 --- a/src/dxvk/rtx_render/rtx_matrix_helpers.h +++ b/src/dxvk/rtx_render/rtx_matrix_helpers.h @@ -80,4 +80,19 @@ static inline void decomposeProjection(const dxvk::Matrix4& matrix, float& aspec "\n\tPROJ_ANGLEMAXY: ", cameraParams[PROJ_ANGLEMAXY])); } #endif -} \ No newline at end of file +} + +struct DecomposeProjectionParams { + float fov; + float aspectRatio; + float nearPlane; + float farPlane; + float shearX; + float shearY; + bool isLHS; + bool isReverseZ; +}; + +static inline void decomposeProjection(const dxvk::Matrix4& matrix, DecomposeProjectionParams& result, bool log = false) { + decomposeProjection(matrix, result.aspectRatio, result.fov, result.nearPlane, result.farPlane, result.shearX, result.shearY, result.isLHS, result.isReverseZ, log); +} diff --git a/src/dxvk/rtx_render/rtx_texture.h b/src/dxvk/rtx_render/rtx_texture.h index 254d88e4..1a6905d1 100644 --- a/src/dxvk/rtx_render/rtx_texture.h +++ b/src/dxvk/rtx_render/rtx_texture.h @@ -143,9 +143,8 @@ namespace dxvk { } XXH64_hash_t getImageHash() const { - const DxvkImageView* resolvedImageView = getImageView(); XXH64_hash_t result = 0; - if (resolvedImageView) { + if (const DxvkImageView* resolvedImageView = getImageView()) { result = resolvedImageView->image()->getHash(); } diff --git a/src/dxvk/rtx_render/rtx_types.cpp b/src/dxvk/rtx_render/rtx_types.cpp index eb6d521f..50c8f435 100644 --- a/src/dxvk/rtx_render/rtx_types.cpp +++ b/src/dxvk/rtx_render/rtx_types.cpp @@ -360,14 +360,7 @@ namespace dxvk { } void BlasEntry::rebuildSpatialMap() { - InstanceMap newMap(RtxOptions::uniqueObjectDistance() * 2.f); - - for (const auto& iter : m_spatialMap.getAll()){ - for (const RtInstance* instance : iter.second) { - newMap.insert(instance->getSpatialCachePosition(), instance); - } - } - m_spatialMap = std::move(newMap); + m_spatialMap.rebuild(RtxOptions::uniqueObjectDistance() * 2.f); } } // namespace dxvk diff --git a/src/dxvk/rtx_render/rtx_types.h b/src/dxvk/rtx_render/rtx_types.h index df70f8a6..f7cc90c1 100644 --- a/src/dxvk/rtx_render/rtx_types.h +++ b/src/dxvk/rtx_render/rtx_types.h @@ -586,7 +586,7 @@ struct BlasEntry { // Frame when the vertex data of this geometry was last updated, used to detect static geometries uint32_t frameLastUpdated = kInvalidFrameIndex; - using InstanceMap = SpatialMap; + using InstanceMap = SpatialMap; Rc staticBlas; diff --git a/src/util/util_spatial_map.h b/src/util/util_spatial_map.h index 5d7c7646..fd95638e 100644 --- a/src/util/util_spatial_map.h +++ b/src/util/util_spatial_map.h @@ -23,6 +23,7 @@ #pragma once #include +#include "util_matrix.h" #include "util_vector.h" #include "util_fast_cache.h" #include "./log/log.h" @@ -31,6 +32,15 @@ namespace dxvk { // A structure to allow for quickly returning data close to a specific position. template class SpatialMap { + private: + struct Entry { + const T* data; + Vector3 centroid; + XXH64_hash_t transformHash; + Entry() : data(nullptr), centroid(0.f), transformHash(0) { } + Entry(const T* data, const Vector3& centroid, XXH64_hash_t transformHash) : data(data), centroid(centroid), transformHash(transformHash) { } + Entry(const Entry& other) : data(other.data), centroid(other.centroid), transformHash(other.transformHash) { } + }; public: SpatialMap(float cellSize) : m_cellSize(cellSize) { if (m_cellSize <= 0) { @@ -41,21 +51,24 @@ namespace dxvk { SpatialMap& operator=(SpatialMap&& other) { m_cellSize = other.m_cellSize; + m_cells = std::move(other.m_cells); m_cache = std::move(other.m_cache); return *this; } - // returns the cell closest to `position` - const std::vector* getDataAtPos(const Vector3& position) const { - auto pair = m_cache.find(getCellPos(position)); + // returns the data with an identical transform + const T* getDataAtTransform(const Matrix4& transform) const { + XXH64_hash_t transformHash = XXH64(&transform, sizeof(transform), 0); + auto pair = m_cache.find(transformHash); if ( pair != m_cache.end()) { - return &pair->second; + return pair->second.data; } return nullptr; } - // returns the 8 cells closest to `position` - const std::vector*> getDataNearPos(const Vector3& position) const { + // returns the entry cosest to `centroid` that passes the `filter` and is less than `sqrt(maxDistSqr)` units from `centroid`. + // `filter` should return true if the entry is a valid result. + const T* getNearestData(const Vector3& centroid, float maxDistSqr, float& nearestDistSqr, std::function filter) const { static const std::array kOffsets{ Vector3i{0, 0, 0}, Vector3i{0, 0, 1}, @@ -66,42 +79,69 @@ namespace dxvk { Vector3i{1, 1, 0}, Vector3i{1, 1, 1} }; - std::vector*> result; - result.reserve(8); - - const Vector3 cellPosition = position / m_cellSize - Vector3(0.5f, 0.5f, 0.5f); + const Vector3 cellPosition = centroid / m_cellSize - Vector3(0.5f, 0.5f, 0.5f); const Vector3i floorPos(int(std::floor(cellPosition.x)), int(std::floor(cellPosition.y)), int(std::floor(cellPosition.z))); + const T* nearestData = nullptr; + nearestDistSqr = FLT_MAX; for (const Vector3i& offset : kOffsets) { - auto iter = m_cache.find(floorPos + offset); - if (iter != m_cache.end()) { - const std::vector* value = &iter->second; - result.push_back(value); + auto cell = m_cells.find(floorPos + offset); + if (cell == m_cells.end()) { + continue; + } + for (const Entry& entry : cell->second) { + if (!filter(entry.data)) { + continue; + } + const float distSqr = lengthSqr(entry.centroid - centroid); + if (distSqr <= maxDistSqr && distSqr < nearestDistSqr) { + nearestDistSqr = distSqr; + if (nearestDistSqr == 0.0f) { + // Not going to find anything closer, so stop the iteration + return entry.data; + } + nearestData = entry.data; + } } } - - return result; - }; + return nearestData; + } - void insert(const Vector3& position, T data) { - insert(getCellPos(position), data); + XXH64_hash_t insert(const Vector3& centroid, const Matrix4& transform, const T* data) { + XXH64_hash_t transformHash = XXH64(&transform, sizeof(transform), 0); + m_cache.emplace(std::piecewise_construct, + std::forward_as_tuple(transformHash), + std::forward_as_tuple(data, centroid, transformHash)); + m_cells[getCellPos(centroid)].emplace_back(data, centroid, transformHash); + return transformHash; } - void erase(const Vector3& position, T data) { - erase(getCellPos(position), data); + void erase(const XXH64_hash_t& transformHash) { + auto pair = m_cache.find(transformHash); + if (pair != m_cache.end()) { + eraseFromCell(pair->second.centroid, transformHash); + m_cache.erase(pair); + } else { + ONCE(Logger::err("Specified hash was missing in SpatialMap::erase().")); + assert(false); + } } - void move(const Vector3& oldPosition, const Vector3& newPosition, T data) { - Vector3i oldPos = getCellPos(oldPosition); - Vector3i newPos = getCellPos(newPosition); - if (oldPos != newPos) { - erase(oldPosition, data); - insert(newPos, data); + XXH64_hash_t move(const XXH64_hash_t& oldTransformHash, const Vector3& centroid, const Matrix4& newTransform, const T* data) { + XXH64_hash_t transformHash = XXH64(&newTransform, sizeof(newTransform), 0); + + if (oldTransformHash != transformHash) { + erase(oldTransformHash); + insert(centroid, newTransform, data); } + return transformHash; } - const fast_spatial_cache>& getAll() { - return m_cache; + void rebuild(float cellSize) { + m_cells.clear(); + for (auto pair : m_cache) { + m_cells[getCellPos(pair.second.centroid)].emplace_back(pair.second); + } } private: @@ -111,33 +151,33 @@ namespace dxvk { return Vector3i(int(std::floor(scaledPos.x)), int(std::floor(scaledPos.y)), int(std::floor(scaledPos.z))); } - void insert(const Vector3i& pos, T data) { - m_cache[pos].push_back(data); - } - - void erase(const Vector3i& pos, T data) { - auto cellIter = m_cache.find(pos); - if (cellIter == m_cache.end()) { - Logger::err("Specified cell was already empty in SpatialMap::erase()."); + void eraseFromCell(const Vector3& pos, XXH64_hash_t hash) { + auto cellIter = m_cells.find(getCellPos(pos)); + if (cellIter == m_cells.end()) { + ONCE(Logger::err("Specified cell was already empty in SpatialMap::erase().")); + assert(false); return; } - std::vector& cell = cellIter->second; - auto iter = std::find(cell.begin(), cell.end(), data); - if (iter != cell.end()) { - if (cell.size() > 1) { - // Swap & pop - faster than "erase", but doesn't preserve order, which is fine here. - std::swap(*iter, cell.back()); - cell.pop_back(); - } else { - m_cache.erase(cellIter); + std::vector& cell = cellIter->second; + for (auto iter = cell.begin(); iter != cell.end(); ++iter) { + if (iter->transformHash == hash) { + if (cell.size() > 1) { + // Swap & pop - faster than "erase", but doesn't preserve order, which is fine here. + std::swap(*iter, cell.back()); + cell.pop_back(); + } else { + m_cells.erase(cellIter); + } + return; } - } else { - Logger::err("Couldn't find matching data in SpatialMap::erase()."); } + + Logger::err("Couldn't find matching data in SpatialMap::erase()."); } float m_cellSize; - fast_spatial_cache> m_cache; + fast_spatial_cache> m_cells; + fast_unordered_cache m_cache; }; } diff --git a/tests/rtx/unit/test_spatial_map.cpp b/tests/rtx/unit/test_spatial_map.cpp index b2bad8b0..f9ca28d6 100644 --- a/tests/rtx/unit/test_spatial_map.cpp +++ b/tests/rtx/unit/test_spatial_map.cpp @@ -40,40 +40,48 @@ namespace dxvk { return str::format("{", pos.x, ", ", pos.y, ", ", pos.z, "}"); } - void testPoint(const SpatialMap& map, const Vector3& pos, const std::set& expectedResult) { - auto candidates = map.getDataNearPos(pos); - std::set candidatesSet; - int count = 0; - for (auto vec : candidates) { - for (int value : *vec) { - count++; - candidatesSet.emplace(value); - } - } - if (candidatesSet != expectedResult) { - throw DxvkError(str::format("incorrect result: for pos ", ToString(pos), " expected [", ToString(expectedResult), "] but got [", ToString(candidatesSet), "].")); + void testPoint(const SpatialMap& map, const Vector3& pos, int expectedResult) { + float nearestDistSqr = FLT_MAX; + const int* result = map.getNearestData(pos, 1.f, nearestDistSqr, [](const int* unused) {return true; }); + if (*result != expectedResult) { + throw DxvkError(str::format("incorrect result: for pos ", ToString(pos), " expected [", expectedResult, "] but got [", *result, "].")); } } + struct TestData { + Vector3 pos; + int data; + Matrix4 transform; + TestData(Vector3 pos, int data) : pos(pos), data(data) { + transform = translationMatrix(pos); + } + }; + void run() { SpatialMap map(2.0f); + Matrix4 foo; + TestData data[5] = { + TestData(Vector3(-1.f, -1.f, -1.f), -1), + TestData(Vector3(0.f, 0.f, 0.f), 0), + TestData(Vector3(1.f, 1.f, 1.f), 1), + TestData(Vector3(2.f, 2.f, 2.f), 2), + TestData(Vector3(3.f, 3.f, 3.f), 3) + }; - map.insert(Vector3(-1.f, -1.f, -1.f), -1); - map.insert(Vector3(0.f, 0.f, 0.f), 0); - map.insert(Vector3(1.f, 1.f, 1.f), 1); - map.insert(Vector3(2.f, 2.f, 2.f), 2); - map.insert(Vector3(3.f, 3.f, 3.f), 3); + for (int i = 0; i < 5; ++i) { + map.insert(data[i].pos, data[i].transform, &data[i].data); + } // corner of a cell - testPoint(map, Vector3(0.f, 0.f, 0.f), {-1, 0, 1}); + testPoint(map, Vector3(0.f, 0.f, 0.f), 0); // center of a cell - testPoint(map, Vector3(1.f, 1.f, 1.f), { 0, 1, 2, 3}); + testPoint(map, Vector3(1.f, 1.f, 1.f), 1); - testPoint(map, Vector3(1.5f, 1.5f, 1.5f), { 0, 1, 2, 3}); + testPoint(map, Vector3(1.5f, 1.5f, 1.51f), 2); // near section of next cell - testPoint(map, Vector3(2.5f, 2.5f, 2.5f), { 0, 1, 2, 3}); + testPoint(map, Vector3(2.5f, 2.5f, 2.51f), 3); // far section of next cell - testPoint(map, Vector3(3.5f, 3.5f, 3.5f), { 2, 3}); + testPoint(map, Vector3(3.5f, 3.5f, 3.5f), 3); std::cout << "All passed\n"; } };