|
1 | 1 | /* |
2 | | - * Copyright 2019-2025 Diligent Graphics LLC |
| 2 | + * Copyright 2019-2026 Diligent Graphics LLC |
3 | 3 | * |
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | 5 | * you may not use this file except in compliance with the License. |
@@ -1918,4 +1918,258 @@ TEST(RenderStateCacheTest, NonSeparableProgramBindingConflict) |
1918 | 1918 | ASSERT_EQ(pPSO->GetStatus(), PIPELINE_STATE_STATUS_READY); |
1919 | 1919 | } |
1920 | 1920 |
|
| 1921 | + |
| 1922 | +void TestSpecializationConstantsCache() |
| 1923 | +{ |
| 1924 | + auto* pEnv = GPUTestingEnvironment::GetInstance(); |
| 1925 | + auto* pDevice = pEnv->GetDevice(); |
| 1926 | + |
| 1927 | + if (pDevice->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) |
| 1928 | + { |
| 1929 | + GTEST_SKIP() << "Specialization constants are not supported by this device"; |
| 1930 | + } |
| 1931 | + |
| 1932 | + GPUTestingEnvironment::ScopedReset AutoReset; |
| 1933 | + |
| 1934 | + auto CreateSpecConstShaders = [&](IRenderStateCache* pCache, |
| 1935 | + RefCntAutoPtr<IShader>& pVS, |
| 1936 | + RefCntAutoPtr<IShader>& pPS, |
| 1937 | + bool PresentInCache) { |
| 1938 | + ShaderCreateInfo ShaderCI; |
| 1939 | + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL; |
| 1940 | + ShaderCI.ShaderCompiler = pEnv->GetDefaultCompiler(ShaderCI.SourceLanguage); |
| 1941 | + |
| 1942 | + { |
| 1943 | + ShaderCI.Desc = {"SpecConst Cache VS", SHADER_TYPE_VERTEX, true}; |
| 1944 | + ShaderCI.EntryPoint = "main"; |
| 1945 | + ShaderCI.Source = HLSL::DrawTest_ProceduralTriangleVS.c_str(); |
| 1946 | + CreateShader(pCache, ShaderCI, PresentInCache, pVS); |
| 1947 | + ASSERT_TRUE(pVS); |
| 1948 | + } |
| 1949 | + |
| 1950 | + { |
| 1951 | + ShaderCI.Desc = {"SpecConst Cache PS", SHADER_TYPE_PIXEL, true}; |
| 1952 | + ShaderCI.EntryPoint = "main"; |
| 1953 | + ShaderCI.Source = HLSL::DrawTest_PS.c_str(); |
| 1954 | + CreateShader(pCache, ShaderCI, PresentInCache, pPS); |
| 1955 | + ASSERT_TRUE(pPS); |
| 1956 | + } |
| 1957 | + }; |
| 1958 | + |
| 1959 | + auto CreateSpecConstPSO = [&](IRenderStateCache* pCache, |
| 1960 | + bool PresentInCache, |
| 1961 | + IShader* pVS, |
| 1962 | + IShader* pPS, |
| 1963 | + const SpecializationConstant* pSpecConsts, |
| 1964 | + Uint32 NumSpecConsts, |
| 1965 | + RefCntAutoPtr<IPipelineState>& pPSO) { |
| 1966 | + auto* pSwapChain = pEnv->GetSwapChain(); |
| 1967 | + |
| 1968 | + GraphicsPipelineStateCreateInfo PsoCI; |
| 1969 | + PsoCI.PSODesc.Name = "Spec Const Cache Test"; |
| 1970 | + |
| 1971 | + PsoCI.pVS = pVS; |
| 1972 | + PsoCI.pPS = pPS; |
| 1973 | + |
| 1974 | + PsoCI.GraphicsPipeline.NumRenderTargets = 1; |
| 1975 | + PsoCI.GraphicsPipeline.RTVFormats[0] = pSwapChain->GetDesc().ColorBufferFormat; |
| 1976 | + PsoCI.GraphicsPipeline.DepthStencilDesc.DepthEnable = False; |
| 1977 | + |
| 1978 | + PsoCI.pSpecializationConstants = pSpecConsts; |
| 1979 | + PsoCI.NumSpecializationConstants = NumSpecConsts; |
| 1980 | + |
| 1981 | + if (pCache != nullptr) |
| 1982 | + { |
| 1983 | + bool PSOFound = pCache->CreateGraphicsPipelineState(PsoCI, &pPSO); |
| 1984 | + EXPECT_EQ(PSOFound, PresentInCache); |
| 1985 | + } |
| 1986 | + else |
| 1987 | + { |
| 1988 | + pDevice->CreateGraphicsPipelineState(PsoCI, &pPSO); |
| 1989 | + } |
| 1990 | + ASSERT_NE(pPSO, nullptr); |
| 1991 | + }; |
| 1992 | + |
| 1993 | + auto VerifySpecConstPSO = [&](IPipelineState* pPSO) { |
| 1994 | + auto* pCtx = pEnv->GetDeviceContext(); |
| 1995 | + auto* pSwapChain = pEnv->GetSwapChain(); |
| 1996 | + |
| 1997 | + static FastRandFloat rnd{2, 0, 1}; |
| 1998 | + const float ClearColor[] = {rnd(), rnd(), rnd(), rnd()}; |
| 1999 | + RenderDrawCommandReference(pSwapChain, ClearColor); |
| 2000 | + |
| 2001 | + ITextureView* pRTVs[] = {pSwapChain->GetCurrentBackBufferRTV()}; |
| 2002 | + pCtx->SetRenderTargets(1, pRTVs, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); |
| 2003 | + pCtx->ClearRenderTarget(pRTVs[0], ClearColor, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); |
| 2004 | + |
| 2005 | + pCtx->SetPipelineState(pPSO); |
| 2006 | + pCtx->Draw({6, DRAW_FLAG_VERIFY_ALL}); |
| 2007 | + |
| 2008 | + pSwapChain->Present(); |
| 2009 | + }; |
| 2010 | + |
| 2011 | + const float SpecConstValue = 1.0f; |
| 2012 | + SpecializationConstant SpecConsts[] = { |
| 2013 | + {"TestConstant", SHADER_TYPE_VERTEX, sizeof(SpecConstValue), &SpecConstValue}, |
| 2014 | + }; |
| 2015 | + |
| 2016 | + // Create uncached reference PSO |
| 2017 | + RefCntAutoPtr<IShader> pUncachedVS, pUncachedPS; |
| 2018 | + CreateSpecConstShaders(nullptr, pUncachedVS, pUncachedPS, false); |
| 2019 | + ASSERT_NE(pUncachedVS, nullptr); |
| 2020 | + ASSERT_NE(pUncachedPS, nullptr); |
| 2021 | + |
| 2022 | + RefCntAutoPtr<IPipelineState> pRefPSO; |
| 2023 | + CreateSpecConstPSO(nullptr, false, pUncachedVS, pUncachedPS, SpecConsts, _countof(SpecConsts), pRefPSO); |
| 2024 | + ASSERT_NE(pRefPSO, nullptr); |
| 2025 | + |
| 2026 | + RefCntAutoPtr<IDataBlob> pData; |
| 2027 | + for (Uint32 pass = 0; pass < 3; ++pass) |
| 2028 | + { |
| 2029 | + // 0: empty cache |
| 2030 | + // 1: loaded cache |
| 2031 | + // 2: reloaded cache (loaded -> stored -> loaded) |
| 2032 | + |
| 2033 | + auto pCache = CreateCache(pDevice, /*HotReload=*/false, pData); |
| 2034 | + ASSERT_TRUE(pCache); |
| 2035 | + |
| 2036 | + RefCntAutoPtr<IShader> pVS, pPS; |
| 2037 | + CreateSpecConstShaders(pCache, pVS, pPS, pData != nullptr); |
| 2038 | + ASSERT_NE(pVS, nullptr); |
| 2039 | + ASSERT_NE(pPS, nullptr); |
| 2040 | + |
| 2041 | + RefCntAutoPtr<IPipelineState> pPSO; |
| 2042 | + CreateSpecConstPSO(pCache, pData != nullptr, pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO); |
| 2043 | + ASSERT_NE(pPSO, nullptr); |
| 2044 | + ASSERT_EQ(pPSO->GetStatus(), PIPELINE_STATE_STATUS_READY); |
| 2045 | + |
| 2046 | + VerifySpecConstPSO(pPSO); |
| 2047 | + |
| 2048 | + // Request the same PSO again - should be found in cache |
| 2049 | + { |
| 2050 | + RefCntAutoPtr<IPipelineState> pPSO2; |
| 2051 | + CreateSpecConstPSO(pCache, true, pVS, pPS, SpecConsts, _countof(SpecConsts), pPSO2); |
| 2052 | + EXPECT_EQ(pPSO, pPSO2); |
| 2053 | + } |
| 2054 | + |
| 2055 | + pData.Release(); |
| 2056 | + pCache->WriteToBlob(pass == 0 ? ContentVersion : ~0u, &pData); |
| 2057 | + } |
| 2058 | +} |
| 2059 | + |
| 2060 | +TEST(RenderStateCacheTest, SpecializationConstants) |
| 2061 | +{ |
| 2062 | + TestSpecializationConstantsCache(); |
| 2063 | +} |
| 2064 | + |
| 2065 | + |
| 2066 | +TEST(RenderStateCacheTest, SpecializationConstants_DistinctEntries) |
| 2067 | +{ |
| 2068 | + auto* pEnv = GPUTestingEnvironment::GetInstance(); |
| 2069 | + auto* pDevice = pEnv->GetDevice(); |
| 2070 | + |
| 2071 | + if (pDevice->GetDeviceInfo().Features.SpecializationConstants != DEVICE_FEATURE_STATE_ENABLED) |
| 2072 | + { |
| 2073 | + GTEST_SKIP() << "Specialization constants are not supported by this device"; |
| 2074 | + } |
| 2075 | + |
| 2076 | + GPUTestingEnvironment::ScopedReset AutoReset; |
| 2077 | + |
| 2078 | + auto* pSwapChain = pEnv->GetSwapChain(); |
| 2079 | + |
| 2080 | + const float ValueA = 1.0f; |
| 2081 | + SpecializationConstant SpecConstsA[] = { |
| 2082 | + {"TestConstant", SHADER_TYPE_VERTEX, sizeof(ValueA), &ValueA}, |
| 2083 | + }; |
| 2084 | + |
| 2085 | + const float ValueB = 2.0f; |
| 2086 | + SpecializationConstant SpecConstsB[] = { |
| 2087 | + {"TestConstant", SHADER_TYPE_VERTEX, sizeof(ValueB), &ValueB}, |
| 2088 | + }; |
| 2089 | + |
| 2090 | + RefCntAutoPtr<IDataBlob> pData; |
| 2091 | + for (Uint32 pass = 0; pass < 3; ++pass) |
| 2092 | + { |
| 2093 | + // 0: empty cache |
| 2094 | + // 1: loaded cache |
| 2095 | + // 2: reloaded cache (loaded -> stored -> loaded) |
| 2096 | + |
| 2097 | + auto pCache = CreateCache(pDevice, /*HotReload=*/false, pData); |
| 2098 | + ASSERT_TRUE(pCache); |
| 2099 | + |
| 2100 | + // Create shaders |
| 2101 | + ShaderCreateInfo ShaderCI; |
| 2102 | + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL; |
| 2103 | + ShaderCI.ShaderCompiler = pEnv->GetDefaultCompiler(ShaderCI.SourceLanguage); |
| 2104 | + |
| 2105 | + RefCntAutoPtr<IShader> pVS, pPS; |
| 2106 | + { |
| 2107 | + ShaderCI.Desc = {"SpecConst Distinct VS", SHADER_TYPE_VERTEX, true}; |
| 2108 | + ShaderCI.EntryPoint = "main"; |
| 2109 | + ShaderCI.Source = HLSL::DrawTest_ProceduralTriangleVS.c_str(); |
| 2110 | + CreateShader(pCache, ShaderCI, pData != nullptr, pVS); |
| 2111 | + ASSERT_TRUE(pVS); |
| 2112 | + } |
| 2113 | + { |
| 2114 | + ShaderCI.Desc = {"SpecConst Distinct PS", SHADER_TYPE_PIXEL, true}; |
| 2115 | + ShaderCI.EntryPoint = "main"; |
| 2116 | + ShaderCI.Source = HLSL::DrawTest_PS.c_str(); |
| 2117 | + CreateShader(pCache, ShaderCI, pData != nullptr, pPS); |
| 2118 | + ASSERT_TRUE(pPS); |
| 2119 | + } |
| 2120 | + |
| 2121 | + auto CreatePSO = [&](const SpecializationConstant* pSpecConsts, |
| 2122 | + Uint32 NumSpecConsts, |
| 2123 | + bool PresentInCache) -> RefCntAutoPtr<IPipelineState> { |
| 2124 | + GraphicsPipelineStateCreateInfo PsoCI; |
| 2125 | + PsoCI.PSODesc.Name = "Spec Const Distinct Test"; |
| 2126 | + |
| 2127 | + PsoCI.pVS = pVS; |
| 2128 | + PsoCI.pPS = pPS; |
| 2129 | + |
| 2130 | + PsoCI.GraphicsPipeline.NumRenderTargets = 1; |
| 2131 | + PsoCI.GraphicsPipeline.RTVFormats[0] = pSwapChain->GetDesc().ColorBufferFormat; |
| 2132 | + PsoCI.GraphicsPipeline.DepthStencilDesc.DepthEnable = False; |
| 2133 | + |
| 2134 | + PsoCI.pSpecializationConstants = pSpecConsts; |
| 2135 | + PsoCI.NumSpecializationConstants = NumSpecConsts; |
| 2136 | + |
| 2137 | + RefCntAutoPtr<IPipelineState> pPSO; |
| 2138 | + bool PSOFound = pCache->CreateGraphicsPipelineState(PsoCI, &pPSO); |
| 2139 | + EXPECT_EQ(PSOFound, PresentInCache); |
| 2140 | + return pPSO; |
| 2141 | + }; |
| 2142 | + |
| 2143 | + // Create PSO with value A |
| 2144 | + auto pPSO_A = CreatePSO(SpecConstsA, _countof(SpecConstsA), pData != nullptr); |
| 2145 | + ASSERT_NE(pPSO_A, nullptr); |
| 2146 | + ASSERT_EQ(pPSO_A->GetStatus(), PIPELINE_STATE_STATUS_READY); |
| 2147 | + |
| 2148 | + // Create PSO with value B (different value, same name) |
| 2149 | + auto pPSO_B = CreatePSO(SpecConstsB, _countof(SpecConstsB), pData != nullptr); |
| 2150 | + ASSERT_NE(pPSO_B, nullptr); |
| 2151 | + ASSERT_EQ(pPSO_B->GetStatus(), PIPELINE_STATE_STATUS_READY); |
| 2152 | + |
| 2153 | + // PSOs must be different objects (different spec constant values => different hash) |
| 2154 | + EXPECT_NE(pPSO_A, pPSO_B); |
| 2155 | + |
| 2156 | + // Request PSO A again - should find exact match in cache |
| 2157 | + auto pPSO_A2 = CreatePSO(SpecConstsA, _countof(SpecConstsA), true); |
| 2158 | + EXPECT_EQ(pPSO_A, pPSO_A2); |
| 2159 | + |
| 2160 | + // Request PSO B again - should find exact match in cache |
| 2161 | + auto pPSO_B2 = CreatePSO(SpecConstsB, _countof(SpecConstsB), true); |
| 2162 | + EXPECT_EQ(pPSO_B, pPSO_B2); |
| 2163 | + |
| 2164 | + // A PSO with no spec constants must also be distinct from both A and B |
| 2165 | + auto pPSO_None = CreatePSO(nullptr, 0, pData != nullptr); |
| 2166 | + ASSERT_NE(pPSO_None, nullptr); |
| 2167 | + EXPECT_NE(pPSO_None, pPSO_A); |
| 2168 | + EXPECT_NE(pPSO_None, pPSO_B); |
| 2169 | + |
| 2170 | + pData.Release(); |
| 2171 | + pCache->WriteToBlob(pass == 0 ? ContentVersion : ~0u, &pData); |
| 2172 | + } |
| 2173 | +} |
| 2174 | + |
1921 | 2175 | } // namespace |
0 commit comments