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
6 changes: 5 additions & 1 deletion core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1273,8 +1273,12 @@ if(BUILD_TESTING)
add_test(NAME agc_shader_frontend_pair_off COMMAND agc_shader_memory_tests --frontend-pair)
set_tests_properties(agc_shader_frontend_pair PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
set_tests_properties(agc_shader_frontend_pair_off PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_PERF_FRONTEND_PAIR=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
add_test(NAME agc_shader_layout_pair COMMAND agc_shader_memory_tests --layout-pair)
add_test(NAME agc_shader_layout_pair_off COMMAND agc_shader_memory_tests --layout-pair)
set_tests_properties(agc_shader_layout_pair PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
set_tests_properties(agc_shader_layout_pair_off PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_PERF_LAYOUT_PAIR=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_shader_bindless agc_shader_srt_read_patching agc_shader_frontend_pair agc_shader_frontend_pair_off PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(agc_shader_bindless agc_shader_srt_read_patching agc_shader_frontend_pair agc_shader_frontend_pair_off agc_shader_layout_pair agc_shader_layout_pair_off PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
foreach(agcTest IN ITEMS agc_shared_write_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_dpp_write_tests agc_masked_select_tests agc_ir_reclaim_tests agc_vector_window_tests agc_constant_bit_test_tests agc_integer_narrowing_tests agc_scalar_select_mask_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_shader_disk_cache_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests agc_formatted_buffer_byte_stores_tests ${agcExecutionTests})
if(agcTest STREQUAL "agc_driver_constant_store_tests" OR agcTest STREQUAL "agc_driver_flat_apertures_tests" OR agcTest STREQUAL "agc_driver_pipeline_optimized_readback_tests")
Expand Down
141 changes: 141 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "SpirvBackend/SpirvOptimizer.hpp"
#endif
#include "CacheKey.hpp"
#include "CompiledVariant.hpp"
#include "BdaAbi.hpp"
#include <spirv/unified1/spirv.hpp>
#include <algorithm>
Expand Down Expand Up @@ -338,6 +339,140 @@ void verifyFrontendPair() {
}
}

ShaderRecompiler::RecompileRequest LayoutPairRequest(std::span<const std::uint32_t> code, std::span<const std::uint32_t> userData, bool registration, std::uint32_t wordLimit) {
using namespace ShaderRecompiler;
auto request = FrontendRequest(code, userData);
request.shader.stage = ShaderStage::Fragment;
request.context.compute.reset();
request.context.pixel = ShaderPixelStageInfo{};
request.context.pixel->wave32 = true;
request.context.pixel->targetOutputMode[0] = 9u;
request.context.waveSize = 32u;
request.target.registrationModules = registration;
request.target.registrationWordLimit = wordLimit;
request.useCache = true;
return request;
}

void verifyLayoutPairVariant(const ShaderRecompiler::SourceHandle& paired, const ShaderRecompiler::SourceHandle& isolated) {
const auto& left = *paired.artifact;
const auto& right = *isolated.artifact;
require(left.artifact.spirv == right.artifact.spirv, "layout pair: the SPIR-V differs from an isolated compile");
require(left.bindings.layout == right.bindings.layout && left.bindings.layout.UsesPushData() == right.bindings.layout.UsesPushData() && left.bindings.pushConstantOffsetBytes == right.bindings.pushConstantOffsetBytes && left.bindings.pushConstantSizeBytes == right.bindings.pushConstantSizeBytes, "layout pair: the binding layout or push range differs from an isolated compile");
const auto& a = left.artifact;
const auto& b = right.artifact;
const auto sameParameter = [](const ShaderRecompiler::FragmentParameter& p, const ShaderRecompiler::FragmentParameter& q) {
return p.location == q.location && p.sourceLocation == q.sourceLocation && p.flat == q.flat && p.perVertex == q.perVertex && p.custom == q.custom && p.flatP10 == q.flatP10;
};
require(a.vertexInputPatches == b.vertexInputPatches && a.memoryOffsetDword == b.memoryOffsetDword && a.shaderDataDwords == b.shaderDataDwords && a.imageMetadataDword == b.imageMetadataDword && a.runtimeImageCount == b.runtimeImageCount && a.runtimeImageResources == b.runtimeImageResources && a.registrationModule == b.registrationModule && a.bdaAbiVersion == b.bdaAbiVersion && a.runtimeAbiVersion == b.runtimeAbiVersion, "layout pair: the artifact's bindings or ABI differ from an isolated compile");
require(a.vertexInputs == b.vertexInputs && a.vertexOffsetSgpr == b.vertexOffsetSgpr && a.instanceOffsetSgpr == b.instanceOffsetSgpr && a.vertexOffsetShared == b.vertexOffsetShared && a.instanceOffsetShared == b.instanceOffsetShared && a.vertexOffsetConflict == b.vertexOffsetConflict && a.instanceOffsetConflict == b.instanceOffsetConflict && a.hostSubgroupSize == b.hostSubgroupSize, "layout pair: the artifact's vertex interface differs from an isolated compile");
require(a.parameterExports == b.parameterExports && std::ranges::equal(a.fragmentParameters, b.fragmentParameters, sameParameter) && a.barycentricEmulation.active == b.barycentricEmulation.active && a.barycentricEmulation.smooth == b.barycentricEmulation.smooth && a.barycentricEmulation.linear == b.barycentricEmulation.linear, "layout pair: the artifact's fragment interface differs from an isolated compile");
const auto& x = left.info;
const auto& y = right.info;
require(x.stage == y.stage && x.shaderHash == y.shaderHash && x.waveSize == y.waveSize && x.userDataBase == y.userDataBase && x.userDataCount == y.userDataCount && x.scratchDwords == y.scratchDwords && x.paramExportMask == y.paramExportMask && x.info == y.info && x.bindings == y.bindings, "layout pair: the compiled shader information differs from an isolated compile");
}

void verifyLayoutPairIsolation(const ShaderRecompiler::RecompileRequest& request, const ShaderRecompiler::SourceHandle& handle, std::uint32_t separateLimit) {
using namespace ShaderRecompiler;
const std::vector<std::uint32_t> isolatedCode(request.shader.code.begin(), request.shader.code.end());
auto isolated = request;
isolated.shader.code = isolatedCode;
isolated.target.registrationWordLimit = separateLimit;
const auto reference = PrepareShader(isolated);
require(reference->source != handle.source, "layout pair: the isolated compile shared a source");
verifyLayoutPairVariant(handle, *reference);
SrtRuntime runtime{};
runtime.userData = request.context.userData;
runtime.shaderBase = request.shader.codeAddress;
auto uncached = request;
uncached.useCache = false;
verifyResult(Recompile(uncached), *MaterializeShader(request, *CaptureResources(request, runtime, handle), handle));
}

void verifyLayoutPair() {
using namespace ShaderRecompiler;
const bool reuse = std::getenv("APS5_NO_PERF_LAYOUT_PAIR") == nullptr;
const std::array<std::uint32_t, 4> buffer{0x10000000u, 0u, 0x40u, 0x00027facu};
const auto pixelCode = [](std::uint32_t variant) {
return std::vector<std::uint32_t>{0xbf800000u | variant, 0xf4200100u, 0xfa000000u, 0xbf8cc07fu, 0x7e000204u, 0xf800180fu, 0u, 0xbf810000u};
};
const auto interpolatedCode = [](std::uint32_t variant) {
return std::vector<std::uint32_t>{0xbf800000u | variant, 0xc8100000u, 0xc8110001u, 0xc8140500u, 0xc8150501u, 0xf4200100u, 0xfa000000u, 0xbf8cc07fu, 0x7e000204u, 0xf800180fu, 0x05040500u, 0xbf810000u};
};
const auto defaultLimit = SpirvTarget{}.registrationWordLimit;
struct Configuration {
bool registration;
std::uint32_t wordLimit;
std::uint32_t pushOffset;
bool interpolated;
};
std::uint32_t variant = 0u;
for (const auto& [registration, wordLimit, pushOffset, interpolated] : {Configuration{false, defaultLimit, 0u, false}, Configuration{true, defaultLimit, 0u, false}, Configuration{true, defaultLimit, 128u, false}, Configuration{true, 1u, 128u, false}, Configuration{true, defaultLimit, 128u, true}}) {
for (const bool zeroFirst : {false, true}) {
const auto code = interpolated ? interpolatedCode(variant++) : pixelCode(variant++);
auto pixel = LayoutPairRequest(code, buffer, registration, wordLimit);
pixel.layout.pushConstantOffsetBytes = pushOffset;
if (interpolated) {
pixel.context.pixel->interpolatorCount = 2u;
pixel.context.pixel->interpolatorSettings[1] = 1u;
pixel.context.pixel->inputAddr = PixelInputBit(PixelInput::PerspectiveCenter);
pixel.context.pixel->hasPerspectiveCenterVgpr = true;
}
auto zero = pixel;
zero.layout.pushConstantSizeBytes = 0u;
ShaderPreparationContext preparation;
const auto first = PrepareShader(zeroFirst ? zero : pixel, &preparation);
const auto second = PrepareShader(zeroFirst ? pixel : zero, &preparation);
require(first->source == second->source, "layout pair: the two layouts did not share a source");
require(preparation.ResourcePrograms() == (reuse ? 1u : 2u), "layout pair: the second layout ignored the reuse switch");
const auto& pixelHandle = zeroFirst ? second : first;
const auto& zeroHandle = zeroFirst ? first : second;
require(pixelHandle->artifact->bindings.layout.UsesPushData() && !zeroHandle->artifact->bindings.layout.UsesPushData(), "layout pair: the fixture does not distinguish the push-constant capacities");
const bool module = registration && wordLimit == defaultLimit;
require(GetPreparedArtifact(*pixelHandle).registrationModule == module && GetPreparedArtifact(*zeroHandle).registrationModule == module, "layout pair: the word limit did not choose the module mode");
require(GetPreparedArtifact(*pixelHandle).fragmentParameters.size() == (interpolated ? 2u : 0u) && GetPreparedArtifact(*zeroHandle).fragmentParameters.size() == (interpolated ? 2u : 0u), "layout pair: the interpolated fixture did not declare its two parameters");
verifyLayoutPairIsolation(pixel, *pixelHandle, wordLimit + 1u);
verifyLayoutPairIsolation(zero, *zeroHandle, wordLimit + 2u);
}
}

const auto code = pixelCode(variant++);
const auto pixel = LayoutPairRequest(code, buffer, true, defaultLimit);
auto relocated = pixel;
relocated.shader.codeAddress += 0x1000u;
relocated.layout.pushConstantSizeBytes = 0u;
ShaderPreparationContext preparation;
const auto first = PrepareShader(pixel, &preparation);
const auto moved = PrepareShader(relocated, &preparation);
require(first->source == moved->source, "layout pair: relocated code did not share the source");
require(preparation.ResourcePrograms() == 2u && moved->artifact->info.shaderHash == relocated.shader.codeAddress, "layout pair: a program translated at another code address was reused");
verifyLayoutPairIsolation(relocated, *moved, defaultLimit + 1u);

const std::vector<std::uint32_t> colorCode{0xbf800000u | variant++, 0x7e000200u, 0xf800180fu, 0u, 0xbf810000u};
auto color = LayoutPairRequest(colorCode, std::span(buffer).first(1u), true, defaultLimit);
color.shader.codeAddress = relocated.shader.codeAddress;
color.layout.pushConstantSizeBytes = 0u;
require(ResolveSource(color) != nullptr, "layout pair: the other source was not resolved");
const auto colored = PrepareShader(color, &preparation);
require(colored->source != first->source && preparation.ResourcePrograms() == 2u, "layout pair: another source at the same code address reused the retained program");
verifyLayoutPairIsolation(color, *colored, defaultLimit + 1u);

const auto retained = colored->source.use_count();
const auto otherCode = pixelCode(variant++);
static_cast<void>(PrepareResourceProgram(LayoutPairRequest(otherCode, buffer, true, defaultLimit), &preparation));
require(preparation.ResourcePrograms() == 3u && retained - colored->source.use_count() == (reuse ? 1 : 0), "layout pair: the context kept a program while it built another");

const std::vector<std::uint32_t> computeCode{0xbf800000u, 0xbf810000u};
auto compute = FrontendRequest(computeCode, buffer);
compute.context.userDataBaseRegister = 2u;
compute.context.compute->scratchDwords = 64u;
auto program = PrepareResourceProgram(compute);
const auto copied = program.CompiledInfo();
const auto taken = std::move(program).TakeCompiledInfo();
require(taken.userDataBase == 2u && taken.scratchDwords == 64u, "layout pair: the compute fixture did not set a user-data base and scratch");
require(copied.stage == taken.stage && copied.shaderHash == taken.shaderHash && copied.waveSize == taken.waveSize && copied.userDataBase == taken.userDataBase && copied.userDataCount == taken.userDataCount && copied.scratchDwords == taken.scratchDwords && copied.paramExportMask == taken.paramExportMask && copied.info == taken.info && copied.bindings == taken.bindings, "layout pair: the copied compiled information differs from the moved one");
}

void verifyRegisterSources() {
using namespace ShaderRecompiler;
IrResourcePlan plan;
Expand Down Expand Up @@ -3548,6 +3683,11 @@ int main(int argc, char** argv) {
std::cout << "Prepared frontend reuse preserves full and partial-thread programs\n";
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--layout-pair") {
verifyLayoutPair();
std::cout << "Both push-constant layouts of a pixel shader compile from one program unchanged\n";
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--bindless") {
verifyBindlessTable();
std::cout << "Bindless mapping, indexing capabilities and strict validation passed\n";
Expand All @@ -3562,6 +3702,7 @@ int main(int argc, char** argv) {
verifyRegisterSources();
verifyEvaluatedValues();
verifyFrontendPair();
verifyLayoutPair();
verifySignedSrtComparison();
verifyOutOfRangeConstantReads();
verifySignedMinimum();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class IrProgram {
const IrResourcePlan& Resources() const;
IrProgramMetadata& Metadata();
const IrProgramMetadata& Metadata() const;
[[nodiscard]] CompiledShaderInfo CompiledInfo() const&;
CompiledShaderInfo TakeCompiledInfo() &&;
IrValue& CreateValue(IrOpcode opcode, IrType type, std::uint64_t flags);
void SetEntryBlock(IrBlock& block);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,16 +309,18 @@ const IrProgramMetadata& IrProgram::Metadata() const {
return metadata;
}

CompiledShaderInfo IrProgram::TakeCompiledInfo() && {
namespace {

CompiledShaderInfo compiledInfo(const IrResourcePlan& resourcePlan, std::uint32_t waveSize, ShaderInfo info, IrBindingLayout bindings) {
CompiledShaderInfo result;
result.stage = resourcePlan.stage;
result.shaderHash = resourcePlan.shaderHash;
result.waveSize = waveSize;
result.userDataBase = resourcePlan.userDataBase;
result.userDataCount = resourcePlan.userDataCount;
result.scratchDwords = resourcePlan.info.scratchDwords;
result.info = std::move(resourcePlan.info);
result.bindings = std::move(metadata.bindings);
result.scratchDwords = info.scratchDwords;
result.info = std::move(info);
result.bindings = std::move(bindings);
for (const auto& output : result.info.outputs) {
if (output.kind == StageOutputKind::Parameter && output.index < 32u) {
result.paramExportMask |= 1u << output.index;
Expand All @@ -327,6 +329,16 @@ CompiledShaderInfo IrProgram::TakeCompiledInfo() && {
return result;
}

}

CompiledShaderInfo IrProgram::CompiledInfo() const& {
return compiledInfo(resourcePlan, waveSize, resourcePlan.info, metadata.bindings);
}

CompiledShaderInfo IrProgram::TakeCompiledInfo() && {
return compiledInfo(resourcePlan, waveSize, std::move(resourcePlan.info), std::move(metadata.bindings));
}

IrValue& IrProgram::CreateValue(IrOpcode opcode, IrType type, std::uint64_t flags) {
IrValue& created = CreateValue(opcode, type);
created.SetFlags(flags);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,26 @@
namespace ShaderRecompiler {

struct PreparedControlFlow;
struct SourceEntry;
class ShaderPreparationContext {
public:
[[nodiscard]] std::shared_ptr<const PreparedControlFlow> AcquireFrontend(const RecompileRequest& request);
[[nodiscard]] std::uint32_t ResourcePrograms() const;

private:
friend IrProgram PrepareResourceProgram(const RecompileRequest& request, ShaderPreparationContext* preparation);
friend struct PreparedInterface;
std::shared_ptr<const PreparedControlFlow> frontend;
std::shared_ptr<SourceEntry> interfaceSource;
std::unique_ptr<IrProgram> interfaceProgram;
std::uint32_t resourcePrograms = 0;
};

[[nodiscard]] IrProgram PrepareResourceProgram(const RecompileRequest& request);
[[nodiscard]] IrProgram PrepareResourceProgram(const RecompileRequest& request, ShaderPreparationContext* preparation);
[[nodiscard]] std::uint64_t FrontEndBuilds();
[[nodiscard]] std::shared_ptr<const IrResourcePlan> GetResourcePlan(const RecompileRequest& request);

struct SourceEntry;
struct ResourceCapture {
std::shared_ptr<const IrResourcePlan> plan;
ResourceSnapshot snapshot;
Expand Down
Loading
Loading