Skip to content

Commit 44874e2

Browse files
Merge branch 'hash-struct-perf' into 'main'
Change struct hashing function to avoid including all member variable pointers in the assembly code See merge request lightspeedrtx/dxvk-remix-nv!2194
2 parents 062101c + f6b52e1 commit 44874e2

7 files changed

Lines changed: 68 additions & 72 deletions

File tree

src/dxvk/dxvk_sampler.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ namespace dxvk {
4545

4646
// NV-DXVK start
4747
XXH64_hash_t calculateHash() const {
48-
return hashStructByMemory(*this,
48+
return hashStructByMemory<DxvkSamplerCreateInfo,
4949
&DxvkSamplerCreateInfo::magFilter,
5050
&DxvkSamplerCreateInfo::minFilter,
5151
&DxvkSamplerCreateInfo::mipmapMode,
@@ -60,7 +60,7 @@ namespace dxvk {
6060
&DxvkSamplerCreateInfo::compareToDepth,
6161
&DxvkSamplerCreateInfo::compareOp,
6262
&DxvkSamplerCreateInfo::borderColor,
63-
&DxvkSamplerCreateInfo::usePixelCoord);
63+
&DxvkSamplerCreateInfo::usePixelCoord>(*this);
6464
}
6565

6666
bool operator== (const DxvkSamplerCreateInfo& other) const {

src/dxvk/rtx_render/rtx_accel_manager.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,13 +1256,13 @@ namespace dxvk {
12561256
// Geometry order is part of the merged BLAS layout and affects primitive
12571257
// to surface mapping, so include the bucket order in the content hash.
12581258
if (!contentHashData.empty()) {
1259-
newContentHash = hashStructArrayByMemory(contentHashData.data(), contentHashData.size(),
1259+
newContentHash = hashStructArrayByMemory<BucketGeometryContentHashData,
12601260
&BucketGeometryContentHashData::vertexHash,
12611261
&BucketGeometryContentHashData::indexHash,
12621262
&BucketGeometryContentHashData::boneHash,
12631263
&BucketGeometryContentHashData::transform,
12641264
&BucketGeometryContentHashData::primitiveCount,
1265-
&BucketGeometryContentHashData::pad);
1265+
&BucketGeometryContentHashData::pad>(contentHashData.data(), contentHashData.size());
12661266
}
12671267
}
12681268

src/dxvk/rtx_render/rtx_accel_manager.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,14 @@ class AccelManager : public CommonDeviceObject {
9494

9595
struct BlasBucketKeyHash {
9696
size_t operator()(const BlasBucketKey& k) const {
97-
return static_cast<size_t>(hashStructByMemory(k,
97+
return static_cast<size_t>(hashStructByMemory<BlasBucketKey,
9898
&BlasBucketKey::instanceShaderBindingTableRecordOffset,
9999
&BlasBucketKey::customIndexFlags,
100100
&BlasBucketKey::instanceFlags,
101101
&BlasBucketKey::instanceMask,
102102
&BlasBucketKey::usesUnorderedApproximations,
103103
&BlasBucketKey::isSubsurface,
104-
&BlasBucketKey::pad));
104+
&BlasBucketKey::pad>(k));
105105
}
106106
};
107107

src/dxvk/rtx_render/rtx_draw_call_tracker.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ namespace dxvk {
109109
data.textureTransform = drawCallState.getTransformData().textureTransform;
110110
data.texgenMode = static_cast<uint32_t>(drawCallState.getTransformData().texgenMode);
111111

112-
return hashStructByMemory(data,
112+
return hashStructByMemory<IdentityHashData,
113113
&IdentityHashData::geoHash,
114114
&IdentityHashData::matHash,
115115
&IdentityHashData::boneHash,
@@ -119,7 +119,7 @@ namespace dxvk {
119119
&IdentityHashData::cameraType,
120120
&IdentityHashData::categories,
121121
&IdentityHashData::texgenMode,
122-
&IdentityHashData::_pad0);
122+
&IdentityHashData::_pad0>(data);
123123
}
124124

125125
void DrawCallTracker::eraseFromSpatialMap(

src/dxvk/rtx_render/rtx_scene_manager.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ namespace dxvk {
112112
data.objectToWorld = transforms.objectToWorld;
113113
data.textureTransform = transforms.textureTransform;
114114

115-
return hashStructByMemory(data,
115+
return hashStructByMemory<ExternalDrawIdentityHashData,
116116
&ExternalDrawIdentityHashData::meshId,
117117
&ExternalDrawIdentityHashData::materialHash,
118118
&ExternalDrawIdentityHashData::boneHash,
@@ -129,7 +129,7 @@ namespace dxvk {
129129
&ExternalDrawIdentityHashData::_pad0,
130130
&ExternalDrawIdentityHashData::_pad1,
131131
&ExternalDrawIdentityHashData::objectToWorld,
132-
&ExternalDrawIdentityHashData::textureTransform);
132+
&ExternalDrawIdentityHashData::textureTransform>(data);
133133
}
134134

135135
SceneManager::SceneManager(DxvkDevice* device)

src/dxvk/rtx_render/rtx_terrain_baker.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,10 @@ namespace dxvk {
186186
uint16_t /*ReplacementMaterialTextureType::Enum*/ textureType;
187187

188188
XXH64_hash_t calculateHash() const {
189-
return hashStructByMemory(*this,
189+
return hashStructByMemory<TextureKey,
190190
&TextureKey::width,
191191
&TextureKey::height,
192-
&TextureKey::textureType);
192+
&TextureKey::textureType>(*this);
193193
}
194194
};
195195

src/util/util_struct_hash.h

Lines changed: 56 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -31,79 +31,75 @@
3131
// Hashing `XXH3_64bits(&value, sizeof(value))` is only well-defined when the
3232
// struct has no uninitialized bytes -- otherwise implicit padding between
3333
// members poisons the hash with stack garbage and produces non-deterministic
34-
// results. The helpers in this file convert "no padding bytes" from an
35-
// invariant the author has to remember into one the compiler proves on every
36-
// build, by requiring the call site to enumerate every non-static data member
37-
// of the struct.
38-
//
39-
// All checks in this header are driven by the *types* in the member-pointer
40-
// pack (via sizeof(Ms)), never by the runtime pointer values. The pointer
41-
// values themselves are unused -- they exist solely so the call site reads
42-
// naturally as a list of `&T::field` and the compiler can deduce `Ms...`.
34+
// results. hashStructByMemory() converts "no padding bytes" from an invariant
35+
// the author has to remember into one the compiler proves on every build, by
36+
// requiring the call site to enumerate every non-static data member of the
37+
// struct as non-type template parameters.
4338

4439
namespace dxvk {
4540

46-
// Returns true iff T has no implicit padding bytes, given that the supplied
47-
// member-pointer pack enumerates every non-static data member of T.
48-
//
49-
// Intended for use inside a static_assert at the point T is declared.
50-
// The pack does not need to be in declaration order; only the sum of member
51-
// sizes is compared against sizeof(T).
52-
//
53-
// Example:
54-
// struct Foo { uint64_t a; uint32_t b; uint32_t c; };
55-
// static_assert(hasNoImplicitPadding<Foo>(&Foo::a, &Foo::b, &Foo::c),
56-
// "Foo has padding bytes");
57-
template <typename T, typename... Ms>
58-
constexpr bool hasNoImplicitPadding(Ms T::*...) {
59-
return sizeof(T) == (size_t{0} + ... + sizeof(Ms));
60-
}
41+
namespace hashStructHelpers {
6142

62-
// Hashes a single T as a contiguous byte range via XXH3, with compile-time
63-
// safety checks:
64-
// * T is standard-layout (well-defined memory layout)
65-
// * T is trivially copyable (memcpy semantics)
66-
// * sizeof(T) equals the sum of the member sizes (no implicit padding,
67-
// given the supplied member-pointer pack enumerates every non-static
68-
// data member of T)
69-
//
70-
// Any violation produces a localized compile error pointing at the call.
71-
//
72-
// The caller is still responsible for value-initializing the object
73-
// (e.g. `Foo data{};`) so that any field forgotten in the data-fill code
74-
// is zero rather than stack garbage.
75-
template <typename T, typename... Ms>
76-
XXH64_hash_t hashStructByMemory(const T& data, Ms T::*...) {
77-
static_assert(std::is_standard_layout_v<T>,
78-
"Type must be standard-layout to hash as a memory range.");
79-
static_assert(std::is_trivially_copyable_v<T>,
80-
"Type must be trivially copyable to hash as a memory range.");
81-
static_assert(sizeof(T) == (size_t{0} + ... + sizeof(Ms)),
82-
"sizeof(T) does not match the sum of the listed member "
83-
"sizes. Either a non-static data member of T was omitted "
84-
"from this call, or T contains implicit padding bytes "
85-
"(reorder fields by descending alignment, or add explicit "
86-
"padding members).");
87-
return XXH3_64bits(&data, sizeof(T));
88-
}
43+
template <auto MemberPtr>
44+
struct memberDataSize;
8945

90-
// Hashes `count` contiguous T values as a single byte range via XXH3, with
91-
// the same compile-time safety checks as hashStructByMemory. Useful when
92-
// accumulating trivially-copyable structs into a vector and hashing them
93-
// all at once.
94-
template <typename T, typename... Ms>
95-
XXH64_hash_t hashStructArrayByMemory(const T* data, size_t count, Ms T::*...) {
46+
template <typename T, typename M, M T::* Ptr>
47+
struct memberDataSize<Ptr> {
48+
static constexpr size_t value = sizeof(M);
49+
};
50+
51+
// Compile-time checks only. Instantiating this type triggers static_asserts
52+
// but emits no runtime code when referenced from static_assert.
53+
template <typename T, auto... MemberPtrs>
54+
struct hashStructByMemoryChecks {
9655
static_assert(std::is_standard_layout_v<T>,
9756
"Type must be standard-layout to hash as a memory range.");
9857
static_assert(std::is_trivially_copyable_v<T>,
9958
"Type must be trivially copyable to hash as a memory range.");
100-
static_assert(sizeof(T) == (size_t{0} + ... + sizeof(Ms)),
59+
static_assert(sizeof(T) == (size_t{0} + ... + memberDataSize<MemberPtrs>::value),
10160
"sizeof(T) does not match the sum of the listed member "
10261
"sizes. Either a non-static data member of T was omitted "
10362
"from this call, or T contains implicit padding bytes "
10463
"(reorder fields by descending alignment, or add explicit "
10564
"padding members).");
106-
return XXH3_64bits(data, count * sizeof(T));
107-
}
65+
static constexpr bool value = true;
66+
};
67+
68+
} // namespace hashStructHelpers
10869

70+
// Hashes a single T as a contiguous byte range via XXH3, with compile-time
71+
// safety checks:
72+
// * T is standard-layout (well-defined memory layout)
73+
// * T is trivially copyable (memcpy semantics)
74+
// * sizeof(T) equals the sum of the member sizes (no implicit padding,
75+
// given the supplied member-pointer pack enumerates every non-static
76+
// data member of T)
77+
//
78+
// Any violation produces a localized compile error pointing at the call.
79+
//
80+
// The caller is still responsible for value-initializing the object
81+
// (e.g. `Foo data{};`) so that any field forgotten in the data-fill code
82+
// is zero rather than stack garbage.
83+
//
84+
// Example:
85+
// return hashStructByMemory<Foo, &Foo::a, &Foo::b, &Foo::c>(data);
86+
template <typename T, auto... MemberPtrs>
87+
inline XXH64_hash_t hashStructByMemory(const T& data) {
88+
static_assert(hashStructHelpers::hashStructByMemoryChecks<T, MemberPtrs...>::value);
89+
return XXH3_64bits(&data, sizeof(T));
10990
}
91+
92+
// Hashes `count` contiguous T values as a single byte range via XXH3, with
93+
// the same compile-time safety checks as hashStructByMemory(). Useful when
94+
// accumulating trivially-copyable structs into a vector and hashing them
95+
// all at once.
96+
//
97+
// Example:
98+
// return hashStructArrayByMemory<Foo, &Foo::a, &Foo::b, &Foo::c>(data, count);
99+
template <typename T, auto... MemberPtrs>
100+
inline XXH64_hash_t hashStructArrayByMemory(const T* data, size_t count) {
101+
static_assert(hashStructHelpers::hashStructByMemoryChecks<T, MemberPtrs...>::value);
102+
return XXH3_64bits(data, count * sizeof(T));
103+
}
104+
105+
} // namespace dxvk

0 commit comments

Comments
 (0)