Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -485,9 +485,26 @@ add_custom_command(
VERBATIM
)

foreach(agcTriangleStage IN ITEMS vert frag)
set(agcTriangleSource ${CMAKE_CURRENT_SOURCE_DIR}/tests/shaders/Triangle.${agcTriangleStage})
set(agcTriangleSpv ${agcTestShaderGeneratedRoot}/Triangle_${agcTriangleStage}.spv)
set(agcTriangleHeader ${agcTestShaderGeneratedRoot}/Triangle_${agcTriangleStage}_spv.h)
add_custom_command(
OUTPUT "${agcTriangleHeader}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${agcTestShaderGeneratedRoot}"
COMMAND "$<TARGET_FILE:glslang-standalone>" -V --target-env vulkan1.1 -o "${agcTriangleSpv}" "${agcTriangleSource}"
COMMAND ${CMAKE_COMMAND} -DINPUT=${agcTriangleSpv} -DOUTPUT=${agcTriangleHeader}
-DSYMBOL=TRIANGLE_${agcTriangleStage}_SPV -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
DEPENDS "${agcTriangleSource}" glslang-standalone
VERBATIM
)
list(APPEND agcTriangleHeaders "${agcTriangleHeader}")
endforeach()

add_test_executable(agc_driver_recorder_tests EXCLUDE_FROM_ALL
tests/Recorder.cpp
tests/ResidentPresent.cpp
${agcTriangleHeaders}
${agcSampleLodShaderHeader}
${agcSampleArrayShaderHeader}
)
Expand All @@ -507,6 +524,14 @@ target_link_libraries(agc_driver_recorder_tests PRIVATE

set_target_properties(agc_driver_recorder_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_driver_recorder_tests)
if(BUILD_TESTING)
set_target_properties(agc_driver_recorder_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_pipeline_cache COMMAND agc_driver_recorder_tests --pipeline-cache-only)
if(WIN32)
set_tests_properties(agc_pipeline_cache PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()

add_test_executable(agc_recorder_kept_bytes_tests EXCLUDE_FROM_ALL tests/RecorderKeptBytes.cpp)
target_include_directories(agc_recorder_kept_bytes_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include)
Expand Down
22 changes: 11 additions & 11 deletions core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <cstdlib>
#include <list>
#include <mutex>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <vector>
Expand Down Expand Up @@ -371,16 +372,19 @@ void append(std::vector<std::byte>& key, const TValue& value) {
// (the recompiler could not identify it, so nothing else may share its pipeline). The rect-list
// control and evaluation stages are generated from the vertex and fragment results, which the key
// already names, so they carry no id of their own.
std::vector<std::byte> pipelineKey(const Context& context, const State& state, const VertexInputLayout& input, const ShaderResources& resources, std::span<const CompiledShader> shaders, VkImageLayout attachmentLayout) {
void pipelineKey(std::vector<std::byte>& key, const Context& context, const State& state, const VertexInputLayout& input, const ShaderResources& resources, std::span<const CompiledShader> shaders, VkImageLayout attachmentLayout) {
using Stage = ShaderRecompiler::ShaderStage;
std::vector<std::byte> key;
key.clear();
append(key, context.device);
append(key, attachmentLayout);
append(key, shaders.size());
for (const auto& shader : shaders) {
Require(shader.program != nullptr, "missing compiled shader");
const bool generated = state.rectList && (shader.stage == Stage::TessellationControl || shader.stage == Stage::TessellationEvaluation);
if (!generated && shader.program->PipelineVariantId() == 0) return {};
if (!generated && shader.program->PipelineVariantId() == 0) {
key.clear();
return;
}
append(key, shader.stage);
append(key, generated ? std::uint64_t{0} : shader.program->PipelineVariantId());
// Where the stage's push constants sit in the block (AssemblePushConstants).
Expand Down Expand Up @@ -452,16 +456,10 @@ std::vector<std::byte> pipelineKey(const Context& context, const State& state, c
append(key, tessellation.partitioning);
append(key, tessellation.outputTopology);
}
return key;
}

std::uint64_t hashKey(const std::vector<std::byte>& key) {
std::uint64_t hash = 14695981039346656037ull;
for (const auto byte : key) {
hash ^= static_cast<std::uint8_t>(byte);
hash *= 1099511628211ull;
}
return hash;
return std::hash<std::string_view>{}(std::string_view(reinterpret_cast<const char*>(key.data()), key.size()));
}

struct PipelineStore {
Expand All @@ -475,6 +473,7 @@ struct PipelineStore {
std::shared_ptr<Pipeline> pipeline;
};
std::mutex mutex;
std::vector<std::byte> scratchKey;
// Least recently used first.
std::list<Entry> entries;
std::unordered_map<std::uint64_t, std::list<Entry>::iterator> index;
Expand Down Expand Up @@ -526,7 +525,8 @@ std::shared_ptr<Pipeline> CachedPipeline(const Context& context, const State& st
auto& store = Pipelines();
std::lock_guard lock(store.mutex);
reportPipelines(store);
const auto key = pipelineKey(context, state, vertexInput, resources, shaders, attachmentLayout);
auto& key = store.scratchKey;
pipelineKey(key, context, state, vertexInput, resources, shaders, attachmentLayout);
if (key.empty()) {
++store.uncached;
return std::make_shared<Pipeline>(context, state, vertexInput, resources, shaders, attachmentLayout);
Expand Down
75 changes: 75 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#include "Triangle_frag_spv.h"
#include "Triangle_vert_spv.h"
#include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
Expand Down Expand Up @@ -30,6 +34,7 @@
#include <array>
#include <atomic>
#include <cmath>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <iostream>
Expand Down Expand Up @@ -2850,6 +2855,71 @@ void atomicViewTests(const Device& device, Recorder& recorder) {
}
}

void pipelineCacheTests(const Device& device, bool benchmark) {
std::thread establishThreadedProcess([] {});
establishThreadedProcess.join();
std::lock_guard gpu(GpuMutex());
const auto& context = device.GetContext();
struct Cleanup {
VkDevice device;
~Cleanup() { ClearCachedPipelines(device); }
} cleanup{context.device};
ShaderRecompiler::RecompileResult vertex, fragment;
vertex.variantId = 1;
fragment.variantId = 2;
vertex.spirv = std::vector<std::uint32_t>(std::begin(TRIANGLE_vert_SPV), std::end(TRIANGLE_vert_SPV));
fragment.spirv = std::vector<std::uint32_t>(std::begin(TRIANGLE_frag_SPV), std::end(TRIANGLE_frag_SPV));
const std::array shaders{CompiledShader{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, CompiledShader{ShaderRecompiler::ShaderStage::Fragment, &fragment, 0}};
State state{};
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
state.hasColorTarget = true;
state.color.format = VK_FORMAT_R8G8B8A8_UNORM;
state.colors.push_back(state.color);
state.blend.colorWriteMask = 15;
state.blends.push_back(state.blend);
ShaderResources resources(context, shaders, state.color, 0, 0);
const auto lookup = [&](const VertexInputLayout& input) {
return CachedPipeline(context, state, input, resources, shaders, VK_IMAGE_LAYOUT_GENERAL);
};
VertexInputLayout input;
const auto first = lookup(input);
Require(lookup(input) == first, "pipeline cache did not retain an identical key");
state.cullMode = VK_CULL_MODE_BACK_BIT;
Require(lookup(input) != first, "pipeline cache ignored changed raster state");
state.cullMode = 0;
for (const auto attributes : {0u, 8u, 32u}) {
Require(attributes <= context.limits.maxVertexInputBindings && attributes <= context.limits.maxVertexInputAttributes, "pipeline cache fixture exceeds vertex input limits");
input.bindings.clear();
input.attributes.clear();
for (std::uint32_t i = 0; i < attributes; ++i) {
input.bindings.push_back({i, 16, VK_VERTEX_INPUT_RATE_VERTEX});
input.attributes.push_back({i, i, VK_FORMAT_R32G32B32A32_SFLOAT, 0});
}
const auto expected = lookup(input);
if (attributes != 0) Require(expected != first, "pipeline cache ignored changed vertex input");
Require(lookup(input) == expected, "pipeline cache lost the larger key");
if (!benchmark) continue;
std::array<double, 9> times;
for (unsigned pass = 0; pass <= times.size(); ++pass) {
const auto started = std::chrono::steady_clock::now();
for (unsigned i = 0; i < 65536; ++i) Require(lookup(input) == expected, "pipeline cache benchmark missed a warmed key");
if (pass != 0) times[pass - 1] = std::chrono::duration<double, std::micro>(std::chrono::steady_clock::now() - started).count() / 65536;
}
std::ranges::sort(times);
std::cout << "Pipeline cache " << attributes << " attributes: median " << times[4] << " us/lookup, p95 pass " << times.back() << " us\n";
}
input = {};
Require(lookup(input) == first, "pipeline cache retained bytes from a larger key");
vertex.variantId = 0;
const auto unidentified = lookup(input);
Require(lookup(input) != unidentified, "pipeline cache retained an unidentified shader");
vertex.variantId = 1;
Require(lookup(input) == first, "an unidentified shader disturbed the retained pipeline");
ClearCachedPipelines(context.device);
Require(lookup(input) != first, "clearing the pipeline cache retained its old entry");
std::cout << "Pipeline cache key and retained lifetime tests passed\n";
}

void keysFillTests(const Device& device, Recorder& recorder) {
const auto& base = device.GetContext();
if (base.hostImportAlignment == 0) {
Expand Down Expand Up @@ -2969,6 +3039,11 @@ void keysFillTests(const Device& device, Recorder& recorder) {
int main(int argc, char** argv) {
try {
Device device;
if (argc == 2 && (std::string_view(argv[1]) == "--benchmark-pipeline-cache" || std::string_view(argv[1]) == "--pipeline-cache-only")) {
pipelineCacheTests(device, std::string_view(argv[1]) == "--benchmark-pipeline-cache");
return 0;
}

std::lock_guard gpu(GpuMutex());
std::cout << "host imports " << (PrepareImportWatch(device.GetContext()) == ImportWatch::Unwatch ? "are compared" : "stay watched") << '\n';
Recorder recorder(device.GetContext());
Expand Down
Loading