git.s-ol.nu ~forks/DiligentCore / b8bdf72
Reworked combined and immutable sampler validation in resource signature assiduous 6 months ago
7 changed file(s) with 205 addition(s) and 95 deletion(s). Raw diff Collapse all Expand all
5454 if (Desc.UseCombinedTextureSamplers && (Desc.CombinedSamplerSuffix == nullptr || Desc.CombinedSamplerSuffix[0] == '\0'))
5555 LOG_PRS_ERROR_AND_THROW("Desc.UseCombinedTextureSamplers is true, but Desc.CombinedSamplerSuffix is null or empty");
5656
57 std::unordered_map<HashMapStringKey, SHADER_TYPE, HashMapStringKey::Hasher> ResourceShaderStages;
58 // Hash map of resources by name
59 std::unordered_multimap<HashMapStringKey, PipelineResourceDesc, HashMapStringKey::Hasher> ResourcesByName;
60
57
58 // Hash map of all resources by name
59 std::unordered_multimap<HashMapStringKey, const PipelineResourceDesc&, HashMapStringKey::Hasher> Resources;
6160 for (Uint32 i = 0; i < Desc.NumResources; ++i)
6261 {
6362 const auto& Res = Desc.Resources[i];
7473 if (Res.ArraySize == 0)
7574 LOG_PRS_ERROR_AND_THROW("Desc.Resources[", i, "].ArraySize must not be 0.");
7675
77 auto& UsedStages = ResourceShaderStages[Res.Name];
78 if ((UsedStages & Res.ShaderStages) != 0)
79 {
80 LOG_PRS_ERROR_AND_THROW("Multiple resources with name '", Res.Name,
81 "' specify overlapping shader stages. There may be multiple resources with the same name in different shader stages, "
82 "but the stages must not overlap.");
83 }
84 if (Features.SeparablePrograms == DEVICE_FEATURE_STATE_DISABLED && UsedStages != SHADER_TYPE_UNKNOWN)
85 {
86 LOG_PRS_ERROR_AND_THROW("This device does not support separable programs, but there are separate resources with the name '",
87 Res.Name, "' in shader stages ",
88 GetShaderStagesString(Res.ShaderStages), " and ",
89 GetShaderStagesString(UsedStages),
90 ". When separable programs are not supported, every resource is always shared between all stages. "
91 "Use distinct resource names for each stage or define a single resource for all stages.");
92 }
93 UsedStages |= Res.ShaderStages;
76 const auto res_range = Resources.equal_range(Res.Name);
77 for (auto res_it = res_range.first; res_it != res_range.second; ++res_it)
78 {
79 if ((res_it->second.ShaderStages & Res.ShaderStages) != 0)
80 {
81 LOG_PRS_ERROR_AND_THROW("Multiple resources with name '", Res.Name,
82 "' specify overlapping shader stages. There may be multiple resources with the same name in different shader stages, "
83 "but the stages must not overlap.");
84 }
85
86 if (Features.SeparablePrograms == DEVICE_FEATURE_STATE_DISABLED)
87 {
88 VERIFY_EXPR(res_it->second.ShaderStages != SHADER_TYPE_UNKNOWN);
89 LOG_PRS_ERROR_AND_THROW("This device does not support separable programs, but there are separate resources with the name '",
90 Res.Name, "' in shader stages ",
91 GetShaderStagesString(Res.ShaderStages), " and ",
92 GetShaderStagesString(res_it->second.ShaderStages),
93 ". When separable programs are not supported, every resource is always shared between all stages. "
94 "Use distinct resource names for each stage or define a single resource for all stages.");
95 }
96 }
9497
9598 if ((Res.Flags & PIPELINE_RESOURCE_FLAG_RUNTIME_ARRAY) != 0 && !Features.ShaderResourceRuntimeArray)
9699 {
101104 {
102105 LOG_PRS_ERROR_AND_THROW("Incorrect Desc.Resources[", i, "].ResourceType (ACCEL_STRUCT): ray tracing is not supported by device.");
103106 }
104 static_assert(SHADER_RESOURCE_TYPE_LAST == 8, "Please add the new resource type to the switch below");
105107
106108 auto AllowedResourceFlags = GetValidPipelineResourceFlags(Res.ResourceType);
107109 if ((Res.Flags & ~AllowedResourceFlags) != 0)
111113 ": ", GetPipelineResourceFlagsString(AllowedResourceFlags, false, ", "), ".");
112114 }
113115
114 ResourcesByName.emplace(Res.Name, Res);
116 Resources.emplace(Res.Name, Res);
115117
116118 // NB: when creating immutable sampler array, we have to define the sampler as both resource and
117119 // immutable sampler. The sampler will not be exposed as a shader variable though.
125127 //}
126128 }
127129
130 // Hash map of all immutable samplers by name
131 std::unordered_multimap<HashMapStringKey, const ImmutableSamplerDesc&, HashMapStringKey::Hasher> ImtblSamplers;
132 for (Uint32 i = 0; i < Desc.NumImmutableSamplers; ++i)
133 {
134 const auto& SamDesc = Desc.ImmutableSamplers[i];
135 if (SamDesc.SamplerOrTextureName == nullptr)
136 LOG_PRS_ERROR_AND_THROW("Desc.ImmutableSamplers[", i, "].SamplerOrTextureName must not be null.");
137
138 if (SamDesc.SamplerOrTextureName[0] == '\0')
139 LOG_PRS_ERROR_AND_THROW("Desc.ImmutableSamplers[", i, "].SamplerOrTextureName must not be empty.");
140
141 if (SamDesc.ShaderStages == SHADER_TYPE_UNKNOWN)
142 LOG_PRS_ERROR_AND_THROW("Desc.ImmutableSamplers[", i, "].ShaderStages must not be SHADER_TYPE_UNKNOWN.");
143
144 const auto sam_range = ImtblSamplers.equal_range(SamDesc.SamplerOrTextureName);
145 for (auto sam_it = sam_range.first; sam_it != sam_range.second; ++sam_it)
146 {
147 if ((sam_it->second.ShaderStages & SamDesc.ShaderStages) != 0)
148 {
149 LOG_PRS_ERROR_AND_THROW("Multiple immutable samplers with name '", SamDesc.SamplerOrTextureName,
150 "' specify overlapping shader stages. There may be multiple immutable samplers with the same name in different shader stages, "
151 "but the stages must not overlap.");
152 }
153 if (Features.SeparablePrograms == DEVICE_FEATURE_STATE_DISABLED)
154 {
155 VERIFY_EXPR(sam_it->second.ShaderStages != SHADER_TYPE_UNKNOWN);
156 LOG_PRS_ERROR_AND_THROW("This device does not support separable programs, but there are separate immutable samplers with the name '",
157 SamDesc.SamplerOrTextureName, "' in shader stages ",
158 GetShaderStagesString(SamDesc.ShaderStages), " and ",
159 GetShaderStagesString(sam_it->second.ShaderStages),
160 ". When separable programs are not supported, every resource is always shared between all stages. "
161 "Use distinct immutable sampler names for each stage or define a single sampler for all stages.");
162 }
163 }
164
165 ImtblSamplers.emplace(SamDesc.SamplerOrTextureName, SamDesc);
166 }
167
128168 if (Desc.UseCombinedTextureSamplers)
129169 {
130170 VERIFY_EXPR(Desc.CombinedSamplerSuffix != nullptr);
171
172 // List of samplers assigned to some texture
173 std::unordered_multimap<HashMapStringKey, SHADER_TYPE, HashMapStringKey::Hasher> AssignedSamplers;
174 // List of immutable samplers assigned to some texture
175 std::unordered_multimap<HashMapStringKey, SHADER_TYPE, HashMapStringKey::Hasher> AssignedImtblSamplers;
131176 for (Uint32 i = 0; i < Desc.NumResources; ++i)
132177 {
133178 const auto& Res = Desc.Resources[i];
134 if (Res.ResourceType == SHADER_RESOURCE_TYPE_TEXTURE_SRV)
179 if (Res.ResourceType != SHADER_RESOURCE_TYPE_TEXTURE_SRV)
180 {
181 // Only texture SRVs can be combined with samplers
182 continue;
183 }
184
135185 {
136186 const auto AssignedSamplerName = String{Res.Name} + Desc.CombinedSamplerSuffix;
137187
138 auto sam_range = ResourcesByName.equal_range(AssignedSamplerName.c_str());
188 const auto sam_range = Resources.equal_range(AssignedSamplerName.c_str());
139189 for (auto sam_it = sam_range.first; sam_it != sam_range.second; ++sam_it)
140190 {
141191 const auto& Sam = sam_it->second;
148198 LOG_PRS_ERROR_AND_THROW("Resource '", Sam.Name, "' combined with texture '", Res.Name, "' is not a sampler.");
149199 }
150200
151 if (Sam.ShaderStages != Res.ShaderStages)
201 if ((Sam.ShaderStages & Res.ShaderStages) != Res.ShaderStages)
152202 {
153 LOG_PRS_ERROR_AND_THROW("Texture '", Res.Name, "' and sampler '", Sam.Name, "' assigned to it use different shader stages.");
203 LOG_PRS_ERROR_AND_THROW("Texture '", Res.Name, "' is defined for the following shader stages: ", GetShaderStagesString(Res.ShaderStages),
204 ", but sampler '", Sam.Name, "' assigned to it uses only some of these stages: ", GetShaderStagesString(Sam.ShaderStages),
205 ". A resource that is present in multiple shader stages can't be combined with different samplers in different stages. "
206 "Either use separate resources for different stages, or define the sampler for all stages that the resource uses.");
154207 }
155208
156209 if (Sam.VarType != Res.VarType)
160213 ") of sampler '", Sam.Name, "' that is assigned to it.");
161214 }
162215
163 ResourcesByName.erase(sam_it);
216 AssignedSamplers.emplace(Sam.Name, Sam.ShaderStages);
164217
165218 break;
166219 }
167220 }
168221 }
169 }
170
171 for (auto& res_it : ResourcesByName)
172 {
173 if (res_it.second.ResourceType == SHADER_RESOURCE_TYPE_SAMPLER)
174 {
175 LOG_PRS_ERROR_AND_THROW("Sampler '", res_it.second.Name, "' is not assigned to any texture. All samplers must be assigned to textures when combined texture samplers are used.");
176 }
177 }
178 }
179
180 std::unordered_map<HashMapStringKey, SHADER_TYPE, HashMapStringKey::Hasher> ImtblSamShaderStages;
181 for (Uint32 i = 0; i < Desc.NumImmutableSamplers; ++i)
182 {
183 const auto& SamDesc = Desc.ImmutableSamplers[i];
184 if (SamDesc.SamplerOrTextureName == nullptr)
185 LOG_PRS_ERROR_AND_THROW("Desc.ImmutableSamplers[", i, "].SamplerOrTextureName must not be null.");
186
187 if (SamDesc.SamplerOrTextureName[0] == '\0')
188 LOG_PRS_ERROR_AND_THROW("Desc.ImmutableSamplers[", i, "].SamplerOrTextureName must not be empty.");
189
190 if (SamDesc.ShaderStages == SHADER_TYPE_UNKNOWN)
191 LOG_PRS_ERROR_AND_THROW("Desc.ImmutableSamplers[", i, "].ShaderStages must not be SHADER_TYPE_UNKNOWN.");
192
193 auto& UsedStages = ImtblSamShaderStages[SamDesc.SamplerOrTextureName];
194 if ((UsedStages & SamDesc.ShaderStages) != 0)
195 {
196 LOG_PRS_ERROR_AND_THROW("Multiple immutable samplers with name '", SamDesc.SamplerOrTextureName,
197 "' specify overlapping shader stages. There may be multiple immutable samplers with the same name in different shader stages, "
198 "but the stages must not overlap.");
199 }
200 if (Features.SeparablePrograms == DEVICE_FEATURE_STATE_DISABLED && UsedStages != SHADER_TYPE_UNKNOWN)
201 {
202 LOG_PRS_ERROR_AND_THROW("This device does not support separable programs, but there are separate immutable samplers with the name '",
203 SamDesc.SamplerOrTextureName, "' in shader stages ",
204 GetShaderStagesString(SamDesc.ShaderStages), " and ",
205 GetShaderStagesString(UsedStages),
206 ". When separable programs are not supported, every resource is always shared between all stages. "
207 "Use distinct immutable sampler names for each stage or define a single sampler for all stages.");
208 }
209 UsedStages |= SamDesc.ShaderStages;
222
223 {
224 const auto imtbl_sam_range = ImtblSamplers.equal_range(Res.Name);
225 for (auto sam_it = imtbl_sam_range.first; sam_it != imtbl_sam_range.second; ++sam_it)
226 {
227 const auto& Sam = sam_it->second;
228 VERIFY_EXPR(strcmp(Sam.SamplerOrTextureName, Res.Name) == 0);
229
230 if ((Sam.ShaderStages & Res.ShaderStages) != 0)
231 {
232 if ((Sam.ShaderStages & Res.ShaderStages) != Res.ShaderStages)
233 {
234 LOG_PRS_ERROR_AND_THROW("Texture '", Res.Name, "' is defined for the following shader stages: ", GetShaderStagesString(Res.ShaderStages),
235 ", but immutable sampler that is assigned to it uses only some of these stages: ", GetShaderStagesString(Sam.ShaderStages),
236 ". A resource that is present in multiple shader stages can't be combined with different immutable samples in different stages. "
237 "Either use separate resources for different stages, or define the immutable sampler for all stages that the resource uses.");
238 }
239
240 AssignedImtblSamplers.emplace(Sam.SamplerOrTextureName, Sam.ShaderStages);
241
242 break;
243 }
244 }
245 }
246 }
247
248 for (Uint32 i = 0; i < Desc.NumResources; ++i)
249 {
250 const auto& Res = Desc.Resources[i];
251 if (Res.ResourceType != SHADER_RESOURCE_TYPE_SAMPLER)
252 continue;
253
254 auto assigned_sam_range = AssignedSamplers.equal_range(Res.Name);
255
256 auto it = assigned_sam_range.first;
257 while (it != assigned_sam_range.second)
258 {
259 if (it->second == Res.ShaderStages)
260 break;
261 ++it;
262 }
263 if (it == assigned_sam_range.second)
264 {
265 LOG_WARNING_MESSAGE("Sampler '", Res.Name, "' (", GetShaderStagesString(Res.ShaderStages), ")' is not assigned to any texture. All samplers should be assigned to textures when combined texture samplers are used.");
266 }
267 }
268
269 for (Uint32 i = 0; i < Desc.NumImmutableSamplers; ++i)
270 {
271 const auto& SamDesc = Desc.ImmutableSamplers[i];
272
273 auto assigned_sam_range = AssignedImtblSamplers.equal_range(SamDesc.SamplerOrTextureName);
274
275 auto it = assigned_sam_range.first;
276 while (it != assigned_sam_range.second)
277 {
278 if (it->second == SamDesc.ShaderStages)
279 break;
280 ++it;
281 }
282 if (it == assigned_sam_range.second)
283 {
284 LOG_WARNING_MESSAGE("Immutable sampler '", SamDesc.SamplerOrTextureName, "' (", GetShaderStagesString(SamDesc.ShaderStages), ") is not assigned to any texture or sampler. All immutable samplers should be assigned to textures or samplers when combined texture samplers are used.");
285 }
286 }
210287 }
211288 }
212289
224301 const auto& Sam = ImtblSamplers[s];
225302 if (((Sam.ShaderStages & ShaderStages) != 0) && StreqSuff(ResourceName, Sam.SamplerOrTextureName, SamplerSuffix))
226303 {
227 DEV_CHECK_ERR((Sam.ShaderStages & ShaderStages) == ShaderStages,
228 "Resource '", ResourceName, "' is defined for the following shader stages: ", GetShaderStagesString(ShaderStages),
229 ", but immutable sampler '", Sam.SamplerOrTextureName, "' specifes only some of these stages: ", GetShaderStagesString(Sam.ShaderStages),
230 ". A resource that is present in multiple shader stages can't use different immutable samples in different stages. "
231 "Either use separate resources for different stages, or define the immutable sample for all stages that the resource uses.");
304 VERIFY((Sam.ShaderStages & ShaderStages) == ShaderStages,
305 "Immutable sampler uses only some of the stages that resource '", ResourceName,
306 "' is defined for. This error should've been caught by ValidatePipelineResourceSignatureDesc().");
232307 return s;
233308 }
234309 }
4646 f4Position = Pos[VertId];
4747 }
4848
49 float4 PSMain(in float4 in_f4Color : COLOR,
49 float4 PSMain(in float4 f4Color : COLOR, // Name must match vertex shader output
5050 in float4 f4Position : SV_Position) : SV_Target
5151 {
52 return in_f4Color * VerifyResources();
52 return f4Color * VerifyResources();
5353 }
116116 return m_NeedWARPResourceArrayIndexingBugWorkaround;
117117 }
118118
119 static void PushExpectedErrorSubstring(const char* Str);
119 static void PushExpectedErrorSubstring(const char* Str, bool ClearStack = true);
120120
121121 protected:
122122 NativeWindow CreateNativeWindow();
282282 PipelineResourceSignatureDesc PRSDesc;
283283 PRSDesc.Name = "Invalid assigned sampler shader stage";
284284 PipelineResourceDesc Resources[]{
285 {SHADER_TYPE_PIXEL, "g_Texture", 1, SHADER_RESOURCE_TYPE_TEXTURE_SRV, SHADER_RESOURCE_VARIABLE_TYPE_STATIC},
286 {SHADER_TYPE_VERTEX | SHADER_TYPE_PIXEL, "g_Texture_sampler", 1, SHADER_RESOURCE_TYPE_SAMPLER, SHADER_RESOURCE_VARIABLE_TYPE_STATIC}};
287 PRSDesc.UseCombinedTextureSamplers = true;
288 PRSDesc.Resources = Resources;
289 PRSDesc.NumResources = _countof(Resources);
290 TestCreatePRSFailure(PRSDesc, "Texture 'g_Texture' and sampler 'g_Texture_sampler' assigned to it use different shader stages");
285 {SHADER_TYPE_VERTEX | SHADER_TYPE_PIXEL, "g_Texture", 1, SHADER_RESOURCE_TYPE_TEXTURE_SRV, SHADER_RESOURCE_VARIABLE_TYPE_STATIC},
286 {SHADER_TYPE_PIXEL, "g_Texture_sampler", 1, SHADER_RESOURCE_TYPE_SAMPLER, SHADER_RESOURCE_VARIABLE_TYPE_STATIC}};
287 PRSDesc.UseCombinedTextureSamplers = true;
288 PRSDesc.Resources = Resources;
289 PRSDesc.NumResources = _countof(Resources);
290 TestCreatePRSFailure(PRSDesc, "Texture 'g_Texture' is defined for the following shader stages: SHADER_TYPE_VERTEX, SHADER_TYPE_PIXEL, but sampler 'g_Texture_sampler' assigned to it uses only some of these stages: SHADER_TYPE_PIXEL");
291291 }
292292
293293 TEST(PRSCreationFailureTest, InvalidAssignedSamplerVarType)
303303 TestCreatePRSFailure(PRSDesc, "The type (mutable) of texture resource 'g_Texture' does not match the type (static) of sampler 'g_Texture_sampler' that is assigned to it");
304304 }
305305
306 #if 0 // Unassigned sampler is a warning
306307 TEST(PRSCreationFailureTest, UnassignedSampler)
307308 {
308309 PipelineResourceSignatureDesc PRSDesc;
315316 PRSDesc.NumResources = _countof(Resources);
316317 TestCreatePRSFailure(PRSDesc, "Sampler 'g_Texture2_sampler' is not assigned to any texture");
317318 }
319 #endif
318320
319321 TEST(PRSCreationFailureTest, NullImmutableSamplerName)
320322 {
439441 TestCreatePRSFailure(PRSDesc, "separate immutable samplers with the name 'g_Texture_sampler' in shader stages SHADER_TYPE_VERTEX, SHADER_TYPE_PIXEL and SHADER_TYPE_HULL, SHADER_TYPE_DOMAIN");
440442 }
441443
444 #if 0 // Unassigned immutable sampler is a warning
445 TEST(PRSCreationFailureTest, UnassignedImmutableSampler)
446 {
447 PipelineResourceSignatureDesc PRSDesc;
448 PRSDesc.Name = "Unassigned immutable sampler";
449 PipelineResourceDesc Resources[]{
450 {SHADER_TYPE_PIXEL, "g_Texture", 1, SHADER_RESOURCE_TYPE_TEXTURE_SRV, SHADER_RESOURCE_VARIABLE_TYPE_MUTABLE}};
451 ImmutableSamplerDesc ImmutableSamplers[]{
452 {SHADER_TYPE_PIXEL, "g_Texture", SamplerDesc{}},
453 {SHADER_TYPE_PIXEL, "g_Texture2", SamplerDesc{}}};
454 PRSDesc.ImmutableSamplers = ImmutableSamplers;
455 PRSDesc.NumImmutableSamplers = _countof(ImmutableSamplers);
456
457 PRSDesc.UseCombinedTextureSamplers = true;
458 PRSDesc.Resources = Resources;
459 PRSDesc.NumResources = _countof(Resources);
460 TestCreatePRSFailure(PRSDesc, "Immutable sampler 'g_Texture2' is not assigned to any texture or sampler");
461 }
462 #endif
463
464 TEST(PRSCreationFailureTest, InvalidImmutableSamplerStages)
465 {
466 PipelineResourceSignatureDesc PRSDesc;
467 PRSDesc.Name = "Invalid immutable sampler stages";
468 PipelineResourceDesc Resources[]{
469 {SHADER_TYPE_VERTEX | SHADER_TYPE_PIXEL, "g_Texture", 1, SHADER_RESOURCE_TYPE_TEXTURE_SRV, SHADER_RESOURCE_VARIABLE_TYPE_MUTABLE}};
470 ImmutableSamplerDesc ImmutableSamplers[]{
471 {SHADER_TYPE_VERTEX, "g_Texture", SamplerDesc{}}};
472 PRSDesc.ImmutableSamplers = ImmutableSamplers;
473 PRSDesc.NumImmutableSamplers = _countof(ImmutableSamplers);
474
475 PRSDesc.UseCombinedTextureSamplers = true;
476 PRSDesc.Resources = Resources;
477 PRSDesc.NumResources = _countof(Resources);
478 TestCreatePRSFailure(PRSDesc, "Texture 'g_Texture' is defined for the following shader stages: SHADER_TYPE_VERTEX, SHADER_TYPE_PIXEL, but immutable sampler that is assigned to it uses only some of these stages: SHADER_TYPE_VERTEX");
479 }
480
442481 } // namespace
643643 auto* pDevice = pEnv->GetDevice();
644644 auto* pContext = pEnv->GetDeviceContext();
645645
646 if (!pDevice->GetDeviceCaps().Features.SeparablePrograms)
647 {
648 GTEST_SKIP();
649 }
650
651646 TestingEnvironment::ScopedReset EnvironmentAutoReset;
652647
653648 auto* pSwapChain = pEnv->GetSwapChain();
697692 TEXTURE_ADDRESS_WRAP, TEXTURE_ADDRESS_WRAP, TEXTURE_ADDRESS_WRAP};
698693 ImmutableSamplerDesc ImmutableSamplers[] =
699694 {
700 {SHADER_TYPE_PIXEL, "g_Texture", SamLinearWrapDesc},
701 {SHADER_TYPE_PIXEL, "g_Sampler", SamLinearWrapDesc},
702 {SHADER_TYPE_VERTEX, "g_Texture", SamLinearWrapDesc} //
695 {SHADER_TYPE_VERTEX | SHADER_TYPE_PIXEL, "g_Texture", SamLinearWrapDesc} //
703696 };
704697
705698 PipelineResourceSignatureDesc Desc;
715708 pDevice->CreatePipelineResourceSignature(Desc, &pSignature2);
716709 ASSERT_NE(pSignature2, nullptr);
717710
718 EXPECT_EQ(pSignature2->GetStaticVariableByName(SHADER_TYPE_PIXEL, "g_Sampler"), nullptr);
711 EXPECT_EQ(pSignature2->GetStaticVariableByName(SHADER_TYPE_PIXEL, "g_Texture"), nullptr);
719712 EXPECT_EQ(pSignature2->GetStaticVariableByName(SHADER_TYPE_PIXEL, "g_Texture_sampler"), nullptr);
720713 }
721714
405405 }
406406 else
407407 {
408 ImtblSamplers.emplace_back(SHADER_TYPE_VERTEX | SHADER_TYPE_PIXEL, "g_Sampler", SamplerDesc{});
408 if(!deviceCaps.IsGLDevice())
409 ImtblSamplers.emplace_back(SHADER_TYPE_VERTEX | SHADER_TYPE_PIXEL, "g_Sampler", SamplerDesc{});
409410 }
410411 // clang-format on
411412
102102 }
103103 }
104104
105 void TestingEnvironment::PushExpectedErrorSubstring(const char* Str)
106 {
105 void TestingEnvironment::PushExpectedErrorSubstring(const char* Str, bool ClearStack)
106 {
107 if (ClearStack)
108 m_ExpectedErrorSubstrings.clear();
107109 VERIFY_EXPR(Str != nullptr && Str[0] != '\0');
108110 m_ExpectedErrorSubstrings.push_back(Str);
109111 }