mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-21 21:35:38 +00:00
GPUDevice: Add API version field
Also tie shader caches to API version and device LUID. That way we don't have tons of cache files, and they're regenerated if the GPU/driver changes.
This commit is contained in:
parent
4ec5ca4319
commit
4de1045693
|
@ -111,6 +111,18 @@ void SmallStringBase::shrink_to_fit()
|
|||
m_buffer_size = buffer_size;
|
||||
}
|
||||
|
||||
void SmallStringBase::convert_to_lower_case()
|
||||
{
|
||||
for (u32 i = 0; i < m_length; i++)
|
||||
m_buffer[i] = static_cast<char>(std::tolower(m_buffer[i]));
|
||||
}
|
||||
|
||||
void SmallStringBase::convert_to_upper_case()
|
||||
{
|
||||
for (u32 i = 0; i < m_length; i++)
|
||||
m_buffer[i] = static_cast<char>(std::toupper(m_buffer[i]));
|
||||
}
|
||||
|
||||
std::string_view SmallStringBase::view() const
|
||||
{
|
||||
return (m_length == 0) ? std::string_view() : std::string_view(m_buffer, m_length);
|
||||
|
|
|
@ -188,6 +188,10 @@ public:
|
|||
ALWAYS_INLINE void push_back(value_type val) { append(val); }
|
||||
ALWAYS_INLINE void pop_back() { erase(-1); }
|
||||
|
||||
// case conversion
|
||||
void convert_to_lower_case();
|
||||
void convert_to_upper_case();
|
||||
|
||||
// returns a string view for this string
|
||||
std::string_view view() const;
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ struct WindowInfo;
|
|||
enum class AudioBackend : u8;
|
||||
enum class AudioExpansionMode : u8;
|
||||
enum class AudioStretchMode : u8;
|
||||
enum class RenderAPI : u32;
|
||||
enum class RenderAPI : u8;
|
||||
class AudioStream;
|
||||
class CDImage;
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
enum class RenderAPI : u32;
|
||||
enum class RenderAPI : u8;
|
||||
enum class MediaCaptureBackend : u8;
|
||||
|
||||
struct SettingInfo
|
||||
|
|
|
@ -53,11 +53,6 @@ D3D11Device::~D3D11Device()
|
|||
Assert(!m_device);
|
||||
}
|
||||
|
||||
RenderAPI D3D11Device::GetRenderAPI() const
|
||||
{
|
||||
return RenderAPI::D3D11;
|
||||
}
|
||||
|
||||
bool D3D11Device::HasSurface() const
|
||||
{
|
||||
return static_cast<bool>(m_swap_chain);
|
||||
|
@ -127,7 +122,8 @@ bool D3D11Device::CreateDevice(std::string_view adapter, std::optional<bool> exc
|
|||
INFO_LOG("D3D Adapter: {}", D3DCommon::GetAdapterName(dxgi_adapter.Get()));
|
||||
else
|
||||
ERROR_LOG("Failed to obtain D3D adapter name.");
|
||||
INFO_LOG("Max device feature level: {}", D3DCommon::GetFeatureLevelString(m_max_feature_level));
|
||||
INFO_LOG("Max device feature level: {}",
|
||||
D3DCommon::GetFeatureLevelString(D3DCommon::GetRenderAPIVersionForFeatureLevel(m_max_feature_level)));
|
||||
|
||||
BOOL allow_tearing_supported = false;
|
||||
hr = m_dxgi_factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported,
|
||||
|
@ -164,6 +160,8 @@ void D3D11Device::SetFeatures(FeatureMask disabled_features)
|
|||
{
|
||||
const D3D_FEATURE_LEVEL feature_level = m_device->GetFeatureLevel();
|
||||
|
||||
m_render_api = RenderAPI::D3D11;
|
||||
m_render_api_version = D3DCommon::GetRenderAPIVersionForFeatureLevel(feature_level);
|
||||
m_max_texture_size = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
|
||||
m_max_multisamples = 1;
|
||||
for (u32 multisamples = 2; multisamples < D3D11_MAX_MULTISAMPLE_SAMPLE_COUNT; multisamples++)
|
||||
|
@ -443,12 +441,11 @@ bool D3D11Device::SupportsExclusiveFullscreen() const
|
|||
|
||||
std::string D3D11Device::GetDriverInfo() const
|
||||
{
|
||||
const D3D_FEATURE_LEVEL fl = m_device->GetFeatureLevel();
|
||||
std::string ret =
|
||||
fmt::format("{} ({})\n", D3DCommon::GetFeatureLevelString(fl), D3DCommon::GetFeatureLevelShaderModelString(fl));
|
||||
std::string ret = fmt::format("{} (Shader Model {})\n", D3DCommon::GetFeatureLevelString(m_render_api_version),
|
||||
D3DCommon::GetShaderModelForFeatureLevelNumber(m_render_api_version));
|
||||
|
||||
ComPtr<IDXGIDevice> dxgi_dev;
|
||||
if (m_device.As(&dxgi_dev))
|
||||
if (SUCCEEDED(m_device.As(&dxgi_dev)))
|
||||
{
|
||||
ComPtr<IDXGIAdapter> dxgi_adapter;
|
||||
if (SUCCEEDED(dxgi_dev->GetAdapter(dxgi_adapter.GetAddressOf())))
|
||||
|
|
|
@ -36,8 +36,6 @@ public:
|
|||
ALWAYS_INLINE static ID3D11DeviceContext1* GetD3DContext() { return GetInstance().m_context.Get(); }
|
||||
ALWAYS_INLINE static D3D_FEATURE_LEVEL GetMaxFeatureLevel() { return GetInstance().m_max_feature_level; }
|
||||
|
||||
RenderAPI GetRenderAPI() const override;
|
||||
|
||||
bool HasSurface() const override;
|
||||
|
||||
bool UpdateWindow() override;
|
||||
|
|
|
@ -100,7 +100,7 @@ std::unique_ptr<GPUShader> D3D11Device::CreateShaderFromSource(GPUShaderStage st
|
|||
std::string_view source, const char* entry_point,
|
||||
DynamicHeapArray<u8>* out_binary, Error* error)
|
||||
{
|
||||
const u32 shader_model = D3DCommon::GetShaderModelForFeatureLevel(m_device->GetFeatureLevel());
|
||||
const u32 shader_model = D3DCommon::GetShaderModelForFeatureLevelNumber(m_render_api_version);
|
||||
if (language != GPUShaderLanguage::HLSL)
|
||||
{
|
||||
return TranspileAndCreateShaderFromSource(stage, language, source, entry_point, GPUShaderLanguage::HLSL,
|
||||
|
|
|
@ -147,12 +147,13 @@ bool D3D12Device::CreateDevice(std::string_view adapter, std::optional<bool> exc
|
|||
}
|
||||
|
||||
// Create the actual device.
|
||||
D3D_FEATURE_LEVEL feature_level = D3D_FEATURE_LEVEL_1_0_CORE;
|
||||
for (D3D_FEATURE_LEVEL try_feature_level : {D3D_FEATURE_LEVEL_11_0})
|
||||
{
|
||||
hr = D3D12CreateDevice(m_adapter.Get(), try_feature_level, IID_PPV_ARGS(&m_device));
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
m_feature_level = try_feature_level;
|
||||
feature_level = try_feature_level;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -232,7 +233,7 @@ bool D3D12Device::CreateDevice(std::string_view adapter, std::optional<bool> exc
|
|||
return false;
|
||||
}
|
||||
|
||||
SetFeatures(disabled_features);
|
||||
SetFeatures(feature_level, disabled_features);
|
||||
|
||||
if (!CreateCommandLists(error) || !CreateDescriptorHeaps(error))
|
||||
return false;
|
||||
|
@ -282,10 +283,28 @@ void D3D12Device::DestroyDevice()
|
|||
m_dxgi_factory.Reset();
|
||||
}
|
||||
|
||||
void D3D12Device::GetPipelineCacheHeader(PIPELINE_CACHE_HEADER* hdr)
|
||||
{
|
||||
const LUID adapter_luid = m_device->GetAdapterLuid();
|
||||
std::memcpy(&hdr->adapter_luid, &adapter_luid, sizeof(hdr->adapter_luid));
|
||||
hdr->render_api_version = m_render_api_version;
|
||||
hdr->unused = 0;
|
||||
}
|
||||
|
||||
bool D3D12Device::ReadPipelineCache(std::optional<DynamicHeapArray<u8>> data)
|
||||
{
|
||||
PIPELINE_CACHE_HEADER expected_header;
|
||||
GetPipelineCacheHeader(&expected_header);
|
||||
if (data.has_value() && (data->size() < sizeof(PIPELINE_CACHE_HEADER) ||
|
||||
std::memcmp(data->data(), &expected_header, sizeof(PIPELINE_CACHE_HEADER)) != 0))
|
||||
{
|
||||
WARNING_LOG("Pipeline cache header does not match current device, ignoring.");
|
||||
data.reset();
|
||||
}
|
||||
|
||||
HRESULT hr =
|
||||
m_device->CreatePipelineLibrary(data.has_value() ? data->data() : nullptr, data.has_value() ? data->size() : 0,
|
||||
m_device->CreatePipelineLibrary(data.has_value() ? (data->data() + sizeof(PIPELINE_CACHE_HEADER)) : nullptr,
|
||||
data.has_value() ? (data->size() - sizeof(PIPELINE_CACHE_HEADER)) : 0,
|
||||
IID_PPV_ARGS(m_pipeline_library.ReleaseAndGetAddressOf()));
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
|
@ -328,8 +347,13 @@ bool D3D12Device::GetPipelineCacheData(DynamicHeapArray<u8>* data)
|
|||
return true;
|
||||
}
|
||||
|
||||
data->resize(size);
|
||||
const HRESULT hr = m_pipeline_library->Serialize(data->data(), data->size());
|
||||
PIPELINE_CACHE_HEADER header;
|
||||
GetPipelineCacheHeader(&header);
|
||||
|
||||
data->resize(sizeof(PIPELINE_CACHE_HEADER) + size);
|
||||
std::memcpy(data->data(), &header, sizeof(PIPELINE_CACHE_HEADER));
|
||||
|
||||
const HRESULT hr = m_pipeline_library->Serialize(data->data() + sizeof(PIPELINE_CACHE_HEADER), size);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
ERROR_LOG("Serialize() failed with HRESULT {:08X}", static_cast<unsigned>(hr));
|
||||
|
@ -771,11 +795,6 @@ void D3D12Device::DestroyDeferredObjects(u64 fence_value)
|
|||
}
|
||||
}
|
||||
|
||||
RenderAPI D3D12Device::GetRenderAPI() const
|
||||
{
|
||||
return RenderAPI::D3D12;
|
||||
}
|
||||
|
||||
bool D3D12Device::HasSurface() const
|
||||
{
|
||||
return static_cast<bool>(m_swap_chain);
|
||||
|
@ -1070,8 +1089,8 @@ bool D3D12Device::SupportsTextureFormat(GPUTexture::Format format) const
|
|||
|
||||
std::string D3D12Device::GetDriverInfo() const
|
||||
{
|
||||
std::string ret = fmt::format("{} ({})\n", D3DCommon::GetFeatureLevelString(m_feature_level),
|
||||
D3DCommon::GetFeatureLevelShaderModelString(m_feature_level));
|
||||
std::string ret = fmt::format("{} (Shader Model {})\n", D3DCommon::GetFeatureLevelString(m_render_api_version),
|
||||
D3DCommon::GetShaderModelForFeatureLevelNumber(m_render_api_version));
|
||||
|
||||
DXGI_ADAPTER_DESC desc;
|
||||
if (m_adapter && SUCCEEDED(m_adapter->GetDesc(&desc)))
|
||||
|
@ -1234,8 +1253,10 @@ void D3D12Device::InsertDebugMessage(const char* msg)
|
|||
#endif
|
||||
}
|
||||
|
||||
void D3D12Device::SetFeatures(FeatureMask disabled_features)
|
||||
void D3D12Device::SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disabled_features)
|
||||
{
|
||||
m_render_api = RenderAPI::D3D12;
|
||||
m_render_api_version = D3DCommon::GetRenderAPIVersionForFeatureLevel(feature_level);
|
||||
m_max_texture_size = D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION;
|
||||
m_max_multisamples = 1;
|
||||
for (u32 multisamples = 2; multisamples < D3D12_MAX_MULTISAMPLE_SAMPLE_COUNT; multisamples++)
|
||||
|
|
|
@ -58,8 +58,6 @@ public:
|
|||
D3D12Device();
|
||||
~D3D12Device() override;
|
||||
|
||||
RenderAPI GetRenderAPI() const override;
|
||||
|
||||
bool HasSurface() const override;
|
||||
|
||||
bool UpdateWindow() override;
|
||||
|
@ -220,9 +218,18 @@ private:
|
|||
bool has_timestamp_query = false;
|
||||
};
|
||||
|
||||
struct PIPELINE_CACHE_HEADER
|
||||
{
|
||||
u64 adapter_luid;
|
||||
u32 render_api_version;
|
||||
u32 unused;
|
||||
};
|
||||
static_assert(sizeof(PIPELINE_CACHE_HEADER) == 16);
|
||||
|
||||
using SamplerMap = std::unordered_map<u64, D3D12DescriptorHandle>;
|
||||
|
||||
void SetFeatures(FeatureMask disabled_features);
|
||||
void GetPipelineCacheHeader(PIPELINE_CACHE_HEADER* hdr);
|
||||
void SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disabled_features);
|
||||
|
||||
u32 GetSwapChainBufferCount() const;
|
||||
bool CreateSwapChain(Error* error);
|
||||
|
@ -291,7 +298,6 @@ private:
|
|||
|
||||
std::array<CommandList, NUM_COMMAND_LISTS> m_command_lists;
|
||||
u32 m_current_command_list = NUM_COMMAND_LISTS - 1;
|
||||
D3D_FEATURE_LEVEL m_feature_level = D3D_FEATURE_LEVEL_11_0;
|
||||
|
||||
ComPtr<IDXGIFactory5> m_dxgi_factory;
|
||||
ComPtr<IDXGISwapChain1> m_swap_chain;
|
||||
|
|
|
@ -38,7 +38,7 @@ std::unique_ptr<GPUShader> D3D12Device::CreateShaderFromSource(GPUShaderStage st
|
|||
std::string_view source, const char* entry_point,
|
||||
DynamicHeapArray<u8>* out_binary, Error* error)
|
||||
{
|
||||
const u32 shader_model = D3DCommon::GetShaderModelForFeatureLevel(m_feature_level);
|
||||
const u32 shader_model = D3DCommon::GetShaderModelForFeatureLevelNumber(m_render_api_version);
|
||||
if (language != GPUShaderLanguage::HLSL)
|
||||
{
|
||||
return TranspileAndCreateShaderFromSource(stage, language, source, entry_point, GPUShaderLanguage::HLSL,
|
||||
|
|
|
@ -19,47 +19,65 @@
|
|||
|
||||
Log_SetChannel(D3DCommon);
|
||||
|
||||
const char* D3DCommon::GetFeatureLevelString(D3D_FEATURE_LEVEL feature_level)
|
||||
namespace D3DCommon {
|
||||
namespace {
|
||||
struct FeatureLevelTableEntry
|
||||
{
|
||||
static constexpr std::array<std::tuple<D3D_FEATURE_LEVEL, const char*>, 11> feature_level_names = {{
|
||||
{D3D_FEATURE_LEVEL_1_0_CORE, "D3D_FEATURE_LEVEL_1_0_CORE"},
|
||||
{D3D_FEATURE_LEVEL_9_1, "D3D_FEATURE_LEVEL_9_1"},
|
||||
{D3D_FEATURE_LEVEL_9_2, "D3D_FEATURE_LEVEL_9_2"},
|
||||
{D3D_FEATURE_LEVEL_9_3, "D3D_FEATURE_LEVEL_9_3"},
|
||||
{D3D_FEATURE_LEVEL_10_0, "D3D_FEATURE_LEVEL_10_0"},
|
||||
{D3D_FEATURE_LEVEL_10_1, "D3D_FEATURE_LEVEL_10_1"},
|
||||
{D3D_FEATURE_LEVEL_11_0, "D3D_FEATURE_LEVEL_11_0"},
|
||||
{D3D_FEATURE_LEVEL_11_1, "D3D_FEATURE_LEVEL_11_1"},
|
||||
{D3D_FEATURE_LEVEL_12_0, "D3D_FEATURE_LEVEL_12_0"},
|
||||
{D3D_FEATURE_LEVEL_12_1, "D3D_FEATURE_LEVEL_12_1"},
|
||||
{D3D_FEATURE_LEVEL_12_2, "D3D_FEATURE_LEVEL_12_2"},
|
||||
}};
|
||||
D3D_FEATURE_LEVEL d3d_feature_level;
|
||||
u16 render_api_version;
|
||||
u16 shader_model_number;
|
||||
const char* feature_level_str;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
for (const auto& [fl, name] : feature_level_names)
|
||||
{
|
||||
if (fl == feature_level)
|
||||
return name;
|
||||
}
|
||||
static constexpr std::array<FeatureLevelTableEntry, 11> s_feature_levels = {{
|
||||
{D3D_FEATURE_LEVEL_1_0_CORE, 100, 40, "D3D_FEATURE_LEVEL_1_0_CORE"},
|
||||
{D3D_FEATURE_LEVEL_9_1, 910, 40, "D3D_FEATURE_LEVEL_9_1"},
|
||||
{D3D_FEATURE_LEVEL_9_2, 920, 40, "D3D_FEATURE_LEVEL_9_2"},
|
||||
{D3D_FEATURE_LEVEL_9_3, 930, 40, "D3D_FEATURE_LEVEL_9_3"},
|
||||
{D3D_FEATURE_LEVEL_10_0, 1000, 40, "D3D_FEATURE_LEVEL_10_0"},
|
||||
{D3D_FEATURE_LEVEL_10_1, 1010, 41, "D3D_FEATURE_LEVEL_10_1"},
|
||||
{D3D_FEATURE_LEVEL_11_0, 1100, 50, "D3D_FEATURE_LEVEL_11_0"},
|
||||
{D3D_FEATURE_LEVEL_11_1, 1110, 50, "D3D_FEATURE_LEVEL_11_1"},
|
||||
{D3D_FEATURE_LEVEL_12_0, 1200, 50, "D3D_FEATURE_LEVEL_12_0"},
|
||||
{D3D_FEATURE_LEVEL_12_1, 1210, 50, "D3D_FEATURE_LEVEL_12_1"},
|
||||
{D3D_FEATURE_LEVEL_12_2, 1220, 50, "D3D_FEATURE_LEVEL_12_2"},
|
||||
}};
|
||||
} // namespace D3DCommon
|
||||
|
||||
return "D3D_FEATURE_LEVEL_UNKNOWN";
|
||||
const char* D3DCommon::GetFeatureLevelString(u32 render_api_version)
|
||||
{
|
||||
const auto iter =
|
||||
std::find_if(s_feature_levels.begin(), s_feature_levels.end(),
|
||||
[&render_api_version](const auto& it) { return it.render_api_version == render_api_version; });
|
||||
return (iter != s_feature_levels.end()) ? iter->feature_level_str : "D3D_FEATURE_LEVEL_UNKNOWN";
|
||||
}
|
||||
|
||||
const char* D3DCommon::GetFeatureLevelShaderModelString(D3D_FEATURE_LEVEL feature_level)
|
||||
u32 D3DCommon::GetRenderAPIVersionForFeatureLevel(D3D_FEATURE_LEVEL feature_level)
|
||||
{
|
||||
static constexpr std::array<std::tuple<D3D_FEATURE_LEVEL, const char*>, 4> feature_level_names = {{
|
||||
{D3D_FEATURE_LEVEL_10_0, "sm40"},
|
||||
{D3D_FEATURE_LEVEL_10_1, "sm41"},
|
||||
{D3D_FEATURE_LEVEL_11_0, "sm50"},
|
||||
{D3D_FEATURE_LEVEL_11_1, "sm50"},
|
||||
}};
|
||||
|
||||
for (const auto& [fl, name] : feature_level_names)
|
||||
const FeatureLevelTableEntry* highest_entry = nullptr;
|
||||
for (const FeatureLevelTableEntry& entry : s_feature_levels)
|
||||
{
|
||||
if (fl == feature_level)
|
||||
return name;
|
||||
if (feature_level >= entry.d3d_feature_level)
|
||||
highest_entry = &entry;
|
||||
}
|
||||
return highest_entry ? highest_entry->render_api_version : 0;
|
||||
}
|
||||
|
||||
return "unk";
|
||||
D3D_FEATURE_LEVEL D3DCommon::GetFeatureLevelForNumber(u32 render_api_version)
|
||||
{
|
||||
const auto iter =
|
||||
std::find_if(s_feature_levels.begin(), s_feature_levels.end(),
|
||||
[&render_api_version](const auto& it) { return it.render_api_version == render_api_version; });
|
||||
return (iter != s_feature_levels.end()) ? iter->d3d_feature_level : D3D_FEATURE_LEVEL_1_0_CORE;
|
||||
}
|
||||
|
||||
u32 D3DCommon::GetShaderModelForFeatureLevelNumber(u32 render_api_version)
|
||||
{
|
||||
const auto iter =
|
||||
std::find_if(s_feature_levels.begin(), s_feature_levels.end(),
|
||||
[&render_api_version](const auto& it) { return it.render_api_version == render_api_version; });
|
||||
return (iter != s_feature_levels.end()) ? iter->shader_model_number : 40;
|
||||
}
|
||||
|
||||
D3D_FEATURE_LEVEL D3DCommon::GetDeviceMaxFeatureLevel(IDXGIAdapter1* adapter)
|
||||
|
@ -379,23 +397,6 @@ std::string D3DCommon::GetDriverVersionFromLUID(const LUID& luid)
|
|||
return ret;
|
||||
}
|
||||
|
||||
u32 D3DCommon::GetShaderModelForFeatureLevel(D3D_FEATURE_LEVEL feature_level)
|
||||
{
|
||||
switch (feature_level)
|
||||
{
|
||||
case D3D_FEATURE_LEVEL_10_0:
|
||||
return 40;
|
||||
|
||||
case D3D_FEATURE_LEVEL_10_1:
|
||||
return 41;
|
||||
|
||||
case D3D_FEATURE_LEVEL_11_0:
|
||||
case D3D_FEATURE_LEVEL_11_1:
|
||||
default:
|
||||
return 50;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<DynamicHeapArray<u8>> D3DCommon::CompileShader(u32 shader_model, bool debug_device, GPUShaderStage stage,
|
||||
std::string_view source, const char* entry_point,
|
||||
Error* error)
|
||||
|
|
|
@ -25,8 +25,10 @@ struct DXGI_MODE_DESC;
|
|||
|
||||
namespace D3DCommon {
|
||||
// returns string representation of feature level
|
||||
const char* GetFeatureLevelString(D3D_FEATURE_LEVEL feature_level);
|
||||
const char* GetFeatureLevelShaderModelString(D3D_FEATURE_LEVEL feature_level);
|
||||
const char* GetFeatureLevelString(u32 render_api_version);
|
||||
u32 GetRenderAPIVersionForFeatureLevel(D3D_FEATURE_LEVEL feature_level);
|
||||
D3D_FEATURE_LEVEL GetFeatureLevelForNumber(u32 render_api_version);
|
||||
u32 GetShaderModelForFeatureLevelNumber(u32 render_api_version);
|
||||
|
||||
// returns max feature level of a device
|
||||
D3D_FEATURE_LEVEL GetDeviceMaxFeatureLevel(IDXGIAdapter1* adapter);
|
||||
|
@ -57,8 +59,6 @@ std::string GetAdapterName(IDXGIAdapter1* adapter);
|
|||
// returns the driver version from the registry as a string
|
||||
std::string GetDriverVersionFromLUID(const LUID& luid);
|
||||
|
||||
u32 GetShaderModelForFeatureLevel(D3D_FEATURE_LEVEL feature_level);
|
||||
|
||||
std::optional<DynamicHeapArray<u8>> CompileShader(u32 shader_model, bool debug_device, GPUShaderStage stage,
|
||||
std::string_view source, const char* entry_point, Error* error);
|
||||
|
||||
|
|
|
@ -367,6 +367,7 @@ bool GPUDevice::Create(std::string_view adapter, std::string_view shader_cache_p
|
|||
return false;
|
||||
}
|
||||
|
||||
INFO_LOG("Render API: {} Version {}", RenderAPIToString(m_render_api), m_render_api_version);
|
||||
INFO_LOG("Graphics Driver Info:\n{}", GetDriverInfo());
|
||||
|
||||
OpenShaderCache(shader_cache_path, shader_cache_version);
|
||||
|
@ -401,7 +402,7 @@ void GPUDevice::OpenShaderCache(std::string_view base_path, u32 version)
|
|||
{
|
||||
const std::string basename = GetShaderCacheBaseName("shaders");
|
||||
const std::string filename = Path::Combine(base_path, basename);
|
||||
if (!m_shader_cache.Open(filename.c_str(), version))
|
||||
if (!m_shader_cache.Open(filename.c_str(), m_render_api_version, version))
|
||||
{
|
||||
WARNING_LOG("Failed to open shader cache. Creating new cache.");
|
||||
if (!m_shader_cache.Create())
|
||||
|
@ -423,7 +424,7 @@ void GPUDevice::OpenShaderCache(std::string_view base_path, u32 version)
|
|||
else
|
||||
{
|
||||
// Still need to set the version - GL needs it.
|
||||
m_shader_cache.Open(std::string_view(), version);
|
||||
m_shader_cache.Open(std::string_view(), m_render_api_version, version);
|
||||
}
|
||||
|
||||
s_pipeline_cache_path = {};
|
||||
|
@ -473,74 +474,47 @@ std::string GPUDevice::GetShaderCacheBaseName(std::string_view type) const
|
|||
{
|
||||
const std::string_view debug_suffix = m_debug_device ? "_debug" : "";
|
||||
|
||||
std::string ret;
|
||||
switch (GetRenderAPI())
|
||||
{
|
||||
#ifdef _WIN32
|
||||
case RenderAPI::D3D11:
|
||||
ret = fmt::format(
|
||||
"d3d11_{}_{}{}", type,
|
||||
D3DCommon::GetFeatureLevelShaderModelString(D3D11Device::GetInstance().GetD3DDevice()->GetFeatureLevel()),
|
||||
debug_suffix);
|
||||
break;
|
||||
case RenderAPI::D3D12:
|
||||
ret = fmt::format("d3d12_{}{}", type, debug_suffix);
|
||||
break;
|
||||
#endif
|
||||
#ifdef ENABLE_VULKAN
|
||||
case RenderAPI::Vulkan:
|
||||
ret = fmt::format("vulkan_{}{}", type, debug_suffix);
|
||||
break;
|
||||
#endif
|
||||
#ifdef ENABLE_OPENGL
|
||||
case RenderAPI::OpenGL:
|
||||
ret = fmt::format("opengl_{}{}", type, debug_suffix);
|
||||
break;
|
||||
case RenderAPI::OpenGLES:
|
||||
ret = fmt::format("opengles_{}{}", type, debug_suffix);
|
||||
break;
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
case RenderAPI::Metal:
|
||||
ret = fmt::format("metal_{}{}", type, debug_suffix);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
UnreachableCode();
|
||||
break;
|
||||
}
|
||||
TinyString lower_api_name(RenderAPIToString(m_render_api));
|
||||
lower_api_name.convert_to_lower_case();
|
||||
|
||||
return ret;
|
||||
return fmt::format("{}_{}{}", lower_api_name, type, debug_suffix);
|
||||
}
|
||||
|
||||
bool GPUDevice::OpenPipelineCache(const std::string& filename)
|
||||
{
|
||||
Error error;
|
||||
CompressHelpers::OptionalByteBuffer data =
|
||||
CompressHelpers::DecompressFile(CompressHelpers::CompressType::Zstandard, filename.c_str(), std::nullopt, &error);
|
||||
s_pipeline_cache_size = 0;
|
||||
s_pipeline_cache_hash = {};
|
||||
|
||||
if (data.has_value())
|
||||
Error error;
|
||||
CompressHelpers::OptionalByteBuffer data;
|
||||
if (FileSystem::FileExists(filename.c_str()))
|
||||
{
|
||||
s_pipeline_cache_size = data->size();
|
||||
s_pipeline_cache_hash = SHA1Digest::GetDigest(data->cspan());
|
||||
data =
|
||||
CompressHelpers::DecompressFile(CompressHelpers::CompressType::Zstandard, filename.c_str(), std::nullopt, &error);
|
||||
if (data.has_value())
|
||||
{
|
||||
s_pipeline_cache_size = data->size();
|
||||
s_pipeline_cache_hash = SHA1Digest::GetDigest(data->cspan());
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG("Failed to load pipeline cache from '{}': {}", Path::GetFileName(filename), error.GetDescription());
|
||||
}
|
||||
}
|
||||
|
||||
INFO_LOG("Loading {} byte pipeline cache with hash {}", s_pipeline_cache_size,
|
||||
SHA1Digest::DigestToString(s_pipeline_cache_hash));
|
||||
|
||||
if (ReadPipelineCache(std::move(data)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG("Failed to load pipeline cache from '{}': {}", Path::GetFileName(filename), error.GetDescription());
|
||||
s_pipeline_cache_size = 0;
|
||||
s_pipeline_cache_hash = {};
|
||||
}
|
||||
|
||||
if (!ReadPipelineCache(std::move(data)))
|
||||
{
|
||||
s_pipeline_cache_size = 0;
|
||||
s_pipeline_cache_hash = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
INFO_LOG("Loaded pipeline cache: {} bytes with hash: {}", s_pipeline_cache_size,
|
||||
SHA1Digest::DigestToString(s_pipeline_cache_hash));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GPUDevice::ReadPipelineCache(std::optional<DynamicHeapArray<u8>> data)
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
class Error;
|
||||
|
||||
enum class RenderAPI : u32
|
||||
enum class RenderAPI : u8
|
||||
{
|
||||
None,
|
||||
D3D11,
|
||||
|
@ -594,6 +594,8 @@ public:
|
|||
}
|
||||
|
||||
ALWAYS_INLINE const Features& GetFeatures() const { return m_features; }
|
||||
ALWAYS_INLINE RenderAPI GetRenderAPI() const { return m_render_api; }
|
||||
ALWAYS_INLINE u32 GetRenderAPIVersion() const { return m_render_api_version; }
|
||||
ALWAYS_INLINE u32 GetMaxTextureSize() const { return m_max_texture_size; }
|
||||
ALWAYS_INLINE u32 GetMaxMultisamples() const { return m_max_multisamples; }
|
||||
|
||||
|
@ -608,8 +610,6 @@ public:
|
|||
|
||||
ALWAYS_INLINE bool IsGPUTimingEnabled() const { return m_gpu_timing_enabled; }
|
||||
|
||||
virtual RenderAPI GetRenderAPI() const = 0;
|
||||
|
||||
bool Create(std::string_view adapter, std::string_view shader_cache_path, u32 shader_cache_version, bool debug_device,
|
||||
GPUVSyncMode vsync, bool allow_present_throttle, std::optional<bool> exclusive_fullscreen_control,
|
||||
FeatureMask disabled_features, Error* error);
|
||||
|
@ -776,6 +776,8 @@ protected:
|
|||
static std::optional<DynamicHeapArray<u8>> OptimizeVulkanSpv(const std::span<const u8> spirv, Error* error);
|
||||
|
||||
Features m_features = {};
|
||||
RenderAPI m_render_api = RenderAPI::None;
|
||||
u32 m_render_api_version = 0;
|
||||
u32 m_max_texture_size = 0;
|
||||
u32 m_max_multisamples = 0;
|
||||
|
||||
|
|
|
@ -18,6 +18,12 @@
|
|||
Log_SetChannel(GPUShaderCache);
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct CacheFileHeader
|
||||
{
|
||||
u32 signature;
|
||||
u32 render_api_version;
|
||||
u32 cache_version;
|
||||
};
|
||||
struct CacheIndexEntry
|
||||
{
|
||||
u8 shader_type;
|
||||
|
@ -34,6 +40,8 @@ struct CacheIndexEntry
|
|||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static constexpr u32 EXPECTED_SIGNATURE = 0x434B5544; // DUKC
|
||||
|
||||
GPUShaderCache::GPUShaderCache() = default;
|
||||
|
||||
GPUShaderCache::~GPUShaderCache()
|
||||
|
@ -59,10 +67,11 @@ std::size_t GPUShaderCache::CacheIndexEntryHash::operator()(const CacheIndexKey&
|
|||
return h;
|
||||
}
|
||||
|
||||
bool GPUShaderCache::Open(std::string_view base_filename, u32 version)
|
||||
bool GPUShaderCache::Open(std::string_view base_filename, u32 render_api_version, u32 cache_version)
|
||||
{
|
||||
m_base_filename = base_filename;
|
||||
m_version = version;
|
||||
m_render_api_version = render_api_version;
|
||||
m_version = cache_version;
|
||||
|
||||
if (base_filename.empty())
|
||||
return true;
|
||||
|
@ -127,7 +136,9 @@ bool GPUShaderCache::CreateNew(const std::string& index_filename, const std::str
|
|||
return false;
|
||||
}
|
||||
|
||||
if (std::fwrite(&m_version, sizeof(m_version), 1, m_index_file) != 1) [[unlikely]]
|
||||
const CacheFileHeader file_header = {
|
||||
.signature = EXPECTED_SIGNATURE, .render_api_version = m_render_api_version, .cache_version = m_version};
|
||||
if (std::fwrite(&file_header, sizeof(file_header), 1, m_index_file) != 1) [[unlikely]]
|
||||
{
|
||||
ERROR_LOG("Failed to write version to index file '{}'", Path::GetFileName(index_filename));
|
||||
std::fclose(m_index_file);
|
||||
|
@ -165,8 +176,10 @@ bool GPUShaderCache::ReadExisting(const std::string& index_filename, const std::
|
|||
return false;
|
||||
}
|
||||
|
||||
u32 file_version = 0;
|
||||
if (std::fread(&file_version, sizeof(file_version), 1, m_index_file) != 1 || file_version != m_version) [[unlikely]]
|
||||
CacheFileHeader file_header;
|
||||
if (std::fread(&file_header, sizeof(file_header), 1, m_index_file) != 1 ||
|
||||
file_header.signature != EXPECTED_SIGNATURE || file_header.render_api_version != m_render_api_version ||
|
||||
file_header.cache_version != m_version) [[unlikely]]
|
||||
{
|
||||
ERROR_LOG("Bad file/data version in '{}'", Path::GetFileName(index_filename));
|
||||
std::fclose(m_index_file);
|
||||
|
|
|
@ -50,7 +50,7 @@ public:
|
|||
|
||||
bool IsOpen() const { return (m_index_file != nullptr); }
|
||||
|
||||
bool Open(std::string_view base_filename, u32 version);
|
||||
bool Open(std::string_view base_filename, u32 render_api_version, u32 cache_version);
|
||||
bool Create();
|
||||
void Close();
|
||||
|
||||
|
@ -77,6 +77,7 @@ private:
|
|||
CacheIndex m_index;
|
||||
|
||||
std::string m_base_filename;
|
||||
u32 m_render_api_version = 0;
|
||||
u32 m_version = 0;
|
||||
|
||||
std::FILE* m_index_file = nullptr;
|
||||
|
|
|
@ -198,8 +198,6 @@ public:
|
|||
MetalDevice();
|
||||
~MetalDevice();
|
||||
|
||||
RenderAPI GetRenderAPI() const override;
|
||||
|
||||
bool HasSurface() const override;
|
||||
|
||||
bool UpdateWindow() override;
|
||||
|
|
|
@ -137,11 +137,6 @@ MetalDevice::~MetalDevice()
|
|||
Assert(m_device == nil);
|
||||
}
|
||||
|
||||
RenderAPI MetalDevice::GetRenderAPI() const
|
||||
{
|
||||
return RenderAPI::Metal;
|
||||
}
|
||||
|
||||
bool MetalDevice::HasSurface() const
|
||||
{
|
||||
return (m_layer != nil);
|
||||
|
@ -234,6 +229,8 @@ bool MetalDevice::CreateDevice(std::string_view adapter, std::optional<bool> exc
|
|||
|
||||
void MetalDevice::SetFeatures(FeatureMask disabled_features)
|
||||
{
|
||||
m_render_api = RenderAPI::Metal;
|
||||
m_render_api_version = 100; // TODO: Make this more meaningful.
|
||||
m_max_texture_size = GetMetalMaxTextureSize(m_device);
|
||||
m_max_multisamples = GetMetalMaxMultisamples(m_device);
|
||||
|
||||
|
|
|
@ -55,11 +55,6 @@ void OpenGLDevice::SetErrorObject(Error* errptr, std::string_view prefix, GLenum
|
|||
Error::SetStringFmt(errptr, "{}GL Error 0x{:04X}", prefix, static_cast<unsigned>(glerr));
|
||||
}
|
||||
|
||||
RenderAPI OpenGLDevice::GetRenderAPI() const
|
||||
{
|
||||
return m_gl_context->IsGLES() ? RenderAPI::OpenGLES : RenderAPI::OpenGL;
|
||||
}
|
||||
|
||||
std::unique_ptr<GPUTexture> OpenGLDevice::CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
|
||||
GPUTexture::Type type, GPUTexture::Format format,
|
||||
const void* data, u32 data_stride)
|
||||
|
@ -347,6 +342,13 @@ bool OpenGLDevice::CheckFeatures(FeatureMask disabled_features)
|
|||
{
|
||||
const bool is_gles = m_gl_context->IsGLES();
|
||||
|
||||
m_render_api = is_gles ? RenderAPI::OpenGLES : RenderAPI::OpenGL;
|
||||
|
||||
GLint major_version = 0, minor_version = 0;
|
||||
glGetIntegerv(GL_MAJOR_VERSION, &major_version);
|
||||
glGetIntegerv(GL_MINOR_VERSION, &minor_version);
|
||||
m_render_api_version = (static_cast<u32>(major_version) * 100u) + (static_cast<u32>(minor_version) * 10u);
|
||||
|
||||
bool vendor_id_amd = false;
|
||||
// bool vendor_id_nvidia = false;
|
||||
bool vendor_id_intel = false;
|
||||
|
|
|
@ -40,8 +40,6 @@ public:
|
|||
static bool ShouldUsePBOsForDownloads();
|
||||
static void SetErrorObject(Error* errptr, std::string_view prefix, GLenum glerr);
|
||||
|
||||
RenderAPI GetRenderAPI() const override;
|
||||
|
||||
bool HasSurface() const override;
|
||||
void DestroySurface() override;
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
|
||||
Log_SetChannel(ReShadeFXShader);
|
||||
|
||||
|
@ -38,7 +39,12 @@ static constexpr s32 DEFAULT_BUFFER_HEIGHT = 2160;
|
|||
|
||||
static RenderAPI GetRenderAPI()
|
||||
{
|
||||
return g_gpu_device ? g_gpu_device->GetRenderAPI() : RenderAPI::D3D11;
|
||||
#ifdef _WIN32
|
||||
static constexpr RenderAPI DEFAULT_RENDER_API = RenderAPI::D3D11;
|
||||
#else
|
||||
static constexpr RenderAPI DEFAULT_RENDER_API = RenderAPI::D3D12;
|
||||
#endif
|
||||
return g_gpu_device ? g_gpu_device->GetRenderAPI() : DEFAULT_RENDER_API;
|
||||
}
|
||||
|
||||
static bool PreprocessorFileExistsCallback(const std::string& path)
|
||||
|
@ -63,37 +69,44 @@ static bool PreprocessorReadFileCallback(const std::string& path, std::string& d
|
|||
return true;
|
||||
}
|
||||
|
||||
static std::unique_ptr<reshadefx::codegen> CreateRFXCodegen()
|
||||
static std::tuple<std::unique_ptr<reshadefx::codegen>, GPUShaderLanguage> CreateRFXCodegen()
|
||||
{
|
||||
const bool debug_info = g_gpu_device ? g_gpu_device->IsDebugDevice() : false;
|
||||
const bool uniforms_to_spec_constants = false;
|
||||
const RenderAPI rapi = GetRenderAPI();
|
||||
[[maybe_unused]] const u32 rapi_version = g_gpu_device ? g_gpu_device->GetRenderAPIVersion() : 0;
|
||||
|
||||
switch (rapi)
|
||||
{
|
||||
case RenderAPI::None:
|
||||
#ifdef _WIN32
|
||||
case RenderAPI::D3D11:
|
||||
case RenderAPI::D3D12:
|
||||
{
|
||||
return std::unique_ptr<reshadefx::codegen>(
|
||||
reshadefx::create_codegen_hlsl(50, debug_info, uniforms_to_spec_constants));
|
||||
return std::make_tuple(std::unique_ptr<reshadefx::codegen>(reshadefx::create_codegen_hlsl(
|
||||
(rapi_version < 1100) ? 40 : 50, debug_info, uniforms_to_spec_constants)),
|
||||
GPUShaderLanguage::HLSL);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
case RenderAPI::Vulkan:
|
||||
case RenderAPI::Metal:
|
||||
{
|
||||
return std::unique_ptr<reshadefx::codegen>(reshadefx::create_codegen_spirv(
|
||||
true, debug_info, uniforms_to_spec_constants, false, (rapi == RenderAPI::Vulkan)));
|
||||
return std::make_tuple(std::unique_ptr<reshadefx::codegen>(reshadefx::create_codegen_spirv(
|
||||
true, debug_info, uniforms_to_spec_constants, false, (rapi == RenderAPI::Vulkan))),
|
||||
GPUShaderLanguage::SPV);
|
||||
}
|
||||
|
||||
case RenderAPI::OpenGL:
|
||||
case RenderAPI::OpenGLES:
|
||||
default:
|
||||
{
|
||||
return std::unique_ptr<reshadefx::codegen>(
|
||||
reshadefx::create_codegen_glsl(ShaderGen::GetGLSLVersion(rapi), (rapi == RenderAPI::OpenGLES), false,
|
||||
debug_info, uniforms_to_spec_constants, false, true));
|
||||
return std::make_tuple(std::unique_ptr<reshadefx::codegen>(reshadefx::create_codegen_glsl(
|
||||
g_gpu_device ? ShaderGen::GetGLSLVersion(rapi) : 460, (rapi == RenderAPI::OpenGLES),
|
||||
false, debug_info, uniforms_to_spec_constants, false, true)),
|
||||
(rapi == RenderAPI::OpenGLES) ? GPUShaderLanguage::GLSLES : GPUShaderLanguage::GLSL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -308,9 +321,7 @@ bool PostProcessing::ReShadeFXShader::LoadFromString(std::string name, std::stri
|
|||
code.push_back('\n');
|
||||
|
||||
// TODO: This could use spv, it's probably fastest.
|
||||
std::unique_ptr<reshadefx::codegen> cg = CreateRFXCodegen();
|
||||
if (!cg)
|
||||
return false;
|
||||
const auto& [cg, cg_language] = CreateRFXCodegen();
|
||||
|
||||
if (!CreateModule(only_config ? DEFAULT_BUFFER_WIDTH : g_gpu_device->GetWindowWidth(),
|
||||
only_config ? DEFAULT_BUFFER_HEIGHT : g_gpu_device->GetWindowHeight(), cg.get(), std::move(code),
|
||||
|
@ -1319,9 +1330,7 @@ bool PostProcessing::ReShadeFXShader::CompilePipeline(GPUTexture::Format format,
|
|||
if (fxcode.empty() || fxcode.back() != '\n')
|
||||
fxcode.push_back('\n');
|
||||
|
||||
std::unique_ptr<reshadefx::codegen> cg = CreateRFXCodegen();
|
||||
if (!cg)
|
||||
return false;
|
||||
const auto& [cg, cg_language] = CreateRFXCodegen();
|
||||
|
||||
Error error;
|
||||
if (!CreateModule(width, height, cg.get(), std::move(fxcode), &error))
|
||||
|
@ -1339,16 +1348,13 @@ bool PostProcessing::ReShadeFXShader::CompilePipeline(GPUTexture::Format format,
|
|||
return false;
|
||||
}
|
||||
|
||||
const RenderAPI api = g_gpu_device->GetRenderAPI();
|
||||
auto get_shader = [api, &cg](const std::string& name, const std::span<Sampler> samplers, GPUShaderStage stage) {
|
||||
auto get_shader = [cg_language, &cg](const std::string& name, const std::span<Sampler> samplers,
|
||||
GPUShaderStage stage) {
|
||||
const std::string real_code = cg->finalize_code_for_entry_point(name);
|
||||
const GPUShaderLanguage lang = (api == RenderAPI::Vulkan || api == RenderAPI::Metal) ?
|
||||
GPUShaderLanguage::SPV :
|
||||
ShaderGen::GetShaderLanguageForAPI(api);
|
||||
const char* entry_point = (lang == GPUShaderLanguage::HLSL) ? name.c_str() : "main";
|
||||
const char* entry_point = (cg_language == GPUShaderLanguage::HLSL) ? name.c_str() : "main";
|
||||
|
||||
Error error;
|
||||
std::unique_ptr<GPUShader> sshader = g_gpu_device->CreateShader(stage, lang, real_code, &error, entry_point);
|
||||
std::unique_ptr<GPUShader> sshader = g_gpu_device->CreateShader(stage, cg_language, real_code, &error, entry_point);
|
||||
if (!sshader)
|
||||
ERROR_LOG("Failed to compile function '{}': {}", name, error.GetDescription());
|
||||
|
||||
|
|
|
@ -1889,11 +1889,6 @@ bool VulkanDevice::IsSuitableDefaultRenderer()
|
|||
#endif
|
||||
}
|
||||
|
||||
RenderAPI VulkanDevice::GetRenderAPI() const
|
||||
{
|
||||
return RenderAPI::Vulkan;
|
||||
}
|
||||
|
||||
bool VulkanDevice::HasSurface() const
|
||||
{
|
||||
return static_cast<bool>(m_swap_chain);
|
||||
|
@ -2507,6 +2502,10 @@ u32 VulkanDevice::GetMaxMultisamples(VkPhysicalDevice physical_device, const VkP
|
|||
|
||||
void VulkanDevice::SetFeatures(FeatureMask disabled_features, const VkPhysicalDeviceFeatures& vk_features)
|
||||
{
|
||||
const u32 store_api_version = std::min(m_device_properties.apiVersion, VK_API_VERSION_1_1);
|
||||
m_render_api = RenderAPI::Vulkan;
|
||||
m_render_api_version = (VK_API_VERSION_MAJOR(store_api_version) * 100u) +
|
||||
(VK_API_VERSION_MINOR(store_api_version) * 10u) + (VK_API_VERSION_PATCH(store_api_version));
|
||||
m_max_texture_size =
|
||||
std::min(m_device_properties.limits.maxImageDimension2D, m_device_properties.limits.maxFramebufferWidth);
|
||||
m_max_multisamples = GetMaxMultisamples(m_physical_device, m_device_properties);
|
||||
|
|
|
@ -75,8 +75,6 @@ public:
|
|||
static GPUList EnumerateGPUs();
|
||||
static AdapterInfoList GetAdapterList();
|
||||
|
||||
RenderAPI GetRenderAPI() const override;
|
||||
|
||||
bool HasSurface() const override;
|
||||
|
||||
bool UpdateWindow() override;
|
||||
|
|
Loading…
Reference in a new issue