mirror of
https://github.com/Vita3K/Vita3K.git
synced 2026-07-11 01:34:23 +02:00
renderer/vulkan: Implement async pipeline compilation (#3145)
This commit is contained in:
@@ -74,7 +74,7 @@ jobs:
|
||||
sudo add-apt-repository -y ppa:mhier/libboost-latest
|
||||
sudo add-apt-repository universe
|
||||
sudo apt update
|
||||
sudo apt -y install ccache libboost-filesystem1.81-dev libboost-program-options1.81-dev libboost-system1.81-dev libgtk-3-dev libsdl2-dev ninja-build libfuse2
|
||||
sudo apt -y install ccache libboost-filesystem1.83-dev libboost-program-options1.83-dev libboost-system1.83-dev libgtk-3-dev libsdl2-dev ninja-build libfuse2
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
run: |
|
||||
sudo add-apt-repository -y ppa:mhier/libboost-latest
|
||||
sudo apt update
|
||||
sudo apt -y install ccache libboost-filesystem1.81-dev libboost-program-options1.81-dev libboost-system1.81-dev libgtk-3-dev libsdl2-dev ninja-build
|
||||
sudo apt -y install ccache libboost-filesystem1.83-dev libboost-program-options1.83-dev libboost-system1.83-dev libgtk-3-dev libsdl2-dev ninja-build
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
|
||||
@@ -96,3 +96,6 @@
|
||||
[submodule "external/cubeb"]
|
||||
path = external/cubeb
|
||||
url = https://github.com/mozilla/cubeb.git
|
||||
[submodule "external/concurrentqueue"]
|
||||
path = external/concurrentqueue
|
||||
url = https://github.com/cameron314/concurrentqueue.git
|
||||
|
||||
Vendored
+3
@@ -78,6 +78,9 @@ target_include_directories(googletest PUBLIC googletest/googletest/include)
|
||||
target_include_directories(googletest PRIVATE googletest/googletest)
|
||||
target_compile_definitions(googletest PUBLIC GTEST_HAS_PTHREAD=0)
|
||||
|
||||
add_library(concurrentqueue INTERFACE)
|
||||
target_include_directories(concurrentqueue INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/concurrentqueue")
|
||||
|
||||
add_subdirectory(libfat16)
|
||||
|
||||
# The imgui target is including both imgui and imgui_club.
|
||||
|
||||
+1
Submodule external/concurrentqueue added at 6dd38b8a1d
@@ -13,4 +13,4 @@ target_link_libraries(app PUBLIC emuenv mem)
|
||||
if(USE_DISCORD_RICH_PRESENCE)
|
||||
target_link_libraries(app PUBLIC discord-rpc)
|
||||
endif()
|
||||
target_link_libraries(app PRIVATE audio config display gdbstub gui io ngs renderer)
|
||||
target_link_libraries(app PRIVATE audio config display gdbstub gui io ngs renderer concurrentqueue)
|
||||
|
||||
@@ -329,7 +329,9 @@ bool init(EmuEnvState &state, Config &cfg, const Root &root_paths) {
|
||||
}
|
||||
|
||||
bool late_init(EmuEnvState &state) {
|
||||
state.renderer->late_init(state.cfg, state.app_path);
|
||||
// note: mem is not initialized yet but that's not an issue
|
||||
// the renderer is not using it yet, just storing it for later uses
|
||||
state.renderer->late_init(state.cfg, state.app_path, state.mem);
|
||||
|
||||
if (!init(state.mem, state.renderer->need_page_table)) {
|
||||
LOG_ERROR("Failed to initialize memory for emulator state!");
|
||||
|
||||
@@ -73,6 +73,7 @@ enum PerfomanceOverleyPosition {
|
||||
code(bool, "v-sync", true, v_sync) \
|
||||
code(int, "anisotropic-filtering", 1, anisotropic_filtering) \
|
||||
code(bool, "texture-cache", true, texture_cache) \
|
||||
code(bool, "async-pipeline-compilation", true, async_pipeline_compilation) \
|
||||
code(bool, "show-compile-shaders", true, show_compile_shaders) \
|
||||
code(bool, "hashless-texture-cache", false, hashless_texture_cache) \
|
||||
code(bool, "import-textures", false, import_textures) \
|
||||
|
||||
@@ -126,6 +126,7 @@ public:
|
||||
std::string screen_filter = "Bilinear";
|
||||
bool v_sync = true;
|
||||
int anisotropic_filtering = 1;
|
||||
bool async_pipeline_compilation = true;
|
||||
bool import_textures = false;
|
||||
bool export_textures = false;
|
||||
bool export_as_png = false;
|
||||
|
||||
@@ -51,5 +51,5 @@ add_library(
|
||||
|
||||
target_include_directories(gui PUBLIC include ${CMAKE_SOURCE_DIR}/vita3k)
|
||||
target_link_libraries(gui PUBLIC app compat config dialog emuenv ime imgui glutil lang regmgr np)
|
||||
target_link_libraries(gui PRIVATE audio ctrl kernel miniz psvpfsparser pugixml::pugixml stb renderer packages sdl2 touch vkutil host::dialog)
|
||||
target_link_libraries(gui PRIVATE audio ctrl kernel miniz psvpfsparser pugixml::pugixml stb renderer packages sdl2 touch vkutil host::dialog concurrentqueue)
|
||||
target_link_libraries(gui PUBLIC tracy)
|
||||
|
||||
@@ -178,6 +178,7 @@ static bool get_custom_config(GuiState &gui, EmuEnvState &emuenv, const std::str
|
||||
config.screen_filter = gpu_child.attribute("screen-filter").as_string();
|
||||
config.v_sync = gpu_child.attribute("v-sync").as_bool();
|
||||
config.anisotropic_filtering = gpu_child.attribute("anisotropic-filtering").as_int();
|
||||
config.async_pipeline_compilation = gpu_child.attribute("async-pipeline-compilation").as_bool();
|
||||
config.import_textures = gpu_child.attribute("import-textures").as_bool();
|
||||
config.export_textures = gpu_child.attribute("export-textures").as_bool();
|
||||
config.export_as_png = gpu_child.attribute("export-as-png").as_bool();
|
||||
@@ -240,6 +241,7 @@ void init_config(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path
|
||||
config.screen_filter = emuenv.cfg.screen_filter;
|
||||
config.v_sync = emuenv.cfg.v_sync;
|
||||
config.anisotropic_filtering = emuenv.cfg.anisotropic_filtering;
|
||||
config.async_pipeline_compilation = emuenv.cfg.async_pipeline_compilation;
|
||||
config.import_textures = emuenv.cfg.import_textures;
|
||||
config.export_textures = emuenv.cfg.export_textures;
|
||||
config.export_as_png = emuenv.cfg.export_as_png;
|
||||
@@ -321,6 +323,7 @@ static void save_config(GuiState &gui, EmuEnvState &emuenv) {
|
||||
gpu_child.append_attribute("screen-filter") = config.screen_filter.c_str();
|
||||
gpu_child.append_attribute("v-sync") = config.v_sync;
|
||||
gpu_child.append_attribute("anisotropic-filtering") = config.anisotropic_filtering;
|
||||
gpu_child.append_attribute("async-pipeline-compilation") = config.async_pipeline_compilation;
|
||||
gpu_child.append_attribute("import-textures") = config.import_textures;
|
||||
gpu_child.append_attribute("export-textures") = config.export_textures;
|
||||
gpu_child.append_attribute("export-as-png") = config.export_as_png;
|
||||
@@ -353,6 +356,7 @@ static void save_config(GuiState &gui, EmuEnvState &emuenv) {
|
||||
emuenv.cfg.screen_filter = config.screen_filter;
|
||||
emuenv.cfg.v_sync = config.v_sync;
|
||||
emuenv.cfg.anisotropic_filtering = config.anisotropic_filtering;
|
||||
emuenv.cfg.async_pipeline_compilation = config.async_pipeline_compilation;
|
||||
emuenv.cfg.import_textures = config.import_textures;
|
||||
emuenv.cfg.export_textures = config.export_textures;
|
||||
emuenv.cfg.export_as_png = config.export_as_png;
|
||||
@@ -415,6 +419,7 @@ void set_config(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path)
|
||||
emuenv.cfg.current_config.screen_filter = emuenv.cfg.screen_filter;
|
||||
emuenv.cfg.current_config.v_sync = emuenv.cfg.v_sync;
|
||||
emuenv.cfg.current_config.anisotropic_filtering = emuenv.cfg.anisotropic_filtering;
|
||||
emuenv.cfg.current_config.async_pipeline_compilation = emuenv.cfg.async_pipeline_compilation;
|
||||
emuenv.cfg.current_config.import_textures = emuenv.cfg.import_textures;
|
||||
emuenv.cfg.current_config.export_textures = emuenv.cfg.export_textures;
|
||||
emuenv.cfg.current_config.export_as_png = emuenv.cfg.export_as_png;
|
||||
@@ -443,6 +448,7 @@ void set_config(GuiState &gui, EmuEnvState &emuenv, const std::string &app_path)
|
||||
emuenv.renderer->set_anisotropic_filtering(emuenv.cfg.current_config.anisotropic_filtering);
|
||||
emuenv.renderer->set_stretch_display(emuenv.cfg.stretch_the_display_area);
|
||||
emuenv.renderer->get_texture_cache()->set_replacement_state(emuenv.cfg.current_config.import_textures, emuenv.cfg.current_config.export_textures, emuenv.cfg.current_config.export_as_png);
|
||||
emuenv.renderer->set_async_compilation(emuenv.cfg.current_config.async_pipeline_compilation);
|
||||
|
||||
// No change it if app already running
|
||||
if (emuenv.io.title_id.empty()) {
|
||||
@@ -608,6 +614,15 @@ void draw_settings_dialog(GuiState &gui, EmuEnvState &emuenv) {
|
||||
ImGui::Checkbox(lang.gpu["disable_surface_sync"].c_str(), &config.disable_surface_sync);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", lang.gpu["surface_sync_description"].c_str());
|
||||
|
||||
if (is_vulkan)
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
if (is_vulkan) {
|
||||
ImGui::Checkbox("Aynchronous pipeline compilation", &config.async_pipeline_compilation);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Allow pipelines to be compiler concurrently on multiple concurrent threads.\n This decreases pipeline compilation stutter at the cost of temporary graphical glitches");
|
||||
}
|
||||
|
||||
// Screen Filter
|
||||
|
||||
@@ -3162,7 +3162,6 @@ EXPORT(int, sceGxmProgramCheck, const SceGxmProgram *program) {
|
||||
|
||||
EXPORT(Ptr<SceGxmProgramParameter>, sceGxmProgramFindParameterByName, Ptr<const SceGxmProgram> program_ptr, const char *name) {
|
||||
TRACY_FUNC(sceGxmProgramFindParameterByName, program_ptr, name);
|
||||
assert(program);
|
||||
if (!program_ptr || !name)
|
||||
return Ptr<SceGxmProgramParameter>();
|
||||
|
||||
@@ -4441,7 +4440,7 @@ EXPORT(int, sceGxmShaderPatcherCreateVertexProgram, SceGxmShaderPatcher *shaderP
|
||||
vp->attributes.insert(vp->attributes.end(), &attributes[0], &attributes[attributeCount]);
|
||||
}
|
||||
|
||||
if (!renderer::create(vp->renderer_data, *emuenv.renderer, *programId->program.get(mem), emuenv.renderer->gxp_ptr_map, emuenv.cache_path.string().c_str(), emuenv.io.title_id.c_str())) {
|
||||
if (!renderer::create(vp->renderer_data, *emuenv.renderer, *programId->program.get(mem), emuenv.renderer->gxp_ptr_map, vp->attributes, emuenv.cache_path.string().c_str(), emuenv.io.title_id.c_str())) {
|
||||
return RET_ERROR(SCE_GXM_ERROR_DRIVER);
|
||||
}
|
||||
|
||||
@@ -4472,6 +4471,10 @@ EXPORT(int, sceGxmShaderPatcherForceUnregisterProgram, SceGxmShaderPatcher *shad
|
||||
if (rp->program.get(emuenv.mem)->is_vertex()) {
|
||||
for (auto it = shaderPatcher->vertex_program_cache.begin(); it != shaderPatcher->vertex_program_cache.end();) {
|
||||
if (it->first.vertex_program.program == rp->program) {
|
||||
SceGxmVertexProgram *vertex_program = it->second.get(emuenv.mem);
|
||||
while (vertex_program->compile_threads_on.load(std::memory_order_acquire) > 0)
|
||||
std::this_thread::yield();
|
||||
|
||||
free_callbacked(emuenv, thread_id, shaderPatcher, it->second.address());
|
||||
it = shaderPatcher->vertex_program_cache.erase(it);
|
||||
} else {
|
||||
@@ -4481,6 +4484,10 @@ EXPORT(int, sceGxmShaderPatcherForceUnregisterProgram, SceGxmShaderPatcher *shad
|
||||
} else {
|
||||
for (auto it = shaderPatcher->fragment_program_cache.begin(); it != shaderPatcher->fragment_program_cache.end();) {
|
||||
if (it->first.fragment_program.program == rp->program) {
|
||||
SceGxmFragmentProgram *frag_program = it->second.get(emuenv.mem);
|
||||
while (frag_program->compile_threads_on.load(std::memory_order_acquire) > 0)
|
||||
std::this_thread::yield();
|
||||
|
||||
free_callbacked(emuenv, thread_id, shaderPatcher, it->second.address());
|
||||
it = shaderPatcher->fragment_program_cache.erase(it);
|
||||
} else {
|
||||
@@ -4577,6 +4584,9 @@ EXPORT(int, sceGxmShaderPatcherReleaseFragmentProgram, SceGxmShaderPatcher *shad
|
||||
SceGxmFragmentProgram *const fp = fragmentProgram.get(emuenv.mem);
|
||||
--fp->reference_count;
|
||||
if (fp->reference_count == 0) {
|
||||
while (fp->compile_threads_on.load(std::memory_order_acquire) > 0)
|
||||
std::this_thread::yield();
|
||||
|
||||
for (FragmentProgramCache::const_iterator it = shaderPatcher->fragment_program_cache.begin(); it != shaderPatcher->fragment_program_cache.end(); ++it) {
|
||||
if (it->second == fragmentProgram) {
|
||||
shaderPatcher->fragment_program_cache.erase(it);
|
||||
@@ -4597,6 +4607,9 @@ EXPORT(int, sceGxmShaderPatcherReleaseVertexProgram, SceGxmShaderPatcher *shader
|
||||
SceGxmVertexProgram *const vp = vertexProgram.get(emuenv.mem);
|
||||
--vp->reference_count;
|
||||
if (vp->reference_count == 0) {
|
||||
while (vp->compile_threads_on.load(std::memory_order_acquire) > 0)
|
||||
std::this_thread::yield();
|
||||
|
||||
for (VertexProgramCache::const_iterator it = shaderPatcher->vertex_program_cache.begin(); it != shaderPatcher->vertex_program_cache.end(); ++it) {
|
||||
if (it->second == vertexProgram) {
|
||||
shaderPatcher->vertex_program_cache.erase(it);
|
||||
|
||||
@@ -46,7 +46,7 @@ add_library(
|
||||
|
||||
target_include_directories(renderer PUBLIC include)
|
||||
target_link_libraries(renderer PUBLIC crypto display dlmalloc mem stb shader glutil threads config util vkutil)
|
||||
target_link_libraries(renderer PRIVATE ddspp sdl2 stb ffmpeg xxHash::xxhash)
|
||||
target_link_libraries(renderer PRIVATE ddspp sdl2 stb ffmpeg xxHash::xxhash concurrentqueue)
|
||||
|
||||
# Marshmallow Tracy linking
|
||||
if(TRACY_ENABLE_ON_CORE_COMPONENTS)
|
||||
|
||||
@@ -32,7 +32,7 @@ struct State;
|
||||
struct VertexProgram;
|
||||
|
||||
bool create(std::unique_ptr<FragmentProgram> &fp, State &state, const SceGxmProgram &program, const SceGxmBlendInfo *blend, GXPPtrMap &gxp_ptr_map, const char *cache_path, const char *title_id);
|
||||
bool create(std::unique_ptr<VertexProgram> &vp, State &state, const SceGxmProgram &program, GXPPtrMap &gxp_ptr_map, const char *cache_path, const char *title_id);
|
||||
bool create(std::unique_ptr<VertexProgram> &vp, State &state, const SceGxmProgram &program, GXPPtrMap &gxp_ptr_map, const std::vector<SceGxmVertexAttribute> &attributes, const char *cache_path, const char *title_id);
|
||||
void create(SceGxmSyncObject *sync, State &state);
|
||||
void destroy(SceGxmSyncObject *sync, State &state);
|
||||
void finish(State &state, Context *context);
|
||||
|
||||
@@ -45,7 +45,7 @@ struct GLState : public renderer::State {
|
||||
ScreenRenderer screen_renderer;
|
||||
|
||||
bool init(const fs::path &static_assets, const bool hashless_texture_cache) override;
|
||||
void late_init(const Config &cfg, const std::string_view game_id) override;
|
||||
void late_init(const Config &cfg, const std::string_view game_id, MemState &mem) override;
|
||||
|
||||
TextureCache *get_texture_cache() override {
|
||||
return &texture_cache;
|
||||
|
||||
@@ -274,8 +274,10 @@ struct GxmContextState {
|
||||
};
|
||||
|
||||
struct SceGxmFragmentProgram {
|
||||
size_t reference_count = 1;
|
||||
std::atomic<uint32_t> reference_count = 1;
|
||||
Ptr<const SceGxmProgram> program;
|
||||
// only necessary with async compilation
|
||||
std::atomic<uint32_t> compile_threads_on = 0;
|
||||
bool is_maskupdate;
|
||||
std::unique_ptr<renderer::FragmentProgram> renderer_data;
|
||||
};
|
||||
@@ -320,12 +322,14 @@ struct SceGxmShaderPatcherParams {
|
||||
};
|
||||
|
||||
struct SceGxmVertexProgram {
|
||||
size_t reference_count = 1;
|
||||
std::atomic<uint32_t> reference_count = 1;
|
||||
Ptr<const SceGxmProgram> program;
|
||||
std::vector<SceGxmVertexStream> streams;
|
||||
std::vector<SceGxmVertexAttribute> attributes;
|
||||
std::unique_ptr<renderer::VertexProgram> renderer_data;
|
||||
uint64_t key_hash;
|
||||
// only necessary with async compilation
|
||||
std::atomic<uint32_t> compile_threads_on = 0;
|
||||
};
|
||||
|
||||
struct SceGxmPrecomputedDraw {
|
||||
|
||||
@@ -82,7 +82,7 @@ struct State {
|
||||
bool need_page_table = false;
|
||||
|
||||
virtual bool init(const fs::path &static_assets, const bool hashless_texture_cache) = 0;
|
||||
virtual void late_init(const Config &cfg, const std::string_view game_id) = 0;
|
||||
virtual void late_init(const Config &cfg, const std::string_view game_id, MemState &mem) = 0;
|
||||
|
||||
virtual TextureCache *get_texture_cache() = 0;
|
||||
|
||||
@@ -99,6 +99,7 @@ struct State {
|
||||
virtual void set_screen_filter(const std::string_view &filter) = 0;
|
||||
virtual int get_max_anisotropic_filtering() = 0;
|
||||
virtual void set_anisotropic_filtering(int anisotropic_filtering) = 0;
|
||||
virtual void set_async_compilation(bool enable) {}
|
||||
void set_surface_sync_state(bool disable) {
|
||||
disable_surface_sync = disable;
|
||||
}
|
||||
|
||||
@@ -141,8 +141,8 @@ struct GxmRecordState {
|
||||
std::array<GXMStreamInfo, SCE_GXM_MAX_VERTEX_STREAMS> vertex_streams;
|
||||
|
||||
// Programs.
|
||||
Ptr<const SceGxmFragmentProgram> fragment_program;
|
||||
Ptr<const SceGxmVertexProgram> vertex_program;
|
||||
Ptr<SceGxmFragmentProgram> fragment_program;
|
||||
Ptr<SceGxmVertexProgram> vertex_program;
|
||||
|
||||
SceGxmColorSurface color_surface;
|
||||
SceGxmDepthStencilSurface depth_stencil_surface;
|
||||
@@ -205,8 +205,6 @@ struct FragmentProgram : ShaderProgram {
|
||||
|
||||
struct VertexProgram : ShaderProgram {
|
||||
shader::usse::AttributeInformationMap attribute_infos;
|
||||
|
||||
bool stripped_symbols_checked;
|
||||
};
|
||||
|
||||
struct ShadersHash {
|
||||
|
||||
@@ -22,24 +22,42 @@
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include <blockingconcurrentqueue.h>
|
||||
#include <util/containers.h>
|
||||
#include <vkutil/objects.h>
|
||||
|
||||
struct SceGxmProgram;
|
||||
struct SceGxmFragmentProgram;
|
||||
struct SceGxmVertexProgram;
|
||||
enum SceGxmPrimitiveType : uint32_t;
|
||||
struct SceGxmVertexAttribute;
|
||||
struct MemState;
|
||||
|
||||
using Sha256Hash = std::array<uint8_t, 32>;
|
||||
|
||||
namespace renderer::vulkan {
|
||||
namespace shader {
|
||||
struct Hints;
|
||||
}
|
||||
|
||||
namespace renderer {
|
||||
|
||||
struct GxmRecordState;
|
||||
|
||||
namespace vulkan {
|
||||
struct VKState;
|
||||
struct VKContext;
|
||||
struct CompileRequest;
|
||||
|
||||
using PipelineCompileQueue = moodycamel::BlockingConcurrentQueue<CompileRequest *>;
|
||||
|
||||
class PipelineCache {
|
||||
private:
|
||||
VKState &state;
|
||||
VKContext *current_context;
|
||||
|
||||
// are we performing pipeline compilation on a parallel thread?
|
||||
bool use_async_compilation = false;
|
||||
// how many threads are used in case async compilation is enabled
|
||||
int nb_worker_threads = 0;
|
||||
|
||||
// does the GPU support vertex attributes with 3 components (like R16G16B16_UNORM), some (like AMD GPUs) don't
|
||||
// this is needed when creating the input state
|
||||
@@ -47,7 +65,7 @@ private:
|
||||
|
||||
// how much time should we wait after the last shader compilation
|
||||
// to update the disk-saved shader cache (in seconds)
|
||||
static constexpr int pipeline_cache_save_delay = 30;
|
||||
static constexpr int pipeline_cache_save_delay = 15;
|
||||
|
||||
vk::PipelineCache pipeline_cache;
|
||||
|
||||
@@ -56,21 +74,33 @@ private:
|
||||
std::map<vk::Format, vk::RenderPass> render_passes[2][2];
|
||||
// render passes used along shader interlock
|
||||
std::map<vk::Format, vk::RenderPass> shader_interlock_pass;
|
||||
std::map<Sha256Hash, vk::ShaderModule> shaders;
|
||||
unordered_map_fast<uint64_t, vk::Pipeline> pipelines;
|
||||
|
||||
// temp vars used to store the result computed by auxialiary functions before createPipeline is called
|
||||
std::vector<vk::VertexInputBindingDescription> binding_descr;
|
||||
std::vector<vk::VertexInputAttributeDescription> attr_descr;
|
||||
// only used when accessing the shaders map
|
||||
std::mutex shaders_mutex;
|
||||
// because of multithreading, we want the pointers to remain stable
|
||||
unordered_map_stable<Sha256Hash, vk::ShaderModule> shaders;
|
||||
unordered_map_stable<uint64_t, vk::Pipeline> pipelines;
|
||||
|
||||
vk::PipelineShaderStageCreateInfo retrieve_shader(const SceGxmProgram *program, const Sha256Hash &hash, bool is_vertex, bool maskupdate, MemState &mem, const std::vector<SceGxmVertexAttribute> *hint_attributes);
|
||||
vk::PipelineLayout retrieve_pipeline_layout(const uint16_t vert_texture_count, const uint16_t frag_texture_count);
|
||||
vk::PipelineVertexInputStateCreateInfo get_vertex_input_state(MemState &mem);
|
||||
vk::PipelineShaderStageCreateInfo retrieve_shader(const SceGxmProgram *program, const Sha256Hash &hash, bool is_vertex, bool maskupdate, MemState &mem, const shader::Hints &hints);
|
||||
vk::PipelineVertexInputStateCreateInfo get_vertex_input_state(const SceGxmVertexProgram &vertex_program, MemState &mem);
|
||||
|
||||
// queue containing request sent by the main thread to the compile threads
|
||||
PipelineCompileQueue pipeline_compile_queue;
|
||||
moodycamel::ProducerToken pipeline_compile_queue_token;
|
||||
|
||||
// each pipeline compiler thread uses this function as its entrypoint
|
||||
void compiler_thread(MemState &mem);
|
||||
|
||||
vk::Pipeline compile_pipeline(SceGxmPrimitiveType type, vk::RenderPass render_pass, const SceGxmVertexProgram &vertex_program_gxm, const SceGxmFragmentProgram &fragment_program_gxm, const GxmRecordState &record, const shader::Hints &hints, MemState &mem);
|
||||
|
||||
public:
|
||||
// if not 0, next time the pipeline cache should be saved (in seconds since epoch)
|
||||
uint64_t next_pipeline_cache_save = std::numeric_limits<uint64_t>::max();
|
||||
|
||||
// modified by the surface cache, estimates if it is safe to use async pipeline compilation
|
||||
// (i.e that it does not causes permanent graphical issues)
|
||||
bool can_use_deferred_compilation;
|
||||
|
||||
vk::DescriptorSetLayout uniforms_layout;
|
||||
// used for the mask, color attachment
|
||||
vk::DescriptorSetLayout attachments_layout;
|
||||
@@ -81,15 +111,18 @@ public:
|
||||
// first index is vertex, second is fragment
|
||||
vk::PipelineLayout pipeline_layouts[17][17] = {};
|
||||
|
||||
explicit PipelineCache(VKState &state);
|
||||
PipelineCache(VKState &state);
|
||||
void init();
|
||||
|
||||
void read_pipeline_cache();
|
||||
void save_pipeline_cache();
|
||||
|
||||
vk::RenderPass retrieve_render_pass(vk::Format format, bool force_load, bool force_store, bool no_color = false);
|
||||
vk::Pipeline retrieve_pipeline(VKContext &context, SceGxmPrimitiveType &type, MemState &mem);
|
||||
vk::Pipeline retrieve_pipeline(VKContext &context, SceGxmPrimitiveType &type, bool consider_for_async, MemState &mem);
|
||||
|
||||
bool precompile_shader(const Sha256Hash &hash);
|
||||
vk::ShaderModule precompile_shader(const Sha256Hash &hash, bool search_first = true);
|
||||
|
||||
void set_async_compilation(bool enable);
|
||||
};
|
||||
} // namespace renderer::vulkan
|
||||
} // namespace vulkan
|
||||
} // namespace renderer
|
||||
|
||||
@@ -40,6 +40,8 @@ struct Viewport {
|
||||
};
|
||||
|
||||
struct VKState : public renderer::State {
|
||||
MemState *mem;
|
||||
|
||||
// 0 = automatic, > 0 = order in instance.enumeratePhysicalDevices
|
||||
int gpu_idx;
|
||||
|
||||
@@ -99,7 +101,7 @@ struct VKState : public renderer::State {
|
||||
|
||||
bool init(const fs::path &static_assets, const bool hashless_texture_cache) override;
|
||||
bool create(SDL_Window *window, std::unique_ptr<renderer::State> &state, const Config &config);
|
||||
void late_init(const Config &cfg, const std::string_view game_id) override;
|
||||
void late_init(const Config &cfg, const std::string_view game_id, MemState &mem) override;
|
||||
void cleanup();
|
||||
|
||||
TextureCache *get_texture_cache() override {
|
||||
@@ -114,6 +116,7 @@ struct VKState : public renderer::State {
|
||||
void set_screen_filter(const std::string_view &filter) override;
|
||||
int get_max_anisotropic_filtering() override;
|
||||
void set_anisotropic_filtering(int anisotropic_filtering) override;
|
||||
void set_async_compilation(bool enable) override;
|
||||
|
||||
bool map_memory(MemState &mem, Ptr<void> address, uint32_t size) override;
|
||||
void unmap_memory(MemState &mem, Ptr<void> address) override;
|
||||
|
||||
@@ -76,6 +76,7 @@ struct ColorSurfaceCacheInfo : public SurfaceCacheInfo {
|
||||
uint16_t original_height;
|
||||
uint16_t pixel_stride;
|
||||
uint32_t total_bytes;
|
||||
uint64_t last_frame_rendered;
|
||||
|
||||
SceGxmColorBaseFormat format;
|
||||
vk::ComponentMapping swizzle;
|
||||
|
||||
@@ -205,7 +205,7 @@ bool create(std::unique_ptr<FragmentProgram> &fp, State &state, const SceGxmProg
|
||||
return true;
|
||||
}
|
||||
|
||||
bool create(std::unique_ptr<VertexProgram> &vp, State &state, const SceGxmProgram &program, GXPPtrMap &gxp_ptr_map, const char *cache_path, const char *title_id) {
|
||||
bool create(std::unique_ptr<VertexProgram> &vp, State &state, const SceGxmProgram &program, GXPPtrMap &gxp_ptr_map, const std::vector<SceGxmVertexAttribute> &attributes, const char *cache_path, const char *title_id) {
|
||||
switch (state.current_backend) {
|
||||
case Backend::OpenGL:
|
||||
gl::create(vp, dynamic_cast<gl::GLState &>(state), program);
|
||||
@@ -231,9 +231,12 @@ bool create(std::unique_ptr<VertexProgram> &vp, State &state, const SceGxmProgra
|
||||
vp->texture_count = std::bit_width(vp->textures_used.to_ulong());
|
||||
|
||||
if (vp->attribute_infos.empty()) {
|
||||
vp->stripped_symbols_checked = false;
|
||||
} else {
|
||||
vp->stripped_symbols_checked = true;
|
||||
// Insert some symbols here
|
||||
if (program.primary_reg_count != 0) {
|
||||
for (size_t i = 0; i < attributes.size(); i++) {
|
||||
vp->attribute_infos.emplace(attributes[i].regIndex, shader::usse::AttributeInformation(static_cast<uint16_t>(i), SCE_GXM_PARAMETER_TYPE_F32, false, false, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -271,7 +271,7 @@ bool GLState::init(const fs::path &static_assets, const bool hashless_texture_ca
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLState::late_init(const Config &cfg, const std::string_view game_id) {
|
||||
void GLState::late_init(const Config &cfg, const std::string_view game_id, MemState &mem) {
|
||||
const fs::path texture_folder = fs::path(shared_path) / "textures";
|
||||
texture_cache.init(cfg.hashless_texture_cache, texture_folder, game_id);
|
||||
}
|
||||
|
||||
@@ -432,18 +432,6 @@ void sync_vertex_streams_and_attributes(GLContext &context, GxmRecordState &stat
|
||||
const SceGxmVertexProgram &vertex_program = *state.vertex_program.get(mem);
|
||||
GLVertexProgram *glvert = reinterpret_cast<GLVertexProgram *>(vertex_program.renderer_data.get());
|
||||
|
||||
if (!glvert->stripped_symbols_checked) {
|
||||
// Insert some symbols here
|
||||
const SceGxmProgram *vertex_program_body = vertex_program.program.get(mem);
|
||||
if (vertex_program_body && (vertex_program_body->primary_reg_count != 0)) {
|
||||
for (std::size_t i = 0; i < vertex_program.attributes.size(); i++) {
|
||||
glvert->attribute_infos.emplace(vertex_program.attributes[i].regIndex, shader::usse::AttributeInformation(static_cast<std::uint16_t>(i), SCE_GXM_PARAMETER_TYPE_F32, false, false, false));
|
||||
}
|
||||
}
|
||||
|
||||
glvert->stripped_symbols_checked = true;
|
||||
}
|
||||
|
||||
// Each draw will upload the stream data. Assuming that, we can just bind buffer, upload data
|
||||
// The GXM submit side should already submit used buffer, but we just delete all just in case
|
||||
std::array<std::size_t, SCE_GXM_MAX_VERTEX_STREAMS> offset_in_buffer;
|
||||
|
||||
@@ -91,7 +91,7 @@ bool get_shaders_cache_hashs(State &renderer) {
|
||||
void save_shaders_cache_hashs(State &renderer, std::vector<ShadersHash> &shaders_cache_hashs) {
|
||||
const auto shaders_path{ fs::path(renderer.cache_path) / "shaders" / renderer.title_id / renderer.self_name };
|
||||
if (!fs::exists(shaders_path))
|
||||
fs::create_directory(shaders_path);
|
||||
fs::create_directories(shaders_path);
|
||||
std::string hash_file_name = fmt::format("hashs-{}.dat", (renderer.current_backend == Backend::OpenGL) ? "gl" : "vk");
|
||||
fs::ofstream shaders_hashs(shaders_path / hash_file_name, std::ios::out | std::ios::binary);
|
||||
|
||||
|
||||
@@ -77,11 +77,11 @@ COMMAND_SET_STATE(region_clip) {
|
||||
|
||||
COMMAND_SET_STATE(program) {
|
||||
TRACY_FUNC_COMMANDS_SET_STATE(program);
|
||||
const Ptr<const void> program = helper.pop<Ptr<const void>>();
|
||||
const Ptr<void> program = helper.pop<Ptr<void>>();
|
||||
const bool is_fragment = helper.pop<bool>();
|
||||
|
||||
if (is_fragment) {
|
||||
render_context->record.fragment_program = program.cast<const SceGxmFragmentProgram>();
|
||||
render_context->record.fragment_program = program.cast<SceGxmFragmentProgram>();
|
||||
const SceGxmFragmentProgram *gxm_program = render_context->record.fragment_program.get(mem);
|
||||
render_context->record.fragment_program_hash = gxm_program->renderer_data->hash;
|
||||
render_context->record.is_maskupdate = gxm_program->is_maskupdate;
|
||||
@@ -99,7 +99,7 @@ COMMAND_SET_STATE(program) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
render_context->record.vertex_program = program.cast<const SceGxmVertexProgram>();
|
||||
render_context->record.vertex_program = program.cast<SceGxmVertexProgram>();
|
||||
const SceGxmVertexProgram *gxm_program = render_context->record.vertex_program.get(mem);
|
||||
render_context->record.vertex_program_hash = gxm_program->renderer_data->hash;
|
||||
}
|
||||
|
||||
@@ -30,14 +30,42 @@
|
||||
#include <util/fs.h>
|
||||
#include <util/log.h>
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
// don't use the dispatch version, because we always hash a small amount
|
||||
// with a known size
|
||||
#define XXH_INLINE_ALL
|
||||
#include <xxhash.h>
|
||||
|
||||
namespace renderer::vulkan {
|
||||
|
||||
// Size of the record containing what is needed for the pipeline construction (what is after is dynamic state)
|
||||
constexpr size_t record_pipeline_len = offsetof(GxmRecordState, vertex_streams);
|
||||
|
||||
// structure containing everything needed to compile a pipeline
|
||||
struct CompileRequest {
|
||||
// iterator to the pipeline location
|
||||
vk::Pipeline *pipeline;
|
||||
|
||||
// this is everything we need to compile the shader on another thread (as the original data will change)
|
||||
SceGxmPrimitiveType type;
|
||||
vk::RenderPass render_pass;
|
||||
SceGxmVertexProgram *vertex_program_gxm;
|
||||
SceGxmFragmentProgram *fragment_program_gxm;
|
||||
shader::Hints hints;
|
||||
|
||||
// the content of the record useful for the pipeline creation
|
||||
alignas(8) uint8_t record_data[record_pipeline_len];
|
||||
|
||||
const GxmRecordState *get_record() {
|
||||
// note: this object is only half defined, but we are only looking at the part that's defined
|
||||
return reinterpret_cast<const GxmRecordState *>(record_data);
|
||||
}
|
||||
};
|
||||
|
||||
PipelineCache::PipelineCache(VKState &state)
|
||||
: state(state) {
|
||||
: state(state)
|
||||
, pipeline_compile_queue_token(pipeline_compile_queue) {
|
||||
}
|
||||
|
||||
void PipelineCache::init() {
|
||||
@@ -151,6 +179,16 @@ void PipelineCache::init() {
|
||||
}
|
||||
}
|
||||
|
||||
// compute all possible pipeline layouts
|
||||
for (uint32_t vert_texture_count = 0; vert_texture_count <= 16; vert_texture_count++) {
|
||||
for (uint32_t frag_texture_count = 0; frag_texture_count <= 16; frag_texture_count++) {
|
||||
vk::PipelineLayoutCreateInfo layout_info{};
|
||||
vk::DescriptorSetLayout set_layouts[] = { uniforms_layout, attachments_layout, vertex_textures_layout[vert_texture_count], fragment_textures_layout[frag_texture_count] };
|
||||
layout_info.setSetLayouts(set_layouts);
|
||||
pipeline_layouts[vert_texture_count][frag_texture_count] = state.device.createPipelineLayout(layout_info);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// look for rgb vertex attribute support
|
||||
// we need to look at each format because it is not the same for all usual 3-component formats (checked on AMD Radeon HD 7800)
|
||||
@@ -164,6 +202,49 @@ void PipelineCache::init() {
|
||||
}
|
||||
state.features.support_rgb_attributes = unsupported_rgb_vertex_attribute_formats.empty();
|
||||
}
|
||||
|
||||
const int nb_logical_threads = SDL_GetCPUCount();
|
||||
// took this from RPCS3 (slightly modified)
|
||||
if (nb_logical_threads > 12)
|
||||
nb_worker_threads = 6;
|
||||
else if (nb_logical_threads > 8)
|
||||
nb_worker_threads = 4;
|
||||
else if (nb_logical_threads >= 6)
|
||||
nb_worker_threads = 2;
|
||||
else
|
||||
nb_worker_threads = 1;
|
||||
|
||||
if (use_async_compilation) {
|
||||
// we could not initialize the worker threads previously
|
||||
use_async_compilation = false;
|
||||
set_async_compilation(true);
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineCache::set_async_compilation(bool enable) {
|
||||
if (enable == use_async_compilation)
|
||||
return;
|
||||
|
||||
use_async_compilation = enable;
|
||||
if (nb_worker_threads == 0)
|
||||
// not ingame yet
|
||||
return;
|
||||
|
||||
if (enable) {
|
||||
LOG_INFO("Enabling asynchronous pipeline compilation with {} threads", nb_worker_threads);
|
||||
// launch all the threads
|
||||
for (int i = 0; i < nb_worker_threads; i++) {
|
||||
std::thread thread(&PipelineCache::compiler_thread, this, std::ref(*state.mem));
|
||||
thread.detach();
|
||||
}
|
||||
} else {
|
||||
LOG_INFO("Asynchronous pipeline compilation is now disabled");
|
||||
|
||||
// we assume that by the time set_async_compilation is called again with enable=true, all previous worker threads have already exited
|
||||
for (int i = 0; i < nb_worker_threads; i++)
|
||||
// if a thread receives nullptr, it exits
|
||||
pipeline_compile_queue.enqueue(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// magic number put at the beginning of the pipeline cache file
|
||||
@@ -226,6 +307,15 @@ void PipelineCache::read_pipeline_cache() {
|
||||
}
|
||||
|
||||
void PipelineCache::save_pipeline_cache() {
|
||||
// first save the shader hashes
|
||||
// do a copy for thread safety
|
||||
std::vector<ShadersHash> shader_cache_copy;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(shaders_mutex);
|
||||
shader_cache_copy = state.shaders_cache_hashs;
|
||||
}
|
||||
renderer::save_shaders_cache_hashs(state, state.shaders_cache_hashs);
|
||||
|
||||
const std::vector<uint8_t> pipeline_data = state.device.getPipelineCacheData(pipeline_cache);
|
||||
if (pipeline_data.empty())
|
||||
// No pipeline was created
|
||||
@@ -257,22 +347,40 @@ void PipelineCache::save_pipeline_cache() {
|
||||
LOG_INFO("Pipeline cache saved");
|
||||
}
|
||||
|
||||
vk::PipelineShaderStageCreateInfo PipelineCache::retrieve_shader(const SceGxmProgram *program, const Sha256Hash &hash, bool is_vertex, bool maskupdate, MemState &mem, const std::vector<SceGxmVertexAttribute> *hint_attributes) {
|
||||
vk::PipelineShaderStageCreateInfo PipelineCache::retrieve_shader(const SceGxmProgram *program, const Sha256Hash &hash, bool is_vertex, bool maskupdate, MemState &mem, const shader::Hints &hints) {
|
||||
if (maskupdate)
|
||||
LOG_CRITICAL("Mask not implemented in the vulkan renderer!");
|
||||
|
||||
auto it = shaders.find(hash);
|
||||
const vk::ShaderModule shader_compiling = std::bit_cast<vk::ShaderModule>(~0ULL);
|
||||
|
||||
if (it == shaders.end()) {
|
||||
vk::ShaderModule *shader_module;
|
||||
{
|
||||
// look if it is in the cache
|
||||
if (precompile_shader(hash))
|
||||
it = shaders.find(hash);
|
||||
std::unique_lock<std::mutex> lock(shaders_mutex);
|
||||
shader_module = &shaders.insert({ hash, nullptr }).first->second;
|
||||
if (*shader_module == shader_compiling) {
|
||||
// another thread is compiling the same exact shader at the same time
|
||||
// it's no use re-compiling it, so just wait for the other thread being done
|
||||
lock.unlock();
|
||||
|
||||
// we shouldn't need atomics and the compiler shouldn't be able to optimize this
|
||||
while (*shader_module == shader_compiling)
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
if (*shader_module == nullptr)
|
||||
// now mark the shader as compiling so that other threads accessing it won't try to compile it a second time
|
||||
*shader_module = shader_compiling;
|
||||
}
|
||||
|
||||
if (it != shaders.end()) {
|
||||
if (*shader_module == shader_compiling) {
|
||||
precompile_shader(hash, false);
|
||||
}
|
||||
|
||||
if (*shader_module != shader_compiling) {
|
||||
vk::PipelineShaderStageCreateInfo shader_stage_info{
|
||||
.stage = is_vertex ? vk::ShaderStageFlagBits::eVertex : vk::ShaderStageFlagBits::eFragment,
|
||||
.module = it->second,
|
||||
.module = *shader_module,
|
||||
.pName = is_vertex ? "main_vs" : "main_fs"
|
||||
};
|
||||
return shader_stage_info;
|
||||
@@ -286,51 +394,35 @@ vk::PipelineShaderStageCreateInfo PipelineCache::retrieve_shader(const SceGxmPro
|
||||
LOG_INFO("Generating vulkan spv shader {}", hash_text.data());
|
||||
const std::string shader_version = fmt::format("vk{}", shader::CURRENT_VERSION);
|
||||
|
||||
// update shader hints
|
||||
current_context->shader_hints.color_format = current_context->record.color_surface.colorFormat;
|
||||
current_context->shader_hints.attributes = hint_attributes;
|
||||
|
||||
shader::usse::SpirvCode source = load_spirv_shader(*program, state.features, true, current_context->shader_hints, maskupdate, state.cache_path.c_str(), title_id, self_name, shader_version, true);
|
||||
shader::usse::SpirvCode source = load_spirv_shader(*program, state.features, true, hints, maskupdate, state.cache_path.c_str(), title_id, self_name, shader_version, true);
|
||||
|
||||
vk::ShaderModuleCreateInfo shader_info{
|
||||
.codeSize = sizeof(uint32_t) * source.size(),
|
||||
.pCode = source.data()
|
||||
};
|
||||
|
||||
vk::ShaderModule shader = current_context->state.device.createShaderModule(shader_info);
|
||||
shaders[hash] = shader;
|
||||
|
||||
// Save shader cache haches
|
||||
// vertex and fragment shaders are not linked together so no need to associate them
|
||||
Sha256Hash empty_hash{};
|
||||
if (is_vertex) {
|
||||
state.shaders_cache_hashs.push_back({ hash, empty_hash });
|
||||
} else {
|
||||
state.shaders_cache_hashs.push_back({ empty_hash, hash });
|
||||
*shader_module = state.device.createShaderModule(shader_info);
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(shaders_mutex);
|
||||
// Save shader cache haches
|
||||
// vertex and fragment shaders are not linked together so no need to associate them
|
||||
Sha256Hash empty_hash{};
|
||||
if (is_vertex) {
|
||||
state.shaders_cache_hashs.push_back({ hash, empty_hash });
|
||||
} else {
|
||||
state.shaders_cache_hashs.push_back({ empty_hash, hash });
|
||||
}
|
||||
}
|
||||
renderer::save_shaders_cache_hashs(state, state.shaders_cache_hashs);
|
||||
|
||||
vk::PipelineShaderStageCreateInfo shader_stage_info{
|
||||
.stage = is_vertex ? vk::ShaderStageFlagBits::eVertex : vk::ShaderStageFlagBits::eFragment,
|
||||
.module = shader,
|
||||
.module = *shader_module,
|
||||
.pName = is_vertex ? "main_vs" : "main_fs"
|
||||
};
|
||||
|
||||
return shader_stage_info;
|
||||
}
|
||||
|
||||
vk::PipelineLayout PipelineCache::retrieve_pipeline_layout(const uint16_t vert_texture_count, const uint16_t frag_texture_count) {
|
||||
if (!pipeline_layouts[vert_texture_count][frag_texture_count]) {
|
||||
// create matching pipeline layout
|
||||
vk::PipelineLayoutCreateInfo layout_info{};
|
||||
vk::DescriptorSetLayout set_layouts[] = { uniforms_layout, attachments_layout, vertex_textures_layout[vert_texture_count], fragment_textures_layout[frag_texture_count] };
|
||||
layout_info.setSetLayouts(set_layouts);
|
||||
pipeline_layouts[vert_texture_count][frag_texture_count] = state.device.createPipelineLayout(layout_info);
|
||||
}
|
||||
|
||||
return pipeline_layouts[vert_texture_count][frag_texture_count];
|
||||
}
|
||||
|
||||
vk::RenderPass PipelineCache::retrieve_render_pass(vk::Format format, bool force_load, bool force_store, bool no_color) {
|
||||
auto &render_passes_map = no_color ? shader_interlock_pass : render_passes[force_load][force_store];
|
||||
|
||||
@@ -455,27 +547,17 @@ vk::RenderPass PipelineCache::retrieve_render_pass(vk::Format format, bool force
|
||||
return render_passes_map[format];
|
||||
}
|
||||
|
||||
vk::PipelineVertexInputStateCreateInfo PipelineCache::get_vertex_input_state(MemState &mem) {
|
||||
// Vertex attributes.
|
||||
const GxmRecordState &state = current_context->record;
|
||||
const SceGxmVertexProgram &vertex_program = *state.vertex_program.get(mem);
|
||||
VertexProgram *vkvert = vertex_program.renderer_data.get();
|
||||
|
||||
if (!vkvert->stripped_symbols_checked) {
|
||||
// Insert some symbols here
|
||||
const SceGxmProgram *vertex_program_body = vertex_program.program.get(mem);
|
||||
if (vertex_program_body && (vertex_program_body->primary_reg_count != 0)) {
|
||||
for (std::size_t i = 0; i < vertex_program.attributes.size(); i++) {
|
||||
vkvert->attribute_infos.emplace(vertex_program.attributes[i].regIndex, shader::usse::AttributeInformation(static_cast<std::uint16_t>(i), SCE_GXM_PARAMETER_TYPE_F32, false, false, false));
|
||||
}
|
||||
}
|
||||
|
||||
vkvert->stripped_symbols_checked = true;
|
||||
}
|
||||
|
||||
vk::PipelineVertexInputStateCreateInfo PipelineCache::get_vertex_input_state(const SceGxmVertexProgram &vertex_program, MemState &mem) {
|
||||
// pointer to these objects are returned (so it needs to be static)
|
||||
// and each thread needs one (hence the thread_local)
|
||||
static thread_local std::vector<vk::VertexInputBindingDescription> binding_descr;
|
||||
static thread_local std::vector<vk::VertexInputAttributeDescription> attr_descr;
|
||||
binding_descr.clear();
|
||||
attr_descr.clear();
|
||||
|
||||
// Vertex attributes.
|
||||
VertexProgram *vkvert = vertex_program.renderer_data.get();
|
||||
|
||||
uint32_t used_streams = 0;
|
||||
|
||||
for (const SceGxmVertexAttribute &attribute : vertex_program.attributes) {
|
||||
@@ -556,6 +638,33 @@ vk::PipelineVertexInputStateCreateInfo PipelineCache::get_vertex_input_state(Mem
|
||||
return vertex_input;
|
||||
}
|
||||
|
||||
void PipelineCache::compiler_thread(MemState &mem) {
|
||||
moodycamel::ConsumerToken consumer_token(pipeline_compile_queue);
|
||||
|
||||
// just a single loop, waiting for a pipeline compile request and compiling it
|
||||
CompileRequest *request;
|
||||
while (true) {
|
||||
pipeline_compile_queue.wait_dequeue(consumer_token, request);
|
||||
|
||||
if (request == nullptr)
|
||||
// use this as an instruction to stop the thread
|
||||
break;
|
||||
|
||||
vk::Pipeline pipeline = compile_pipeline(request->type, request->render_pass, *request->vertex_program_gxm, *request->fragment_program_gxm, *request->get_record(), request->hints, mem);
|
||||
*request->pipeline = pipeline;
|
||||
|
||||
request->vertex_program_gxm->compile_threads_on.fetch_sub(1, std::memory_order_release);
|
||||
request->fragment_program_gxm->compile_threads_on.fetch_sub(1, std::memory_order_release);
|
||||
|
||||
const auto time_s = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
next_pipeline_cache_save = time_s + pipeline_cache_save_delay;
|
||||
|
||||
state.shaders_count_compiled++;
|
||||
|
||||
delete request;
|
||||
}
|
||||
}
|
||||
|
||||
static vk::StencilOpState convert_op_state(const GxmStencilStateOp &state) {
|
||||
return vk::StencilOpState{
|
||||
.failOp = translate_stencil_op(state.stencil_fail),
|
||||
@@ -565,43 +674,18 @@ static vk::StencilOpState convert_op_state(const GxmStencilStateOp &state) {
|
||||
};
|
||||
}
|
||||
|
||||
vk::Pipeline PipelineCache::retrieve_pipeline(VKContext &context, SceGxmPrimitiveType &type, MemState &mem) {
|
||||
current_context = &context;
|
||||
const GxmRecordState &record = context.record;
|
||||
// get the hash of the current context
|
||||
constexpr size_t record_pipeline_len = offsetof(GxmRecordState, vertex_streams);
|
||||
uint64_t key = XXH3_64bits(&record, record_pipeline_len);
|
||||
|
||||
// add the hash of the blending
|
||||
const SceGxmFragmentProgram &fragment_program_gxm = *record.fragment_program.get(mem);
|
||||
const VKFragmentProgram &fragment_program = *reinterpret_cast<VKFragmentProgram *>(
|
||||
fragment_program_gxm.renderer_data.get());
|
||||
key ^= fragment_program.blending_hash;
|
||||
|
||||
// add the hash of the attribute and stream layout
|
||||
const SceGxmVertexProgram &vertex_program_gxm = *record.vertex_program.get(mem);
|
||||
key ^= vertex_program_gxm.key_hash;
|
||||
|
||||
// and also add the primitive type
|
||||
key ^= static_cast<uint64_t>(type);
|
||||
auto it = pipelines.find(key);
|
||||
if (it != pipelines.end()) {
|
||||
if (it->second != nullptr)
|
||||
return it->second;
|
||||
} else {
|
||||
// the pipeline hash was not in the cache
|
||||
state.shaders_count_compiled++;
|
||||
}
|
||||
|
||||
vk::Pipeline PipelineCache::compile_pipeline(SceGxmPrimitiveType type, vk::RenderPass render_pass, const SceGxmVertexProgram &vertex_program_gxm, const SceGxmFragmentProgram &fragment_program_gxm, const GxmRecordState &record, const shader::Hints &hints, MemState &mem) {
|
||||
const VertexProgram &vertex_program = *reinterpret_cast<VertexProgram *>(
|
||||
vertex_program_gxm.renderer_data.get());
|
||||
const SceGxmProgram *gxm_fragment_shader = fragment_program_gxm.program.get(mem);
|
||||
const VKFragmentProgram &fragment_program = *reinterpret_cast<VKFragmentProgram *>(
|
||||
fragment_program_gxm.renderer_data.get());
|
||||
|
||||
// the vertex input state must be computed before shader are retrieved in case symbols are stripped
|
||||
const vk::PipelineVertexInputStateCreateInfo vertex_input = get_vertex_input_state(mem);
|
||||
const vk::PipelineVertexInputStateCreateInfo vertex_input = get_vertex_input_state(vertex_program_gxm, mem);
|
||||
|
||||
const vk::PipelineShaderStageCreateInfo vertex_shader = retrieve_shader(vertex_program_gxm.program.get(mem), vertex_program.hash, true, fragment_program_gxm.is_maskupdate, mem, &vertex_program_gxm.attributes);
|
||||
const vk::PipelineShaderStageCreateInfo fragment_shader = retrieve_shader(gxm_fragment_shader, fragment_program.hash, false, fragment_program_gxm.is_maskupdate, mem, nullptr);
|
||||
const vk::PipelineShaderStageCreateInfo vertex_shader = retrieve_shader(vertex_program_gxm.program.get(mem), vertex_program.hash, true, fragment_program_gxm.is_maskupdate, mem, hints);
|
||||
const vk::PipelineShaderStageCreateInfo fragment_shader = retrieve_shader(gxm_fragment_shader, fragment_program.hash, false, fragment_program_gxm.is_maskupdate, mem, hints);
|
||||
const vk::PipelineShaderStageCreateInfo shader_stages[] = { vertex_shader, fragment_shader };
|
||||
// disable the fragment shader if gxm asks us to
|
||||
const bool is_fragment_disabled = record.front_side_fragment_program_mode == SCE_GXM_FRAGMENT_PROGRAM_DISABLED || gxm_fragment_shader->has_no_effect();
|
||||
@@ -652,7 +736,7 @@ vk::Pipeline PipelineCache::retrieve_pipeline(VKContext &context, SceGxmPrimitiv
|
||||
color_blending.setAttachments(blending);
|
||||
}
|
||||
|
||||
vk::PipelineLayout pipeline_layout = retrieve_pipeline_layout(vertex_program.texture_count, fragment_program.texture_count);
|
||||
vk::PipelineLayout pipeline_layout = pipeline_layouts[vertex_program.texture_count][fragment_program.texture_count];
|
||||
|
||||
// all of these can be changed at any time using the vita graphics api (like opengl)
|
||||
// Because each one can take a lot of different values, it's better to set them as dynamic
|
||||
@@ -674,7 +758,6 @@ vk::Pipeline PipelineCache::retrieve_pipeline(VKContext &context, SceGxmPrimitiv
|
||||
.scissorCount = 1
|
||||
};
|
||||
|
||||
const vk::RenderPass render_pass = use_shader_interlock ? context.current_shader_interlock_pass : context.current_render_pass;
|
||||
vk::GraphicsPipelineCreateInfo pipeline_info{
|
||||
.stageCount = shader_stage_count,
|
||||
.pStages = shader_stages,
|
||||
@@ -697,22 +780,105 @@ vk::Pipeline PipelineCache::retrieve_pipeline(VKContext &context, SceGxmPrimitiv
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto time_s = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
next_pipeline_cache_save = time_s + pipeline_cache_save_delay;
|
||||
|
||||
pipelines[key] = result.value;
|
||||
|
||||
return result.value;
|
||||
}
|
||||
|
||||
bool PipelineCache::precompile_shader(const Sha256Hash &hash) {
|
||||
vk::Pipeline PipelineCache::retrieve_pipeline(VKContext &context, SceGxmPrimitiveType &type, bool consider_for_async, MemState &mem) {
|
||||
const GxmRecordState &record = context.record;
|
||||
// get the hash of the current context
|
||||
uint64_t key = XXH3_64bits(&record, record_pipeline_len);
|
||||
|
||||
// add the hash of the blending
|
||||
SceGxmFragmentProgram &fragment_program_gxm = *record.fragment_program.get(mem);
|
||||
const VKFragmentProgram &fragment_program = *reinterpret_cast<VKFragmentProgram *>(
|
||||
fragment_program_gxm.renderer_data.get());
|
||||
key ^= fragment_program.blending_hash;
|
||||
|
||||
// add the hash of the attribute and stream layout
|
||||
SceGxmVertexProgram &vertex_program_gxm = *record.vertex_program.get(mem);
|
||||
key ^= vertex_program_gxm.key_hash;
|
||||
|
||||
// and also add the primitive type
|
||||
key ^= static_cast<uint64_t>(type);
|
||||
|
||||
// can't use constexpr because of apple clang...
|
||||
const vk::Pipeline pipeline_compiling = std::bit_cast<vk::Pipeline, uint64_t>(~0ULL);
|
||||
// if the pipeline is in the pipeline cache, we can expect its creation time to be almost instantaneous
|
||||
bool already_in_cache = false;
|
||||
|
||||
auto it = pipelines.find(key);
|
||||
if (it != pipelines.end()) {
|
||||
if (it->second != nullptr) {
|
||||
if (it->second == pipeline_compiling)
|
||||
// pipeline is still compiling
|
||||
return nullptr;
|
||||
else
|
||||
return it->second;
|
||||
}
|
||||
already_in_cache = true;
|
||||
} else {
|
||||
// the pipeline hash was not in the cache;
|
||||
it = pipelines.insert({ key, pipeline_compiling }).first;
|
||||
}
|
||||
|
||||
// get the correct renderpass here
|
||||
const SceGxmProgram *gxm_fragment_shader = fragment_program_gxm.program.get(mem);
|
||||
const bool use_shader_interlock = state.features.support_shader_interlock && gxm_fragment_shader->is_frag_color_used();
|
||||
const vk::RenderPass render_pass = use_shader_interlock ? context.current_shader_interlock_pass : context.current_render_pass;
|
||||
// update the shader hints
|
||||
context.shader_hints.color_format = record.color_surface.colorFormat;
|
||||
context.shader_hints.attributes = &vertex_program_gxm.attributes;
|
||||
|
||||
const bool compile_pipeline_async = !already_in_cache && consider_for_async && use_async_compilation && can_use_deferred_compilation;
|
||||
|
||||
if (compile_pipeline_async) {
|
||||
// create the pipeline compile request
|
||||
CompileRequest *request = new CompileRequest;
|
||||
*request = {
|
||||
.pipeline = &it->second,
|
||||
.type = type,
|
||||
.render_pass = render_pass,
|
||||
.vertex_program_gxm = &vertex_program_gxm,
|
||||
.fragment_program_gxm = &fragment_program_gxm,
|
||||
.hints = context.shader_hints
|
||||
};
|
||||
memcpy(request->record_data, &record, record_pipeline_len);
|
||||
it->second = pipeline_compiling;
|
||||
|
||||
// we must not delete these programs until the worker is done
|
||||
vertex_program_gxm.compile_threads_on.fetch_add(1, std::memory_order_relaxed);
|
||||
fragment_program_gxm.compile_threads_on.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
pipeline_compile_queue.enqueue(pipeline_compile_queue_token, request);
|
||||
|
||||
return nullptr;
|
||||
} else {
|
||||
// can't wait, compile it right now
|
||||
vk::Pipeline result = compile_pipeline(type, render_pass, vertex_program_gxm, fragment_program_gxm, record, context.shader_hints, mem);
|
||||
|
||||
const auto time_s = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
next_pipeline_cache_save = time_s + pipeline_cache_save_delay;
|
||||
|
||||
state.shaders_count_compiled++;
|
||||
|
||||
it->second = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
vk::ShaderModule PipelineCache::precompile_shader(const Sha256Hash &hash, bool search_first) {
|
||||
if (search_first) {
|
||||
// happens while loading the thread, no parallel access so no need for a mutex
|
||||
auto it = shaders.find(hash);
|
||||
if (it != shaders.end())
|
||||
return it->second;
|
||||
}
|
||||
|
||||
const auto shader_path{ fs::path(state.cache_path) / "shaders" / state.title_id / state.self_name };
|
||||
|
||||
if (shaders.contains(hash))
|
||||
return true;
|
||||
|
||||
if (!fs::exists(shader_path) || fs::is_empty(shader_path))
|
||||
return false;
|
||||
return nullptr;
|
||||
|
||||
Sha256Hash shader_hash;
|
||||
memcpy(shader_hash.data(), hash.data(), sizeof(Sha256Hash));
|
||||
@@ -721,7 +887,7 @@ bool PipelineCache::precompile_shader(const Sha256Hash &hash) {
|
||||
const std::vector<uint32_t> source = renderer::pre_load_shader_spirv(hash_ver.c_str(), "spv", state.cache_path.c_str(), state.title_id, state.self_name);
|
||||
|
||||
if (source.empty())
|
||||
return false;
|
||||
return nullptr;
|
||||
|
||||
vk::ShaderModuleCreateInfo shader_info{
|
||||
.codeSize = sizeof(uint32_t) * source.size(),
|
||||
@@ -729,8 +895,11 @@ bool PipelineCache::precompile_shader(const Sha256Hash &hash) {
|
||||
};
|
||||
|
||||
vk::ShaderModule shader = state.device.createShaderModule(shader_info);
|
||||
shaders[hash] = shader;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(shaders_mutex);
|
||||
shaders[hash] = shader;
|
||||
}
|
||||
|
||||
return true;
|
||||
return shader;
|
||||
}
|
||||
} // namespace renderer::vulkan
|
||||
} // namespace renderer::vulkan
|
||||
|
||||
@@ -627,7 +627,9 @@ bool VKState::create(SDL_Window *window, std::unique_ptr<renderer::State> &state
|
||||
return true;
|
||||
}
|
||||
|
||||
void VKState::late_init(const Config &cfg, const std::string_view game_id) {
|
||||
void VKState::late_init(const Config &cfg, const std::string_view game_id, MemState &mem) {
|
||||
this->mem = &mem;
|
||||
|
||||
bool use_high_accuracy = cfg.current_config.high_accuracy;
|
||||
|
||||
// shader interlock is more accurate but slower
|
||||
@@ -926,6 +928,10 @@ void VKState::set_anisotropic_filtering(int anisotropic_filtering) {
|
||||
texture_cache.anisotropic_filtering = anisotropic_filtering;
|
||||
}
|
||||
|
||||
void VKState::set_async_compilation(bool enable) {
|
||||
pipeline_cache.set_async_compilation(enable);
|
||||
}
|
||||
|
||||
std::vector<std::string> VKState::get_gpu_list() {
|
||||
const std::vector<vk::PhysicalDevice> gpus = instance.enumeratePhysicalDevices();
|
||||
|
||||
|
||||
@@ -255,21 +255,6 @@ static void bind_vertex_streams(VKContext &context, MemState &mem) {
|
||||
const SceGxmVertexProgram &vertex_program = *state.vertex_program.get(mem);
|
||||
VertexProgram *vkvert = vertex_program.renderer_data.get();
|
||||
|
||||
// we need to do another check here (the same is done in pipeline_cache)
|
||||
// because if a game (like Secret of Mana) uses two programs with the same shaders and the same vertex input stripped
|
||||
// the pipeline cache won't add stripped symbols for the second program
|
||||
if (!vkvert->stripped_symbols_checked) {
|
||||
// Insert some symbols here
|
||||
const SceGxmProgram *vertex_program_body = vertex_program.program.get(mem);
|
||||
if (vertex_program_body && (vertex_program_body->primary_reg_count != 0)) {
|
||||
for (std::size_t i = 0; i < vertex_program.attributes.size(); i++) {
|
||||
vkvert->attribute_infos.emplace(vertex_program.attributes[i].regIndex, shader::usse::AttributeInformation(static_cast<std::uint16_t>(i), SCE_GXM_PARAMETER_TYPE_F32, false, false, false));
|
||||
}
|
||||
}
|
||||
|
||||
vkvert->stripped_symbols_checked = true;
|
||||
}
|
||||
|
||||
int max_stream_idx = -1;
|
||||
|
||||
for (const SceGxmVertexAttribute &attribute : vertex_program.attributes) {
|
||||
@@ -385,14 +370,24 @@ void draw(VKContext &context, SceGxmPrimitiveType type, SceGxmIndexFormat format
|
||||
if (context.refresh_pipeline || type != context.last_primitive) {
|
||||
context.refresh_pipeline = false;
|
||||
context.last_primitive = type;
|
||||
vk::Pipeline new_pipeline = context.state.pipeline_cache.retrieve_pipeline(context, type, mem);
|
||||
|
||||
// We don't want to defer cases where we draw a whole quad over the screen as these draws could be necessary
|
||||
// to be able to see anything
|
||||
bool can_be_whole_quad = instance_count == 1 && (count == 4 || count == 6);
|
||||
vk::Pipeline new_pipeline = context.state.pipeline_cache.retrieve_pipeline(context, type, !can_be_whole_quad, mem);
|
||||
|
||||
if (new_pipeline != context.current_pipeline) {
|
||||
context.current_pipeline = new_pipeline;
|
||||
context.render_cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, context.current_pipeline);
|
||||
|
||||
if (new_pipeline != nullptr)
|
||||
context.render_cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, context.current_pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
// can happen with asynchronous pipeline compilation
|
||||
if (context.current_pipeline == nullptr)
|
||||
return;
|
||||
|
||||
if (config.log_active_shaders) {
|
||||
const std::string hash_text_f = hex_string(context.record.fragment_program.get(mem)->renderer_data->hash);
|
||||
const std::string hash_text_v = hex_string(context.record.vertex_program.get(mem)->renderer_data->hash);
|
||||
|
||||
@@ -152,6 +152,8 @@ SurfaceRetrieveResult VKSurfaceCache::retrieve_color_surface_for_framebuffer(Mem
|
||||
uint32_t bytes_per_stride = color->strideInPixels * gxm::bits_per_pixel(base_format) / 8;
|
||||
uint32_t total_surface_size = bytes_per_stride * original_height;
|
||||
|
||||
VKContext *context = reinterpret_cast<VKContext *>(state.context);
|
||||
|
||||
if (overlap) {
|
||||
ColorSurfaceCacheInfo &info = *ite->second;
|
||||
|
||||
@@ -179,6 +181,11 @@ SurfaceRetrieveResult VKSurfaceCache::retrieve_color_surface_for_framebuffer(Mem
|
||||
color_surface_queue.set_as_mru(&info);
|
||||
last_written_surface = &info;
|
||||
|
||||
// if this surface has not been rendered to for the last 60 frames, consider it is not safe not to render all shaders to it
|
||||
constexpr uint64_t big_delay_between_frames = 60;
|
||||
state.pipeline_cache.can_use_deferred_compilation = context->frame_timestamp - info.last_frame_rendered < big_delay_between_frames;
|
||||
info.last_frame_rendered = context->frame_timestamp;
|
||||
|
||||
if (vk_format == info.texture.format) {
|
||||
return { info.texture.view, &info.texture };
|
||||
} else {
|
||||
@@ -199,7 +206,6 @@ SurfaceRetrieveResult VKSurfaceCache::retrieve_color_surface_for_framebuffer(Mem
|
||||
}
|
||||
}
|
||||
|
||||
VKContext *context = reinterpret_cast<VKContext *>(state.context);
|
||||
// get the least recently used (probably unused) color surface
|
||||
ColorSurfaceCacheInfo &info_added = *color_surface_queue.get_lru();
|
||||
if (info_added.texture.image)
|
||||
@@ -209,6 +215,8 @@ SurfaceRetrieveResult VKSurfaceCache::retrieve_color_surface_for_framebuffer(Mem
|
||||
color_address_lookup.erase(info_added.data.address());
|
||||
|
||||
color_surface_queue.set_as_mru(&info_added);
|
||||
info_added.last_frame_rendered = context->frame_timestamp;
|
||||
|
||||
color_address_lookup[address] = &info_added;
|
||||
|
||||
info_added.width = width;
|
||||
@@ -280,6 +288,9 @@ SurfaceRetrieveResult VKSurfaceCache::retrieve_color_surface_for_framebuffer(Mem
|
||||
});
|
||||
}
|
||||
|
||||
// it's not impossible that this surface will be rendered once and only used after, so do not skip any shader on it
|
||||
state.pipeline_cache.can_use_deferred_compilation = false;
|
||||
|
||||
return { info_added.texture.view, &info_added.texture };
|
||||
}
|
||||
|
||||
@@ -872,6 +883,9 @@ Framebuffer &VKSurfaceCache::retrieve_framebuffer_handle(MemState &mem, SceGxmCo
|
||||
LOG_ERROR_ONCE("Depth stencil and color surface are both null!");
|
||||
}
|
||||
|
||||
// might get modified by retrieve_color_surface_for_framebuffer
|
||||
state.pipeline_cache.can_use_deferred_compilation = true;
|
||||
|
||||
// First retrieve separately the color surface and ds surface
|
||||
SurfaceRetrieveResult color_result;
|
||||
SurfaceRetrieveResult ds_result;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <shader/usse_types.h>
|
||||
#include <util/log.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
@@ -27,7 +28,7 @@ namespace shader {
|
||||
namespace usse {
|
||||
namespace disasm {
|
||||
|
||||
extern std::string *disasm_storage;
|
||||
extern thread_local std::stringstream *disasm_storage;
|
||||
|
||||
//
|
||||
// Disasm helpers
|
||||
@@ -47,10 +48,10 @@ std::string swizzle_to_str(Swizzle<s> swizz, const Imm4 write_mask);
|
||||
} // namespace shader
|
||||
|
||||
// TODO: make LOG_RAW
|
||||
#define LOG_DISASM(fmt_str, ...) \
|
||||
{ \
|
||||
auto fmt_disasm = fmt::format(fmt_str, ##__VA_ARGS__); \
|
||||
std::cout << fmt_disasm << std::endl; \
|
||||
if (shader::usse::disasm::disasm_storage) \
|
||||
*shader::usse::disasm::disasm_storage += fmt_disasm + '\n'; \
|
||||
#define LOG_DISASM(fmt_str, ...) \
|
||||
{ \
|
||||
auto fmt_disasm = fmt::format(fmt_str, ##__VA_ARGS__); \
|
||||
LOG_TRACE("\t{}", fmt_disasm); \
|
||||
if (shader::usse::disasm::disasm_storage) \
|
||||
*shader::usse::disasm::disasm_storage << fmt_disasm << '\n'; \
|
||||
}
|
||||
|
||||
@@ -1755,7 +1755,7 @@ static SpirvCode convert_gxp_to_spirv_impl(const SceGxmProgram &program, const s
|
||||
break;
|
||||
}
|
||||
|
||||
std::string disasm_dump;
|
||||
std::stringstream disasm_dump;
|
||||
|
||||
// Put disasm storage
|
||||
disasm::disasm_storage = &disasm_dump;
|
||||
@@ -1838,7 +1838,7 @@ static SpirvCode convert_gxp_to_spirv_impl(const SceGxmProgram &program, const s
|
||||
LOG_ERROR("SPIR-V Error:\n{}", spirv_log);
|
||||
|
||||
if (dumper) {
|
||||
dumper("dsm", disasm_dump);
|
||||
dumper("dsm", disasm_dump.str());
|
||||
}
|
||||
|
||||
b.dump(spirv);
|
||||
|
||||
@@ -26,7 +26,7 @@ using namespace shader::usse;
|
||||
|
||||
namespace shader::usse::disasm {
|
||||
|
||||
std::string *disasm_storage = nullptr;
|
||||
thread_local std::stringstream *disasm_storage = nullptr;
|
||||
|
||||
//
|
||||
// Disasm helpers
|
||||
|
||||
@@ -21,14 +21,18 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#if BOOST_VERSION >= 108100
|
||||
#if BOOST_VERSION >= 108200
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
#include <boost/unordered/unordered_flat_set.hpp>
|
||||
#include <boost/unordered/unordered_node_map.hpp>
|
||||
// use a boost unordered_flat_map as performance really matters for fields using this type
|
||||
template <typename T, typename S>
|
||||
using unordered_map_fast = boost::unordered::unordered_flat_map<T, S>;
|
||||
template <typename T>
|
||||
using unordered_set_fast = boost::unordered::unordered_flat_set<T>;
|
||||
// this is in case we need pointer stability under rehashing
|
||||
template <typename T, typename S>
|
||||
using unordered_map_stable = boost::unordered::unordered_node_map<T, S>;
|
||||
#else
|
||||
// fallback in case someone is using an old boost version
|
||||
#include <boost/unordered_map.hpp>
|
||||
@@ -37,6 +41,8 @@ template <typename T, typename S>
|
||||
using unordered_map_fast = boost::unordered_map<T, S>;
|
||||
template <typename T>
|
||||
using unordered_set_fast = boost::unordered_set<T>;
|
||||
template <typename T, typename S>
|
||||
using unordered_map_stable = boost::unordered_map<T, S>;
|
||||
#endif
|
||||
|
||||
namespace lru {
|
||||
|
||||
Reference in New Issue
Block a user