diff --git a/src/common/parallelFor.h b/src/common/parallelFor.h new file mode 100644 index 000000000..9c53fa023 --- /dev/null +++ b/src/common/parallelFor.h @@ -0,0 +1,35 @@ +#ifndef KYTY_COMMON_PARALLELFOR_H_ +#define KYTY_COMMON_PARALLELFOR_H_ + +#include +#include +#include +#include +#include + +namespace Common { + +// Half the hardware threads, at least one. +inline size_t ParallelThreads() { + return std::max(1u, std::thread::hardware_concurrency() / 2); +} + +// Calls job(i) for every i below count on up to ParallelThreads() threads, the caller included. +template +void ParallelFor(size_t count, const Job& job) { + std::atomic_size_t next = 0; + const auto work = [&] { + for (auto i = next++; i < count; i = next++) { + job(i); + } + }; + std::vector helpers; + for (auto i = std::min(count, ParallelThreads()); i > 1; i--) { + helpers.emplace_back(work); + } + work(); +} + +} // namespace Common + +#endif // KYTY_COMMON_PARALLELFOR_H_ diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h index 294396c86..5f566d82a 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.h @@ -262,7 +262,7 @@ private: // pipelineSerialization.cpp void WarmUp(const std::filesystem::path& path); - bool LoadRecord(std::span& records, std::vector& pipelines); + bool LoadRecord(std::span& records); void StoreModule(const ProgramKey& key, const Program& program, const ShaderModule& module, const std::vector& spirv); void StorePipeline(const GraphicsPipelineKey& key); diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineSerialization.cpp b/src/graphics/host_gpu/renderer/pipeline/pipelineSerialization.cpp index 8b86479a6..30ce92de9 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineSerialization.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineSerialization.cpp @@ -1,6 +1,7 @@ #include "graphics/host_gpu/renderer/pipeline/pipelineCache.h" #include "common/logging/log.h" +#include "common/parallelFor.h" #include "common/stringUtils.h" #include "graphics/host_gpu/vulkanCommon.h" #include "graphics/shader/recompiler/ShaderRecompiler.h" @@ -153,35 +154,41 @@ void PipelineCache::WarmUp(const std::filesystem::path& path) { file.Read(bytes.data(), static_cast(bytes.size()), &read); bytes.resize(read); } - Log::WriteToConsoleAndLog( - fmt::format("Shader cache: compiling shaders and pipelines from {} ({:.1f} MB)\n", - Common::PathToString(path), static_cast(bytes.size()) / 1'000'000.0)); - std::vector pipelines; - auto records = std::span(bytes); + Log::WriteToConsoleAndLog(fmt::format( + "Shader cache: compiling shaders and pipelines from {} ({:.1f} MB) with {} threads\n", + Common::PathToString(path), static_cast(bytes.size()) / 1'000'000.0, + Common::ParallelThreads())); + auto records = std::span(bytes); const bool reusable = records.size() >= header.size() && std::ranges::equal(records.first(header.size()), header); if (reusable) { records = records.subspan(header.size()); - while (LoadRecord(records, pipelines)) { + while (LoadRecord(records)) { } } // A damaged record ends the store; the records before it stay. m_store.Open(path, header, reusable ? bytes.size() - records.size() : 0); const auto modules = MapModules(); - for (const auto& key: pipelines) { - Pipeline pipeline; - if (CreateGraphicsPipeline(pipeline, key, modules)) { - m_graphics_pipelines.emplace(key, pipeline); - } + std::vector> jobs; + jobs.reserve(m_graphics_pipelines.size() + modules.size()); + for (auto& node: m_graphics_pipelines) { + jobs.emplace_back([this, &modules, &node] { + CreateGraphicsPipeline(node.second, node.first, modules); + }); } // A compute pipeline depends only on its module. for (const auto* module: modules | std::views::values) { if (module != nullptr && module->info.stage == ShaderType::Compute) { - CreatePipelineInternal(m_graphics, m_compute_pipelines[module->id], *module, - m_driver_cache); + jobs.emplace_back([this, module, &pipeline = m_compute_pipelines[module->id]] { + CreatePipelineInternal(m_graphics, pipeline, *module, m_driver_cache); + }); } } + Common::ParallelFor(jobs.size(), [&](size_t i) { jobs[i](); }); + // A key with a stage missing from the store keeps no pipeline. + std::erase_if(m_graphics_pipelines, + [](const auto& entry) { return entry.second.pipeline == nullptr; }); if (!m_graphics_pipelines.empty() || !m_compute_pipelines.empty()) { CheckpointDriverCache(); } @@ -194,8 +201,7 @@ void PipelineCache::WarmUp(const std::filesystem::path& path) { .count())); } -bool PipelineCache::LoadRecord(std::span& records, - std::vector& pipelines) { +bool PipelineCache::LoadRecord(std::span& records) { Frame frame; if (records.size() < sizeof(frame)) { return false; @@ -249,7 +255,8 @@ bool PipelineCache::LoadRecord(std::span& records, if (!complete()) { return false; } - pipelines.push_back(key); + // WarmUp creates the pipeline once every record is loaded. + m_graphics_pipelines.try_emplace(key); break; } case Record::TessellationLayout: {