Skip to content
Closed
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
1 change: 1 addition & 0 deletions core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@ target_include_directories(agc_driver_prepared_graphics_tests PRIVATE $<TARGET_P
target_include_directories(agc_driver_prepared_shaders_tests PRIVATE
$<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>
)
target_link_libraries(agc_driver_interpolation_arithmetic_tests PRIVATE shader_recompiler_agc_driver)

if(BUILD_TESTING)
add_test(NAME agc_shader_bindless COMMAND agc_shader_memory_tests --bindless)
Expand Down
32 changes: 30 additions & 2 deletions core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1102,7 +1102,7 @@ void verifyPixelInputs() {
}
}

ShaderRecompiler::RecompileResult recompileSlots(std::initializer_list<std::uint32_t> controls, std::span<const std::uint32_t> code) {
ShaderRecompiler::RecompileResult recompileSlots(std::initializer_list<std::uint32_t> controls, std::span<const std::uint32_t> code, bool float64 = true, bool barycentric = true) {
using namespace ShaderRecompiler;
ShaderPixelStageInfo pixel{};
pixel.interpolatorCount = static_cast<std::uint32_t>(controls.size());
Expand All @@ -1119,7 +1119,9 @@ ShaderRecompiler::RecompileResult recompileSlots(std::initializer_list<std::uint
request.target.vulkanVersion = 0x00401000u;
request.target.spirvVersion = 0x00010300u;
request.target.subgroupSize = 64;
request.target.fragmentShaderBarycentricEnabled = true;
request.target.fragmentShaderBarycentricEnabled = barycentric;
static constexpr std::array<std::uint32_t, 1> capabilities{spv::CapabilityFloat64};
request.target.supportedCapabilities = std::span(capabilities).first(float64 ? 1u : 0u);
request.layout.pushConstantSizeBytes = 128;
request.useCache = false;
return Recompile(request);
Expand Down Expand Up @@ -1172,6 +1174,30 @@ void verifyPixelParameterSlots() {
expectFailure([] { static_cast<void>(recompileSlots({0x423u, 0x3u}, shared)); }, "passes its vertices through unchanged", "an interpolated pass-through input was accepted");
}

void verifyF16InterpolationCapabilities() {
static constexpr std::array<std::uint32_t, 5> p1ll{0xd7420002u, 0x00020000u, 0xf800180fu, 0x02020202u, 0xbf810000u};
static constexpr std::array<std::uint32_t, 7> p1lv{0x7e0e02ffu, 0x4500bc00u, 0xd7430008u, 0x041e0000u, 0xf800180fu, 0x08080808u, 0xbf810000u};
static constexpr std::array<std::uint32_t, 6> p2{0x7e040280u, 0xd75a0003u, 0x040a0200u, 0xf800180fu, 0x03030303u, 0xbf810000u};
for (const auto code : {std::span<const std::uint32_t>(p1ll), std::span<const std::uint32_t>(p1lv), std::span<const std::uint32_t>(p2)}) {
require(!recompileSlots({0x03080000u}, code).spirv.empty(), "16-bit interpolation did not compile with its required capabilities");
expectFailure([&] { static_cast<void>(recompileSlots({0x03080000u}, code, false)); }, "Float64 capability", "16-bit interpolation compiled without Float64");
expectFailure([&] { static_cast<void>(recompileSlots({0x03080000u}, code, true, false)); }, "requires fragmentShaderBarycentric", "16-bit interpolation compiled without barycentrics");
}
}

void verifyF16PixelParameterSlots() {
static constexpr std::array<std::uint32_t, 7> high{0xd7420002u, 0x00020100u, 0xd75a0003u, 0x040a0300u, 0xf800180fu, 0x03030303u, 0xbf810000u};
static constexpr std::array<std::uint32_t, 7> low{0xd7420002u, 0x00020000u, 0xd75a0003u, 0x040a0200u, 0xf800180fu, 0x03030303u, 0xbf810000u};
auto inputs = slotInputs({0x03080003u}, high);
require(inputs.size() == 1u && inputs[0].first == 3u && inputs[0].second, "a 16-bit interpolated input was not read per vertex at its slot");
inputs = slotInputs({0x03080023u}, high);
require(inputs.size() == 1u && inputs[0].first == 3u && inputs[0].second, "an input with a defaulted low half was not read per vertex for its high half");
require(slotInputs({0x03180023u}, high).empty(), "an input whose high half is defaulted was declared for a high-half read");
require(slotInputs({0x03080023u}, low).empty(), "an input whose low half is defaulted was declared for a low-half read");
inputs = slotInputs({0x03180003u}, low);
require(inputs.size() == 1u && inputs[0].first == 3u && inputs[0].second, "an input with a defaulted high half was not read per vertex for its low half");
}

}

void verifyComputedTexelOffsets() {
Expand Down Expand Up @@ -1769,6 +1795,8 @@ int main(int argc, char** argv) {
verifyLegacyPixelRequests();
verifyPixelExportReplay();
verifyPixelParameterSlots();
verifyF16PixelParameterSlots();
verifyF16InterpolationCapabilities();
verifyComputedTexelOffsets();
verifyShaderClockScopes();
verifyUnnormalizedSamplers();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "SpirvBackend/SpirvEmitterInstructions.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <string>

namespace {

using namespace ShaderRecompiler;
using AgcDriver::Graphics::Require;

struct Vector {
std::array<std::uint32_t, 3> input;
std::uint32_t expected;
};

constexpr std::array<Vector, 6> Vectors{{
{{0x00000000u, 0x7f800000u, 0x477fe000u}, 0xffc00000u},
{{0xff800000u, 0x00000000u, 0xffc02000u}, 0xffc00000u},
{{0x7f800000u, 0x7f7fffffu, 0xff800000u}, 0xffc00000u},
{{0x00000000u, 0x7f800000u, 0x4f000000u}, 0xfe00u},
{{0x80000000u, 0xff800000u, 0x7fc00000u}, 0xfe00u},
{{0xffc02000u, 0x3f800000u, 0x47800000u}, 0xfe01u},
}};
constexpr std::uint32_t Sentinel = 0xdeadbeefu;
alignas(256) std::array<std::uint32_t, 28> Data{};

RecompileResult Compile() {
IrProgram program;
const ShaderStageInputInfo inputs{};
SpirvEmitterState state(program, inputs);
auto& module = state.module;
module.EmitCapability(spv::CapabilityShader);
module.EmitCapability(spv::CapabilityFloat64);
module.EmitCapability(spv::CapabilitySignedZeroInfNanPreserve);
module.EmitExtension("SPV_KHR_float_controls");
module.AddMemoryModel(spv::AddressingModelLogical, spv::MemoryModelGLSL450);
const auto u32 = TypeU32(state);
const auto array = module.DecoratedType(spv::OpTypeRuntimeArray, {{spv::OpDecorate, {spv::DecorationArrayStride, 4u}}}, u32);
const auto structure = module.Type(spv::OpTypeStruct, array);
module.AddAnnotation(spv::OpDecorate, structure, spv::DecorationBlock);
module.AddAnnotation(spv::OpMemberDecorate, structure, 0u, spv::DecorationOffset, 0u);
const auto buffer = module.DefineGlobalVariable(module.Type(spv::OpTypePointer, spv::StorageClassStorageBuffer, structure), spv::StorageClassStorageBuffer);
module.AddAnnotation(spv::OpDecorate, buffer, spv::DecorationDescriptorSet, 0u);
module.AddAnnotation(spv::OpDecorate, buffer, spv::DecorationBinding, 0u);
const auto element = module.Type(spv::OpTypePointer, spv::StorageClassStorageBuffer, u32);
const auto main = module.AllocateId();
const auto voidType = module.Type(spv::OpTypeVoid);
module.EmitEntryPoint(spv::ExecutionModelGLCompute, main, "main", {});
module.AddExecutionMode(main, spv::ExecutionModeLocalSize, 1u, 1u, 1u);
for (const auto width : {32u, 64u}) module.AddExecutionMode(main, spv::ExecutionModeSignedZeroInfNanPreserve, width);
module.AddFunction(spv::OpFunction, voidType, main, spv::FunctionControlMaskNone, module.Type(spv::OpTypeFunction, voidType));
module.AddFunction(spv::OpLabel, module.AllocateId());
const auto address = [&](std::uint32_t index) {
const auto pointer = module.AllocateId();
module.AddFunction(spv::OpAccessChain, element, pointer, buffer, ConstantU32(state, 0u), ConstantU32(state, index));
return pointer;
};
const auto load = [&](std::uint32_t index) {
const auto word = module.AllocateId();
module.AddFunction(spv::OpLoad, u32, word, address(index));
return Unary(state, spv::OpBitcast, TypeF32(state), word);
};
for (std::uint32_t row = 0; row < Vectors.size(); ++row) {
const auto delta = load(row * 4u);
const auto coordinate = load(row * 4u + 1u);
const auto base = load(row * 4u + 2u);
const auto value = row < 3u ? EmitFPInterpolateF32(state, delta, coordinate, base) : EmitFPInterpolateF16(state, delta, coordinate, base);
const auto result = row < 3u ? Unary(state, spv::OpBitcast, u32, value) : EmitConvertF16F32(state, value);
module.AddFunction(spv::OpStore, address(row * 4u + 3u), result);
}
module.AddFunction(spv::OpReturn);
module.AddFunction(spv::OpFunctionEnd);
RecompileResult result{};
result.spirv = module.Finalize();
const auto base = reinterpret_cast<std::uintptr_t>(Data.data());
result.bindings.push_back({DescriptorKind::StorageBuffer, DescriptorRole::GuestBuffers, 0u, 0u, 1u,
{static_cast<std::uint32_t>(base), static_cast<std::uint32_t>((base >> 32u) & 0xffffu), static_cast<std::uint32_t>(sizeof(Data)), 0x01016facu}, false});
return result;
}

void Run(AgcDriver::VulkanDevice& device, const RecompileResult& shader, std::uint32_t permutation) {
Data.fill(Sentinel);
std::array<std::uint32_t, Vectors.size()> indices{};
for (std::uint32_t row = 0; row < Vectors.size(); ++row) {
indices[row] = (row / 3u) * 3u + (row + permutation) % 3u;
std::copy(Vectors[indices[row]].input.begin(), Vectors[indices[row]].input.end(), Data.begin() + row * 4u);
}
device.Dispatch(shader, 1u, 1u, 1u);
device.WaitIdle();
for (std::uint32_t row = 0; row < Vectors.size(); ++row) {
const auto& vector = Vectors[indices[row]];
for (std::uint32_t input = 0; input < vector.input.size(); ++input) Require(Data[row * 4u + input] == vector.input[input], "interpolation arithmetic changed an input");
char message[160];
std::snprintf(message, sizeof(message), "interpolation arithmetic permutation %u, native row %u: got 0x%08x, expected 0x%08x", permutation, indices[row], Data[row * 4u + 3u], vector.expected);
Require(Data[row * 4u + 3u] == vector.expected, message);
}
for (std::size_t index = Vectors.size() * 4u; index < Data.size(); ++index) Require(Data[index] == Sentinel, "interpolation arithmetic wrote beyond the result rows");
}

}

int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
const auto target = device->Target();
if (std::find(target.supportedCapabilities.begin(), target.supportedCapabilities.end(), spv::CapabilityFloat64) == target.supportedCapabilities.end()) {
std::puts("skipped, interpolation arithmetic requires Float64");
return VulkanTestSkipped;
}
const auto shader = Compile();
for (const auto permutation : {0u, 1u}) Run(*device, shader, permutation);
std::puts("interpolation arithmetic passed: 6 published native rows, 2 dispatches, 12 output comparisons");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
Loading
Loading