Merge pull request #423 from MichonGoddijn231849/feat/clip-cull-distances
feat(agc): vertex clip and cull distances
This commit is contained in:
commit
4bb2c7f394
|
|
@ -1277,6 +1277,14 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(agc_driver_sampled_cache_budget_memory agc_driver_resource_cache_storage_admission agc_driver_resource_cache_oversized_storage_admission agc_driver_resource_cache_storage_clear PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
# The driver draw test again with the draw key off: the draw cache then keys on the decoded vertex
|
||||
# stage, which must include PA_CL_VS_OUT_CNTL.
|
||||
add_test(NAME agc_driver_vertex_clip_cull_draw_no_key COMMAND agc_driver_vertex_clip_cull_draw_tests no-key)
|
||||
set_tests_properties(agc_driver_vertex_clip_cull_draw_no_key PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_DRAW_KEY=1" SKIP_RETURN_CODE 77)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_vertex_clip_cull_draw_no_key PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test(NAME agc_driver_pm4_failure COMMAND agc_driver_pm4_tests failure)
|
||||
set_tests_properties(agc_driver_pm4_failure PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77)
|
||||
add_test(NAME agc_descriptor_pool_reset_failure COMMAND agc_driver_graphics_tests --descriptor-pool-reset-failure)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/QueueState.hpp"
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
|
|
@ -84,6 +85,7 @@ struct ShaderSnapshot {
|
|||
std::shared_ptr<PreparedShaders> prepared = std::make_shared<PreparedShaders>();
|
||||
std::shared_ptr<const RegisteredShaderState> registeredState;
|
||||
std::shared_ptr<std::atomic<std::uint8_t>> workgroupUse = std::make_shared<std::atomic<std::uint8_t>>(0u);
|
||||
std::shared_ptr<std::atomic<std::uint8_t>> positionVectorUse = std::make_shared<std::atomic<std::uint8_t>>(0u);
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -92,6 +94,9 @@ std::shared_ptr<const ShaderSnapshot> ProgramSnapshot(const ShaderRegistry& shad
|
|||
void NoteRawGraphicsDraw();
|
||||
bool RawGraphicsDrawn();
|
||||
bool ProgramUsesWorkgroup(const ShaderSnapshot& snapshot, std::size_t codeOffset);
|
||||
// The PA_CL_VS_OUT_CNTL position export bits a vertex program is compiled with: none for a program
|
||||
// without pos1..pos3 exports, so its artifact does not depend on them.
|
||||
std::uint32_t PositionExportLayout(const ShaderSnapshot* snapshot, std::size_t codeOffset, std::span<const std::uint32_t> code, std::uint32_t paClVsOutCntl);
|
||||
bool VertexWorkgroupRequired(const QueueState& queue, const ShaderRegistry& registry);
|
||||
|
||||
std::shared_ptr<const ShaderRecompiler::SourceHandle> PrepareShaderWithDiagnostics(const ShaderRecompiler::RecompileRequest& request, ShaderRecompiler::ShaderPreparationContext* preparation = nullptr);
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
|
|||
if (program.binary.stage == Stage::Fragment || roles[i] == Role::GeometryBack) return;
|
||||
decodeReads[i].clear();
|
||||
vertexInfos[i] = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, &decodeReads[i], false, 8u - program.firstUserSgpr);
|
||||
if (vertexInfos[i]) vertexInfos[i]->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, readRegister(queue.context, 0x207));
|
||||
};
|
||||
if (!registerKey) {
|
||||
for (std::size_t i = 0; i < programs.size(); ++i) decodeVertexInfo(i);
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr<Vulk
|
|||
mix(vertex.fetchAttribReg);
|
||||
mix(vertex.fetchBufferReg);
|
||||
mix(vertex.fetchEmbedded);
|
||||
mix(vertex.paClVsOutCntl);
|
||||
for (std::uint32_t r = 0; r < vertex.resourcesNum; ++r) {
|
||||
for (const auto field : vertex.resources[r].fields) mix(field);
|
||||
const auto& destination = vertex.resourcesDst[r];
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ std::uint64_t Driver::drawRegisterKey(const QueueState& queue, const ShaderRegis
|
|||
}
|
||||
|
||||
bool Driver::sameVertexInfo(const ShaderRecompiler::ShaderVertexStageInfo& a, const ShaderRecompiler::ShaderVertexStageInfo& b) {
|
||||
if (a.resourcesNum != b.resourcesNum || a.fetchAttribReg != b.fetchAttribReg || a.fetchBufferReg != b.fetchBufferReg || a.fetchEmbedded != b.fetchEmbedded) return false;
|
||||
if (a.resourcesNum != b.resourcesNum || a.fetchAttribReg != b.fetchAttribReg || a.fetchBufferReg != b.fetchBufferReg || a.fetchEmbedded != b.fetchEmbedded || a.paClVsOutCntl != b.paClVsOutCntl) return false;
|
||||
for (std::uint32_t i = 0; i < a.resourcesNum && i < a.resources.size(); ++i) {
|
||||
if (a.resources[i].fields != b.resources[i].fields) return false;
|
||||
const auto& x = a.resourcesDst[i];
|
||||
|
|
|
|||
|
|
@ -167,6 +167,23 @@ bool ProgramUsesWorkgroup(const ShaderSnapshot& snapshot, std::size_t codeOffset
|
|||
return used;
|
||||
}
|
||||
|
||||
std::uint32_t PositionExportLayout(const ShaderSnapshot* snapshot, std::size_t codeOffset, std::span<const std::uint32_t> code, std::uint32_t paClVsOutCntl) {
|
||||
constexpr std::uint8_t Unknown = 0, Unused = 1, Used = 2;
|
||||
const auto bits = paClVsOutCntl & Graphics::PositionExportLayoutBits;
|
||||
if (bits == 0) return 0;
|
||||
const bool cached = snapshot != nullptr && codeOffset == 0;
|
||||
if (cached) {
|
||||
const auto use = snapshot->positionVectorUse->load(std::memory_order_acquire);
|
||||
if (use != Unknown) return use == Used ? bits : 0;
|
||||
}
|
||||
const auto decoded = ShaderRecompiler::RdnaInstructionDecoder{}.Decode(code);
|
||||
const bool used = std::any_of(decoded.instructions.begin(), decoded.instructions.end(), [](const auto& instruction) {
|
||||
return instruction.family == ShaderRecompiler::RdnaInstructionFamily::EXP && instruction.exportTarget >= 0x0du && instruction.exportTarget <= 0x0fu && instruction.exportEnableMask != 0;
|
||||
});
|
||||
if (cached) snapshot->positionVectorUse->store(used ? Used : Unused, std::memory_order_release);
|
||||
return used ? bits : 0;
|
||||
}
|
||||
|
||||
|
||||
bool VertexWorkgroupRequired(const QueueState& queue, const ShaderRegistry& registry) {
|
||||
Graphics::NoteRegisterRead(Graphics::RegisterBank::Context, 0x2d5);
|
||||
|
|
@ -676,7 +693,11 @@ std::unique_ptr<RegisteredPreparation> PlanRegistered(const ShaderSnapshot& snap
|
|||
if (firstUser == 0) userCount += 8;
|
||||
}
|
||||
plan->userData.resize(userCount);
|
||||
if (stage != Stage::Compute && stage != Stage::Fragment && snapshot.type != 6) vertex = Graphics::DecodeVertexStageInfo(snapshot.header, snapshot.headerAddress, plan->userData, nullptr, true, 8u - firstUser);
|
||||
if (stage != Stage::Compute && stage != Stage::Fragment && snapshot.type != 6) {
|
||||
vertex = Graphics::DecodeVertexStageInfo(snapshot.header, snapshot.headerAddress, plan->userData, nullptr, true, 8u - firstUser);
|
||||
const auto outputs = state.context.find(0x207u);
|
||||
if (vertex && outputs != state.context.end()) vertex->paClVsOutCntl = PositionExportLayout(&snapshot, codeOffset, std::span(snapshot.code).subspan(codeOffset), outputs->second);
|
||||
}
|
||||
plan->swappc = {vertex.has_value(), firstUser, userCount};
|
||||
if (deferred != nullptr) {
|
||||
if (const auto counter = ShaderRecompiler::UnresolvableSwappcTarget(plan->decoded, &plan->swappc)) {
|
||||
|
|
@ -910,6 +931,7 @@ std::vector<PreparedGraphicsStage> PrepareGraphicsStages(const DrawDecode& decod
|
|||
const auto wave = fragment ? decoded.state.stages.fragmentWaveSize : decoded.state.stages.vertexWaveSize;
|
||||
std::optional<ShaderRecompiler::ShaderVertexStageInfo> vertex;
|
||||
if (!fragment) vertex = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true, 8u - program.firstUserSgpr);
|
||||
if (vertex) vertex->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, decoded.state.paClVsOutCntl);
|
||||
pushOffset = Graphics::StagePushOffset(pushOffset, program.binary.stage, fixedSlots);
|
||||
const auto offset = pushOffset;
|
||||
const auto room = (fixedSlots && fragment ? Graphics::PipelinePushSlotBytes : capacity) - pushOffset % Graphics::PipelinePushSlotBytes;
|
||||
|
|
|
|||
|
|
@ -269,6 +269,7 @@ struct VulkanDevice::State {
|
|||
bool geometryShader = false;
|
||||
bool sampleRateShading = false;
|
||||
bool shaderClipDistance = false;
|
||||
bool shaderCullDistance = false;
|
||||
bool shaderClock = false;
|
||||
bool narrowSubgroupClock = false;
|
||||
bool float64DenormPreserve = false;
|
||||
|
|
@ -1398,8 +1399,11 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
|
|||
state->sampleRateShading = enabled.sampleRateShading == VK_TRUE;
|
||||
if (enabled.geometryShader) state->capabilities.push_back(spv::CapabilityGeometry);
|
||||
enabled.shaderClipDistance = available.shaderClipDistance;
|
||||
enabled.shaderCullDistance = available.shaderCullDistance;
|
||||
state->shaderClipDistance = enabled.shaderClipDistance == VK_TRUE;
|
||||
state->shaderCullDistance = enabled.shaderCullDistance == VK_TRUE;
|
||||
if (enabled.shaderClipDistance) state->capabilities.push_back(spv::CapabilityClipDistance);
|
||||
if (enabled.shaderCullDistance) state->capabilities.push_back(spv::CapabilityCullDistance);
|
||||
if (enabled.shaderStorageImageWriteWithoutFormat) state->capabilities.push_back(spv::CapabilityStorageImageWriteWithoutFormat);
|
||||
if (enabled.shaderStorageImageReadWithoutFormat) state->capabilities.push_back(spv::CapabilityStorageImageReadWithoutFormat);
|
||||
state->storageImageWithoutFormat = enabled.shaderStorageImageWriteWithoutFormat == VK_TRUE && enabled.shaderStorageImageReadWithoutFormat == VK_TRUE;
|
||||
|
|
@ -3128,6 +3132,7 @@ Graphics::Context VulkanDevice::buildContext() const {
|
|||
context.geometryShader = state->geometryShader;
|
||||
context.sampleRateShading = state->sampleRateShading;
|
||||
context.shaderClipDistance = state->shaderClipDistance;
|
||||
context.shaderCullDistance = state->shaderCullDistance;
|
||||
context.nullDescriptors = state->shaderProfile != nullptr && state->shaderProfile->NullDescriptors();
|
||||
context.bufferInt64Atomics = state->bufferInt64Atomics;
|
||||
context.placeholderImages = state->placeholderImages.get();
|
||||
|
|
|
|||
|
|
@ -161,6 +161,7 @@ struct Context {
|
|||
bool geometryShader = false;
|
||||
bool sampleRateShading = false;
|
||||
bool shaderClipDistance = false;
|
||||
bool shaderCullDistance = false;
|
||||
bool nullDescriptors = false;
|
||||
bool bufferInt64Atomics = false;
|
||||
const PlaceholderImages* placeholderImages = nullptr;
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ bool ValidateSampleLocations(const Context& context, const State& state);
|
|||
void ValidateShaderPair(const ShaderRecompiler::RecompileResult& vertex, const ShaderRecompiler::RecompileResult& fragment);
|
||||
bool ShadersWriteMemory(std::span<const CompiledShader> shaders);
|
||||
// Returns the color attachment locations the pixel shader writes.
|
||||
std::set<std::uint32_t> ValidateShaders(std::span<const CompiledShader> shaders, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup, bool fragmentShaderBarycentric, bool descriptorIndexing = false, bool imageInt64Atomics = false, bool geometryShader = false, bool sampleRateShading = false, bool bufferInt64Atomics = false, bool shaderClipDistance = false);
|
||||
std::set<std::uint32_t> ValidateShaders(std::span<const CompiledShader> shaders, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup, bool fragmentShaderBarycentric, bool descriptorIndexing = false, bool imageInt64Atomics = false, bool geometryShader = false, bool sampleRateShading = false, bool bufferInt64Atomics = false, bool shaderClipDistance = false, bool shaderCullDistance = false);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -118,8 +118,13 @@ struct State {
|
|||
std::uint32_t samples = 1;
|
||||
std::array<std::uint8_t, 8> sampleLocations{};
|
||||
bool customSampleLocations = false;
|
||||
std::uint32_t paClVsOutCntl = 0;
|
||||
};
|
||||
|
||||
// PA_CL_VS_OUT_CNTL bits that place position exports (clip and cull distances, the misc and the two
|
||||
// distance vectors); a vertex program that exports pos1..pos3 depends on them.
|
||||
inline constexpr std::uint32_t PositionExportLayoutBits = 0xffffu | (1u << 21u) | (1u << 22u) | (1u << 23u);
|
||||
|
||||
ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup = false);
|
||||
State DecodeState(const QueueState& queue, bool vertexWorkgroup = false);
|
||||
std::array<VkSampleLocationEXT, 8> SampleLocations(const State& state);
|
||||
|
|
|
|||
|
|
@ -686,6 +686,9 @@ bool ValidationKey(const Context& context, std::span<const CompiledShader> shade
|
|||
add(context.subgroup.supportedStages);
|
||||
add(context.subgroup.supportedOperations);
|
||||
add(context.fragmentShaderBarycentric);
|
||||
add(context.shaderClipDistance);
|
||||
add(context.shaderCullDistance);
|
||||
add(state.paClVsOutCntl);
|
||||
add(state.stages.mesh.has_value());
|
||||
if (state.stages.mesh) {
|
||||
const auto& mesh = *state.stages.mesh;
|
||||
|
|
@ -737,7 +740,7 @@ std::set<std::uint32_t> CachedFragmentOutputs(const Context& context, std::span<
|
|||
}
|
||||
std::set<std::uint32_t> outputs;
|
||||
try {
|
||||
outputs = ValidateShaders(shaders, state, context.subgroup, context.fragmentShaderBarycentric, context.descriptorIndexing, context.imageInt64Atomics, context.geometryShader, context.sampleRateShading, context.bufferInt64Atomics, context.shaderClipDistance);
|
||||
outputs = ValidateShaders(shaders, state, context.subgroup, context.fragmentShaderBarycentric, context.descriptorIndexing, context.imageInt64Atomics, context.geometryShader, context.sampleRateShading, context.bufferInt64Atomics, context.shaderClipDistance, context.shaderCullDistance);
|
||||
} catch (const std::exception& error) {
|
||||
if (keyed) {
|
||||
std::lock_guard lock(validationMutex());
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp"
|
||||
#include <spirv/unified1/spirv.hpp>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
|
|
@ -44,7 +45,8 @@ struct Module {
|
|||
std::map<std::uint32_t, std::string> outputs;
|
||||
std::optional<std::string> secondSource;
|
||||
bool position = false;
|
||||
bool clipDistance = false;
|
||||
std::uint32_t clipDistances = 0;
|
||||
std::uint32_t cullDistances = 0;
|
||||
bool primitiveIndices = false;
|
||||
bool fragmentBarycentric = false;
|
||||
std::map<std::uint32_t, std::vector<std::uint32_t>> modes;
|
||||
|
|
@ -111,9 +113,13 @@ struct Module {
|
|||
if (!input) position = true;
|
||||
return;
|
||||
}
|
||||
if (value == spv::BuiltInClipDistance && !input && (vertex || stage == Stage::TessellationEvaluation)) {
|
||||
Require(!clipDistance && Signature(type) == "f32[1]", "invalid or duplicate clip distance output");
|
||||
clipDistance = true;
|
||||
// One gl_ClipDistance array holds the guest clip distances and the recompiler's all-zero position distance.
|
||||
if (!input && ((value == spv::BuiltInClipDistance && (vertex || stage == Stage::TessellationEvaluation)) || (value == spv::BuiltInCullDistance && vertex))) {
|
||||
const auto signature = Signature(type);
|
||||
auto& count = value == spv::BuiltInClipDistance ? clipDistances : cullDistances;
|
||||
Require(count == 0 && signature.size() == 6 && signature.starts_with("f32[") && signature[4] >= '1' && signature[4] <= '8' && signature[5] == ']', "invalid or duplicate clip or cull distance output");
|
||||
count = static_cast<std::uint32_t>(signature[4] - '0');
|
||||
Require(clipDistances + cullDistances <= 8, "more than eight clip and cull distances in one shader");
|
||||
return;
|
||||
}
|
||||
if (stage == Stage::TessellationControl || stage == Stage::TessellationEvaluation || stage == Stage::Mesh) {
|
||||
|
|
@ -143,7 +149,7 @@ struct Module {
|
|||
}
|
||||
};
|
||||
|
||||
Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup, bool fragmentShaderBarycentric, bool descriptorIndexing, bool imageInt64Atomics, bool geometryShader, bool sampleRateShading, bool bufferInt64Atomics, bool shaderClipDistance) {
|
||||
Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup, bool fragmentShaderBarycentric, bool descriptorIndexing, bool imageInt64Atomics, bool geometryShader, bool sampleRateShading, bool bufferInt64Atomics, bool shaderClipDistance, bool shaderCullDistance) {
|
||||
using Stage = ShaderRecompiler::ShaderStage;
|
||||
Require(compiled.program != nullptr, "missing compiled shader");
|
||||
const auto& shader = *compiled.program;
|
||||
|
|
@ -237,6 +243,12 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
fragment &&
|
||||
capability == spv::CapabilityStencilExportEXT;
|
||||
|
||||
if ((vertex || evaluation) && (capability == spv::CapabilityClipDistance || capability == spv::CapabilityCullDistance)) {
|
||||
char word[16];
|
||||
std::snprintf(word, sizeof(word), "0x%08x", state.paClVsOutCntl);
|
||||
Require(capability == spv::CapabilityClipDistance ? shaderClipDistance : vertex && shaderCullDistance, std::string("vertex clip or cull distance needs shaderClipDistance or shaderCullDistance, which the device lacks (PA_CL_VS_OUT_CNTL=") + word + ")");
|
||||
}
|
||||
|
||||
// Enabled unconditionally or by the device setup in VulkanDevice.
|
||||
const bool isFeatureCapability =
|
||||
capability == spv::CapabilitySampled1D ||
|
||||
|
|
@ -248,6 +260,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
(fragment && sampleRateShading && capability == spv::CapabilitySampleRateShading) ||
|
||||
capability == spv::CapabilityImageMSArray ||
|
||||
capability == spv::CapabilityStorageImageMultisample ||
|
||||
(vertex && shaderCullDistance && capability == spv::CapabilityCullDistance) ||
|
||||
capability == spv::CapabilityStorageImageWriteWithoutFormat ||
|
||||
capability == spv::CapabilityStorageImageReadWithoutFormat ||
|
||||
capability == spv::CapabilityInt64 ||
|
||||
|
|
@ -570,7 +583,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
|
||||
}
|
||||
|
||||
std::set<std::uint32_t> ValidateShaders(std::span<const CompiledShader> shaders, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup, bool fragmentShaderBarycentric, bool descriptorIndexing, bool imageInt64Atomics, bool geometryShader, bool sampleRateShading, bool bufferInt64Atomics, bool shaderClipDistance) {
|
||||
std::set<std::uint32_t> ValidateShaders(std::span<const CompiledShader> shaders, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup, bool fragmentShaderBarycentric, bool descriptorIndexing, bool imageInt64Atomics, bool geometryShader, bool sampleRateShading, bool bufferInt64Atomics, bool shaderClipDistance, bool shaderCullDistance) {
|
||||
using Stage = ShaderRecompiler::ShaderStage;
|
||||
const bool tessellation = state.stages.path == ShaderPath::Tessellation;
|
||||
const bool mesh = state.stages.path == ShaderPath::Geometry;
|
||||
|
|
@ -591,7 +604,7 @@ std::set<std::uint32_t> ValidateShaders(std::span<const CompiledShader> shaders,
|
|||
Require(shaders[i].program != nullptr, "missing compiled shader");
|
||||
Require(shaders[i].stage == expected, "graphics stage order disagrees");
|
||||
for (const auto& binding : shaders[i].program->bindings) Require(binding.descriptorSet == ShaderRecompiler::RuntimeAbi::DescriptorSet || (shaders[i].stage == ShaderRecompiler::ShaderStage::Fragment && binding.descriptorSet == ShaderRecompiler::RuntimeAbi::FragmentDescriptorSet), "graphics resource uses a descriptor set other than its stage's");
|
||||
const auto current = Inspect(shaders[i], state, subgroup, fragmentShaderBarycentric, descriptorIndexing, imageInt64Atomics, geometryShader, sampleRateShading, bufferInt64Atomics, shaderClipDistance);
|
||||
const auto current = Inspect(shaders[i], state, subgroup, fragmentShaderBarycentric, descriptorIndexing, imageInt64Atomics, geometryShader, sampleRateShading, bufferInt64Atomics, shaderClipDistance, shaderCullDistance);
|
||||
if (i != 0) {
|
||||
for (const auto& [location, signature] : current.inputs) {
|
||||
const auto output = previous.outputs.find(location);
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ VkConservativeRasterizationModeEXT decodeConservativeRasterization(const QueueSt
|
|||
// Render target index, viewport index and the misc export vector that carries them are accepted but
|
||||
// not routed: color targets are single-layer, so layered draws land in layer 0.
|
||||
constexpr std::uint32_t LayerExports = (1u << 18u) | (1u << 19u) | (1u << 21u) | (1u << 24u);
|
||||
constexpr std::uint32_t ClipCullExports = 0xffffu | (1u << 22u) | (1u << 23u);
|
||||
constexpr std::uint32_t DepthControlMask = ~0x007007f0u;
|
||||
// EXEC_ON_HIER_FAIL / EXEC_ON_NOOP / EXEC_IF_OVERLAPPED (bits 9, 10, 17) only force the pixel shader
|
||||
// to run, which it always does here.
|
||||
|
|
@ -698,7 +699,9 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
|
|||
std::fprintf(stderr, "[gpu] layer/viewport index vertex exports are ignored (PA_CL_VS_OUT_CNTL=0x%08x)\n", read(cx, 0x207));
|
||||
}
|
||||
}
|
||||
zero(cx, 0x207, ~LayerExports, "clip distances, layer, viewport or auxiliary vertex exports");
|
||||
zero(cx, 0x207, ~(LayerExports | ClipCullExports), "layer, viewport or auxiliary vertex exports");
|
||||
Require(std::popcount(read(cx, 0x207) & 0xffffu) <= 8, "more than eight clip and cull distances are unsupported");
|
||||
result.paClVsOutCntl = read(cx, 0x207);
|
||||
result.samples = decodeSamples(cx);
|
||||
decodeSampleLocations(cx, result);
|
||||
{
|
||||
|
|
@ -1184,7 +1187,8 @@ std::string DrawRejection(const QueueState& queue, bool indexed) {
|
|||
if (value(queue.userConfig, 0x242, primitive) && (primitive & 0x3fu) != 1 && (primitive & 0x3fu) != 2 && (primitive & 0x3fu) != 3 && (primitive & 0x3fu) != 4 && (primitive & 0x3fu) != 5 && (primitive & 0x3fu) != 6) return "AGC graphics: primitive restart is only supported for point, line and triangle topologies";
|
||||
if (value(cx, 0x103, resetIndex) && (resetIndex & 0xffffu) != 0xffffu) return "AGC graphics: primitive restart index other than all ones is unsupported";
|
||||
}
|
||||
if (auto reason = nonzero(cx, 0x207, ~LayerExports, "clip distances, layer, viewport or auxiliary vertex exports"); !reason.empty()) return reason;
|
||||
if (auto reason = nonzero(cx, 0x207, ~(LayerExports | ClipCullExports), "layer, viewport or auxiliary vertex exports"); !reason.empty()) return reason;
|
||||
if (value(cx, 0x207, word) && std::popcount(word & 0xffffu) > 8) return require(false, "more than eight clip and cull distances are unsupported");
|
||||
if (value(cx, 0x200, word)) {
|
||||
const auto renderControl = find(cx, 0x000);
|
||||
const bool depthClear = renderControl != cx.end() && (renderControl->second & 1u) != 0;
|
||||
|
|
|
|||
|
|
@ -2248,6 +2248,18 @@ void multisampleTests() {
|
|||
Require(TakeMultisampledClear(clearedCmask, pending, halfFloat, clear) && clear.float32[0] == 1.0f && !TakeMultisampledClear(clearedCmask, pending, halfFloat, clear), "a pending CMASK clear and a whole fill did not clear once to the CMASK clear color");
|
||||
}
|
||||
|
||||
void clipDistanceTests() {
|
||||
auto queue = makeState();
|
||||
queue.context[0x207] = 0x0040000f;
|
||||
Require(AgcDriver::Graphics::DecodeState(queue).paClVsOutCntl == 0x0040000fu, "DecodeState dropped the PA_CL_VS_OUT_CNTL value the validator quotes");
|
||||
queue.context[0x1b3] = 2;
|
||||
queue.context[0x1b4] = 2;
|
||||
Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "four clip distances were rejected by the precheck");
|
||||
queue.context[0x207] = 0x004001ff;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "more than eight clip and cull distances");
|
||||
Require(AgcDriver::Graphics::DrawRejection(queue, false).find("more than eight") != std::string::npos, "nine clip distances passed the precheck");
|
||||
}
|
||||
|
||||
void DepthClipTests() {
|
||||
auto queue = makeState();
|
||||
const auto direct = AgcDriver::Graphics::DecodeState(queue);
|
||||
|
|
@ -3356,6 +3368,8 @@ struct ModuleShape {
|
|||
bool fragDepth = false;
|
||||
std::uint32_t sampleMaskLength = 0;
|
||||
std::uint32_t floatControlsWidth = 0;
|
||||
std::uint32_t clipDistanceLength = 0;
|
||||
std::uint32_t cullDistanceLength = 0;
|
||||
};
|
||||
|
||||
void emit(std::vector<std::uint32_t>& out, spv::Op op, std::initializer_list<std::uint32_t> operands) {
|
||||
|
|
@ -3455,6 +3469,30 @@ std::vector<std::uint32_t> makeModule(const ModuleShape& shape) {
|
|||
emit(annotations, spv::OpDecorate, {variable, spv::DecorationBuiltIn, spv::BuiltInSampleMask});
|
||||
extraInterface.push_back(variable);
|
||||
}
|
||||
if (shape.clipDistanceLength != 0) {
|
||||
const auto length = id();
|
||||
const auto array = id();
|
||||
const auto pointer = id();
|
||||
const auto variable = id();
|
||||
emit(declarations, spv::OpConstant, {uintType, length, shape.clipDistanceLength});
|
||||
emit(declarations, spv::OpTypeArray, {array, floatType, length});
|
||||
emit(declarations, spv::OpTypePointer, {pointer, spv::StorageClassOutput, array});
|
||||
emit(declarations, spv::OpVariable, {pointer, variable, spv::StorageClassOutput});
|
||||
emit(annotations, spv::OpDecorate, {variable, spv::DecorationBuiltIn, spv::BuiltInClipDistance});
|
||||
extraInterface.push_back(variable);
|
||||
}
|
||||
if (shape.cullDistanceLength != 0) {
|
||||
const auto length = id();
|
||||
const auto array = id();
|
||||
const auto pointer = id();
|
||||
const auto variable = id();
|
||||
emit(declarations, spv::OpConstant, {uintType, length, shape.cullDistanceLength});
|
||||
emit(declarations, spv::OpTypeArray, {array, floatType, length});
|
||||
emit(declarations, spv::OpTypePointer, {pointer, spv::StorageClassOutput, array});
|
||||
emit(declarations, spv::OpVariable, {pointer, variable, spv::StorageClassOutput});
|
||||
emit(annotations, spv::OpDecorate, {variable, spv::DecorationBuiltIn, spv::BuiltInCullDistance});
|
||||
extraInterface.push_back(variable);
|
||||
}
|
||||
if (shape.perVertex) {
|
||||
const auto length = id();
|
||||
const auto array = id();
|
||||
|
|
@ -3524,6 +3562,8 @@ std::vector<std::uint32_t> makeModule(const ModuleShape& shape) {
|
|||
emit(words, spv::OpCapability, {spv::CapabilityShader});
|
||||
if (shape.sampleId) emit(words, spv::OpCapability, {spv::CapabilitySampleRateShading});
|
||||
if (shape.layer) emit(words, spv::OpCapability, {spv::CapabilityGeometry});
|
||||
if (shape.clipDistanceLength != 0) emit(words, spv::OpCapability, {spv::CapabilityClipDistance});
|
||||
if (shape.cullDistanceLength != 0) emit(words, spv::OpCapability, {spv::CapabilityCullDistance});
|
||||
if (shape.barycentric) {
|
||||
emit(words, spv::OpCapability, {spv::CapabilityFragmentBarycentricKHR});
|
||||
const std::string extension = "SPV_KHR_fragment_shader_barycentric";
|
||||
|
|
@ -4814,6 +4854,19 @@ void validationTests() {
|
|||
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false, false, false, true, false); }, "unsupported device capability 35");
|
||||
pixel.spirv = makeModule({.fragment = true, .sampleMaskLength = 2});
|
||||
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false); }, "unsupported fragment built-in");
|
||||
pixel.spirv = makeModule({.fragment = true});
|
||||
vertex.spirv = makeModule({.parameterOutput = true, .clipDistanceLength = 4, .cullDistanceLength = 4});
|
||||
AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false, false, false, false, false, false, true, true);
|
||||
state.paClVsOutCntl = 0x0040000fu;
|
||||
vertex.spirv = makeModule({.parameterOutput = true, .clipDistanceLength = 4});
|
||||
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false, false, false, false, false, false, false, true); }, "PA_CL_VS_OUT_CNTL=0x0040000f");
|
||||
AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false, false, false, false, false, false, true, false);
|
||||
vertex.spirv = makeModule({.parameterOutput = true, .cullDistanceLength = 4});
|
||||
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false, false, false, false, false, false, true, false); }, "PA_CL_VS_OUT_CNTL=0x0040000f");
|
||||
AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false, false, false, false, false, false, false, true);
|
||||
state.paClVsOutCntl = 0;
|
||||
vertex.spirv = makeModule({.parameterOutput = true, .clipDistanceLength = 9});
|
||||
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false, false, false, false, false, false, true, true); }, "clip or cull distance");
|
||||
vertex.spirv = makeModule({.parameterOutput = true, .sampleId = true});
|
||||
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false); }, "unsupported device capability 35");
|
||||
vertex.spirv = makeModule({.parameterOutput = true});
|
||||
|
|
@ -5392,6 +5445,7 @@ int main(int argc, char** argv) {
|
|||
uint8x4TargetTests();
|
||||
multisampleTests();
|
||||
MsaaEnableSingleSampleTests();
|
||||
clipDistanceTests();
|
||||
ShaderStageTests();
|
||||
NggVertexWorkgroupTests();
|
||||
TuningFieldTests();
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include "Optimization/SrtWalker/SrtFlatSlotClasses.hpp"
|
||||
#include "Optimization/SrtWalker.hpp"
|
||||
#include "IntermediateRepresentation/IrBuilder.hpp"
|
||||
#include "IntermediateRepresentation/IrProgram.hpp"
|
||||
#include "SpirvBackend/SpirvAnalysis.hpp"
|
||||
#if ANYPS5_ENABLE_SPIRV_TOOLS
|
||||
#include "SpirvBackend/SpirvOptimizer.hpp"
|
||||
|
|
@ -1660,6 +1661,22 @@ void verifyMeshConfiguration() {
|
|||
request.graphics = GraphicsCompileContext{0u, {}, mesh, std::nullopt, {}};
|
||||
const auto replay = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(request));
|
||||
require(replay.request.graphics.has_value() && replay.request.graphics->mesh.has_value() && replay.request.graphics->mesh->esgsItemSize == 12u && replay.request.graphics->mesh->primitivesPerGroup == 21u, "mesh configuration was lost in serialization");
|
||||
{
|
||||
RecompileRequest vertex{};
|
||||
vertex.shader = {ShaderStage::Vertex, 0x10000u, code, 0, {}};
|
||||
vertex.context.waveSize = 64;
|
||||
ShaderVertexStageInfo info{};
|
||||
info.paClVsOutCntl = 0x0040000fu;
|
||||
vertex.context.vertex = info;
|
||||
const auto back = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(vertex));
|
||||
require(back.request.context.vertex.has_value() && back.request.context.vertex->paClVsOutCntl == 0x0040000fu, "PA_CL_VS_OUT_CNTL was lost in serialization");
|
||||
std::vector<std::uint64_t> clipped;
|
||||
RecompileCacheKey::Build(vertex, clipped);
|
||||
vertex.context.vertex->paClVsOutCntl = 0;
|
||||
std::vector<std::uint64_t> unclipped;
|
||||
RecompileCacheKey::Build(vertex, unclipped);
|
||||
require(clipped != unclipped, "PA_CL_VS_OUT_CNTL is not part of the recompile cache key");
|
||||
}
|
||||
std::vector<std::uint64_t> key;
|
||||
RecompileCacheKey::Build(request, key);
|
||||
const auto first = key;
|
||||
|
|
@ -1809,13 +1826,13 @@ void verifyPixelRequestSerialization() {
|
|||
minimal.context.pixel = ShaderPixelStageInfo{};
|
||||
minimal.context.pixel->rasterPrimitive = RasterPrimitive::Rectangle;
|
||||
const auto encoded = serializer.Serialize(minimal);
|
||||
require(requestPrefix(encoded, 8u) == "NVNQQRMAAAA=", "new requests did not use serialization version 19");
|
||||
require(requestPrefix(encoded, 8u) == "NVNQQRQAAAA=", "new requests did not use serialization version 20");
|
||||
require(serializer.Deserialize(encoded).request.context.pixel->rasterPrimitive == RasterPrimitive::Rectangle, "the pixel primitive class did not round-trip");
|
||||
constexpr std::size_t mappingOffset = 8u + 37u + 18u + 163u;
|
||||
for (std::size_t bytes = 0; bytes < 20u; ++bytes) {
|
||||
expectFailure([&] { static_cast<void>(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source, alpha-to-coverage, stencil reference export flag or primitive class was accepted");
|
||||
}
|
||||
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQRQAAAA="}) {
|
||||
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQRUAAAA="}) {
|
||||
expectFailure([&] { static_cast<void>(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted");
|
||||
}
|
||||
static constexpr std::string_view version18Request{
|
||||
|
|
@ -1829,6 +1846,102 @@ void verifyPixelRequestSerialization() {
|
|||
require(version18.request.target.registrationModules && version18.request.target.registrationWordLimit == 1234u && version18.request.target.vertexSubgroups, "a version 18 request trailer was misread");
|
||||
}
|
||||
|
||||
void verifyPositionExportComponents() {
|
||||
constexpr std::uint32_t none = 0xffffffffu;
|
||||
struct Case {
|
||||
std::uint32_t control;
|
||||
std::array<std::uint32_t, 4> clip;
|
||||
std::array<std::uint32_t, 4> cull;
|
||||
};
|
||||
const std::array<Case, 6> cases{{
|
||||
{0x00400000u, {none, none, none, none}, {none, none, none, none}},
|
||||
{0x00400001u, {0u, none, none, none}, {none, none, none, none}},
|
||||
{0x00400002u, {none, 0u, none, none}, {none, none, none, none}},
|
||||
{0x00400005u, {0u, none, 1u, none}, {none, none, none, none}},
|
||||
{0x00400100u, {none, none, none, none}, {0u, none, none, none}},
|
||||
{0x00400400u, {none, none, none, none}, {none, none, 0u, none}},
|
||||
}};
|
||||
for (const auto& test : cases) {
|
||||
for (std::uint32_t component = 0; component < 4; ++component) {
|
||||
const auto output = ShaderRecompiler::DecodePositionExportComponent(test.control, 1u, component);
|
||||
require(output.clipDistance == test.clip[component] && output.cullDistance == test.cull[component], "PA_CL_VS_OUT_CNTL decoded a position component to the wrong clip or cull distance");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> decodeBase64(std::string_view text) {
|
||||
constexpr std::string_view alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
std::vector<std::uint8_t> bytes;
|
||||
std::uint32_t chunk = 0;
|
||||
std::size_t bits = 0;
|
||||
for (const char symbol : text) {
|
||||
if (symbol == '=') break;
|
||||
const auto value = alphabet.find(symbol);
|
||||
require(value != std::string_view::npos, "invalid base64 symbol");
|
||||
chunk = ((chunk << 6u) | static_cast<std::uint32_t>(value)) & 0xffffffu;
|
||||
bits += 6u;
|
||||
if (bits >= 8u) {
|
||||
bits -= 8u;
|
||||
bytes.push_back(static_cast<std::uint8_t>((chunk >> bits) & 0xffu));
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
std::string encodeBase64(const std::vector<std::uint8_t>& bytes) {
|
||||
constexpr std::string_view alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
std::string text;
|
||||
for (std::size_t index = 0; index < bytes.size(); index += 3u) {
|
||||
const std::size_t count = std::min<std::size_t>(3u, bytes.size() - index);
|
||||
std::uint32_t chunk = static_cast<std::uint32_t>(bytes[index]) << 16u;
|
||||
if (count > 1u) chunk |= static_cast<std::uint32_t>(bytes[index + 1u]) << 8u;
|
||||
if (count > 2u) chunk |= bytes[index + 2u];
|
||||
for (std::size_t symbol = 0; symbol < 4u; ++symbol) {
|
||||
text.push_back(symbol <= count ? alphabet[(chunk >> (18u - 6u * symbol)) & 0x3fu] : '=');
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
void verifyVertexInfoVersion19() {
|
||||
using namespace ShaderRecompiler;
|
||||
const std::array<std::uint32_t, 1> code{0xbf810000u};
|
||||
const RequestSerializer serializer;
|
||||
RecompileRequest vertex{};
|
||||
vertex.shader = {ShaderStage::Vertex, 0x10000u, code, 0, {}};
|
||||
vertex.context.waveSize = 64;
|
||||
vertex.context.vertex = ShaderVertexStageInfo{};
|
||||
const auto unmarked = serializer.Serialize(vertex);
|
||||
vertex.context.vertex->paClVsOutCntl = 0x89abcdefu;
|
||||
const auto marked = serializer.Serialize(vertex);
|
||||
const auto plain = decodeBase64(unmarked);
|
||||
const auto flagged = decodeBase64(marked);
|
||||
require(plain.size() == flagged.size(), "PA_CL_VS_OUT_CNTL changed the request size");
|
||||
std::size_t offset = 0;
|
||||
while (offset < plain.size() && plain[offset] == flagged[offset]) ++offset;
|
||||
require(offset + 4u <= plain.size() && flagged[offset] == 0xefu && flagged[offset + 1u] == 0xcdu && flagged[offset + 2u] == 0xabu && flagged[offset + 3u] == 0x89u, "PA_CL_VS_OUT_CNTL is not the four bytes the version 20 vertex info adds");
|
||||
require(std::equal(plain.begin() + offset + 4u, plain.end(), flagged.begin() + offset + 4u), "PA_CL_VS_OUT_CNTL moved the fields after it");
|
||||
auto legacy = plain;
|
||||
legacy.erase(legacy.begin() + offset, legacy.begin() + offset + 4u);
|
||||
for (const auto version : {16u, 17u, 18u, 19u}) {
|
||||
legacy[4] = static_cast<std::uint8_t>(version);
|
||||
const auto replay = serializer.Deserialize(encodeBase64(legacy));
|
||||
require(replay.request.context.vertex.has_value() && replay.request.context.vertex->paClVsOutCntl == 0u, "a legacy vertex info read a PA_CL_VS_OUT_CNTL word");
|
||||
require(serializer.Serialize(replay.request) == unmarked, "a legacy vertex info misread the fields after it");
|
||||
}
|
||||
RecompileRequest pixel{};
|
||||
pixel.shader = {ShaderStage::Fragment, 0x20000u, code, 0, {}};
|
||||
pixel.context.waveSize = 64;
|
||||
pixel.context.pixel = ShaderPixelStageInfo{};
|
||||
pixel.context.pixel->rasterPrimitive = RasterPrimitive::Rectangle;
|
||||
const auto encodedPixel = serializer.Serialize(pixel);
|
||||
auto legacyPixel = decodeBase64(encodedPixel);
|
||||
legacyPixel[4] = 19u;
|
||||
const auto replayPixel = serializer.Deserialize(encodeBase64(legacyPixel));
|
||||
require(replayPixel.request.context.pixel.has_value() && replayPixel.request.context.pixel->rasterPrimitive == RasterPrimitive::Rectangle, "a main version 19 request lost its pixel primitive class");
|
||||
require(serializer.Serialize(replayPixel.request) == encodedPixel, "a main version 19 request misread the fields after its pixel primitive class");
|
||||
}
|
||||
|
||||
void verifyLegacyPixelRequests() {
|
||||
using namespace ShaderRecompiler;
|
||||
static constexpr std::array<std::string_view, 14> legacyPixelRequests{
|
||||
|
|
@ -3372,6 +3485,8 @@ int main(int argc, char** argv) {
|
|||
verifyMeshConfiguration();
|
||||
verifyPixelInputs();
|
||||
verifyPixelRequestSerialization();
|
||||
verifyVertexInfoVersion19();
|
||||
verifyPositionExportComponents();
|
||||
verifyLegacyPixelRequests();
|
||||
verifyPixelExportReplay();
|
||||
verifyPixelParameterSlots();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,176 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Width = 192;
|
||||
constexpr std::uint32_t Height = 128;
|
||||
constexpr std::uint32_t DistanceVector = 0x00400000u;
|
||||
alignas(256) std::array<std::byte, Width * Height * 4> Pixels{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 10> VertexCode{
|
||||
0xe0382000, 0x80000005, 0xe0382010, 0x80000405, 0xbf8c3f70,
|
||||
0xf80000cf, 0x03020100, 0xf80008df, 0x07060504, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 7> PixelCode{
|
||||
0x7e0002f2, 0x7e0202f2, 0x7e0402f2, 0x7e0602f2, 0xf800080f, 0x03020100, 0xbf810000,
|
||||
};
|
||||
|
||||
struct Vertex {
|
||||
std::array<float, 4> position;
|
||||
std::array<float, 4> distances;
|
||||
};
|
||||
|
||||
constexpr std::array<float, 3> PositionX{-3.0f, 1.0f, 1.0f};
|
||||
constexpr std::array<float, 3> PositionY{-1.0f, -1.0f, 3.0f};
|
||||
constexpr std::array<float, 3> Negative{-1.0f, -1.0f, -1.0f};
|
||||
constexpr std::array<float, 3> Mixed{-1.0f, 1.0f, -1.0f};
|
||||
|
||||
enum class Visible { All, None, X, Y, XAndY };
|
||||
|
||||
struct Row {
|
||||
const char* name;
|
||||
std::uint32_t control;
|
||||
Visible visible;
|
||||
std::array<float, 3> component0;
|
||||
std::array<float, 3> component1;
|
||||
std::array<float, 3> component2;
|
||||
};
|
||||
|
||||
const std::array<Row, 7> Rows{{
|
||||
{"no enable bits", DistanceVector, Visible::All, Negative, Negative, Negative},
|
||||
{"clip plane 0 from component 0", DistanceVector | 0x1u, Visible::X, PositionX, Negative, Negative},
|
||||
{"clip plane 1 alone, from component 1", DistanceVector | 0x2u, Visible::Y, Negative, PositionY, Negative},
|
||||
{"clip planes 0x5, components 0 and 2", DistanceVector | 0x5u, Visible::XAndY, PositionX, Negative, PositionY},
|
||||
{"cull plane 0, every vertex negative", DistanceVector | 0x100u, Visible::None, Negative, Negative, Negative},
|
||||
{"cull plane 0, one vertex not negative", DistanceVector | 0x100u, Visible::All, Mixed, Negative, Negative},
|
||||
{"cull plane 0 from component 2, sparse 0x4", DistanceVector | 0x400u, Visible::None, Mixed, Negative, Negative},
|
||||
}};
|
||||
|
||||
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(data);
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu};
|
||||
}
|
||||
|
||||
bool Expected(Visible visible, std::uint32_t pixel) {
|
||||
const double x = -1.0 + 2.0 * ((pixel % Width) + 0.5) / Width;
|
||||
const double y = 1.0 - 2.0 * ((pixel / Width) + 0.5) / Height;
|
||||
switch (visible) {
|
||||
case Visible::All: return true;
|
||||
case Visible::None: return false;
|
||||
case Visible::X: return x >= 0.0;
|
||||
case Visible::Y: return y >= 0.0;
|
||||
case Visible::XAndY: return x >= 0.0 && y >= 0.0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const Row& row) {
|
||||
Pixels.fill(std::byte{0});
|
||||
std::array<Vertex, 3> vertices{};
|
||||
for (std::size_t i = 0; i < vertices.size(); ++i) {
|
||||
vertices[i] = {{PositionX[i], PositionY[i], 0.5f, 1.0f}, {row.component0[i], row.component1[i], row.component2[i], 0.0f}};
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> vertexUserData(4, 0u);
|
||||
const auto vertexBuffer = BufferDescriptor(vertices.data(), sizeof(Vertex), static_cast<std::uint32_t>(vertices.size()));
|
||||
std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin());
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> vertexMemory{{{reinterpret_cast<std::uintptr_t>(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}};
|
||||
ShaderRecompiler::ShaderVertexStageInfo vertexInfo{};
|
||||
vertexInfo.paClVsOutCntl = row.control;
|
||||
ShaderRecompiler::RecompileRequest vertex{
|
||||
{ShaderStage::Vertex, reinterpret_cast<std::uintptr_t>(VertexCode.data()), VertexCode, 0, {}},
|
||||
{waveSize, 0, vertexUserData, std::nullopt, std::nullopt, vertexInfo, vertexMemory},
|
||||
device.Target(),
|
||||
{0, 0, 0, 64}
|
||||
};
|
||||
vertex.useCache = false;
|
||||
const auto vertexResult = ShaderRecompiler::Recompile(vertex);
|
||||
const auto vertexPush = static_cast<std::uint32_t>(vertexResult.pushConstants.size());
|
||||
|
||||
ShaderRecompiler::ShaderPixelStageInfo pixel{};
|
||||
pixel.wave32 = waveSize == 32u;
|
||||
pixel.targetOutputMode[0] = 9;
|
||||
pixel.targetExportMapping.fill(0xe4u);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> pixelMemory{{{reinterpret_cast<std::uintptr_t>(PixelCode.data()), std::as_bytes(std::span(PixelCode))}}};
|
||||
ShaderRecompiler::RecompileRequest fragment{
|
||||
{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(PixelCode.data()), PixelCode, 0, {}},
|
||||
{waveSize, 0, std::vector<std::uint32_t>(4, 0u), std::nullopt, pixel, std::nullopt, pixelMemory},
|
||||
device.Target(),
|
||||
PixelPushLayout(vertexPush, device.Target())
|
||||
};
|
||||
fragment.useCache = false;
|
||||
const auto pixelResult = ShaderRecompiler::Recompile(fragment);
|
||||
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{
|
||||
{ShaderStage::Vertex, &vertexResult, 0},
|
||||
{ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, device.Target())}
|
||||
}};
|
||||
|
||||
AgcDriver::Graphics::State state{};
|
||||
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt};
|
||||
state.paClVsOutCntl = row.control;
|
||||
state.color = {reinterpret_cast<std::uintptr_t>(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u};
|
||||
state.colors = {state.color};
|
||||
state.hasColorTarget = true;
|
||||
state.renderExtent = {Width, Height};
|
||||
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
state.viewport = {0, static_cast<float>(Height), static_cast<float>(Width), -static_cast<float>(Height), 0, 1};
|
||||
state.negativeOneToOne = false;
|
||||
state.scissor = {{0, 0}, {Width, Height}};
|
||||
state.cullMode = VK_CULL_MODE_NONE;
|
||||
state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
state.blend.colorWriteMask = 15;
|
||||
state.blends = {state.blend};
|
||||
state.blendConstants = {};
|
||||
const AgcDriver::Pm4::DrawParameters draw{0, static_cast<std::uint32_t>(vertices.size()), 0, 1, 0, false};
|
||||
device.Draw(state, draw, shaders);
|
||||
device.WaitIdle();
|
||||
}
|
||||
|
||||
void Check(const Row& row, std::uint32_t waveSize) {
|
||||
const auto what = std::string("vertex clip and cull distances, ") + row.name + ", wave" + std::to_string(waveSize);
|
||||
for (std::uint32_t pixel = 0; pixel < Width * Height; ++pixel) {
|
||||
const auto value = std::to_integer<std::uint8_t>(Pixels[pixel * 4u]);
|
||||
Require(value == 0u || value == 255u, what + ": pixel " + std::to_string(pixel) + " stored " + std::to_string(value));
|
||||
const bool written = value == 255u;
|
||||
const bool expected = Expected(row.visible, pixel);
|
||||
Require(written == expected, what + ": pixel (" + std::to_string(pixel % Width) + ", " + std::to_string(pixel / Width) + ") is " + (written ? "written" : "clear") + ", expected " + (expected ? "written" : "clear"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
if (device->Target().subgroupSize < 32u) {
|
||||
std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize);
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
for (const auto waveSize : {64u, 32u}) {
|
||||
for (const auto& row : Rows) {
|
||||
Draw(*device, waveSize, row);
|
||||
Check(row, waveSize);
|
||||
}
|
||||
}
|
||||
std::puts("vertex clip and cull distance tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp"
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
#include "prx/libc/include/GuestWriteWatch.hpp"
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
|
||||
constexpr std::uint32_t Width = 192;
|
||||
constexpr std::uint32_t Height = 128;
|
||||
constexpr std::uint32_t DistanceVector = 0x00400000u;
|
||||
constexpr std::uint32_t SetContextReg = 0x69u;
|
||||
constexpr std::uint32_t SetShReg = 0x76u;
|
||||
constexpr std::uint32_t SetUconfigReg = 0x79u;
|
||||
constexpr std::uint32_t DrawIndexAuto = 0x2du;
|
||||
|
||||
constexpr std::size_t Block = 65536;
|
||||
constexpr std::size_t ColorBytes = Width * Height * 4;
|
||||
std::byte* ColorMemory = nullptr;
|
||||
alignas(64) volatile std::uint32_t EndOfPipe = 0;
|
||||
|
||||
std::byte* AllocateWatched(std::size_t bytes) {
|
||||
#ifdef _WIN32
|
||||
void* block = GuestArena::GuestArenaAllocate_nid_postfix(bytes, Block);
|
||||
GuestArena::GuestArenaCommit_nid_postfix(block, bytes, PAGE_READWRITE, bytes);
|
||||
#else
|
||||
void* raw = mmap(nullptr, bytes + Block, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (raw == MAP_FAILED) throw std::runtime_error("cannot map the watched block");
|
||||
const auto begin = reinterpret_cast<std::uintptr_t>(raw);
|
||||
const auto aligned = (begin + Block - 1) & ~static_cast<std::uintptr_t>(Block - 1);
|
||||
if (aligned != begin) munmap(raw, aligned - begin);
|
||||
if (aligned + bytes != begin + bytes + Block) munmap(reinterpret_cast<void*>(aligned + bytes), begin + Block - aligned);
|
||||
void* block = reinterpret_cast<void*>(aligned);
|
||||
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block, bytes);
|
||||
#endif
|
||||
return static_cast<std::byte*>(block);
|
||||
}
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 10> VertexCode{
|
||||
0xe0382000, 0x80020005, 0xe0382010, 0x80020405, 0xbf8c3f70,
|
||||
0xf80000cf, 0x03020100, 0xf80008df, 0x07060504, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 7> PixelCode{
|
||||
0x7e0002f2, 0x7e0202f2, 0x7e0402f2, 0x7e0602f2, 0xf800080f, 0x03020100, 0xbf810000,
|
||||
};
|
||||
|
||||
struct Vertex {
|
||||
std::array<float, 4> position;
|
||||
std::array<float, 4> distances;
|
||||
};
|
||||
|
||||
constexpr std::array<float, 3> PositionX{-3.0f, 1.0f, 1.0f};
|
||||
constexpr std::array<float, 3> PositionY{-1.0f, -1.0f, 3.0f};
|
||||
constexpr std::array<float, 3> Negative{-1.0f, -1.0f, -1.0f};
|
||||
constexpr std::array<float, 3> Mixed{-1.0f, 1.0f, -1.0f};
|
||||
|
||||
enum class Visible { All, None, X, Y };
|
||||
|
||||
struct Step {
|
||||
const char* name;
|
||||
std::uint32_t control;
|
||||
Visible visible;
|
||||
std::array<float, 3> component0;
|
||||
std::array<float, 3> component1;
|
||||
std::array<float, 3> component2;
|
||||
};
|
||||
|
||||
const std::array<Step, 4> Steps{{
|
||||
{"no enable bits", DistanceVector, Visible::All, Negative, Negative, Negative},
|
||||
{"clip plane 0 from component 0", DistanceVector | 0x1u, Visible::X, PositionX, Negative, Negative},
|
||||
{"cull plane 0 from component 2, not every vertex negative", DistanceVector | 0x400u, Visible::All, Negative, Negative, Mixed},
|
||||
{"clip plane 1 alone, from component 1", DistanceVector | 0x2u, Visible::Y, Negative, PositionY, Negative},
|
||||
}};
|
||||
|
||||
struct RegisteredShader {
|
||||
Shader shader;
|
||||
ShaderUserData userData;
|
||||
};
|
||||
RegisteredShader VertexShader{};
|
||||
RegisteredShader PixelShader{};
|
||||
|
||||
const std::vector<std::pair<std::uint32_t, std::uint32_t>> ContextBaseline{
|
||||
{0x2d5, 0x2000},
|
||||
{0x1b6, 0}, {0x200, 0}, {0x203, 0x800},
|
||||
{0x2dc, 0xaa00}, {0x2f8, 0}, {0x292, 2}, {0x293, 0},
|
||||
{0x80, 0}, {0x8d, 0}, {0x83, 0xffff}, {0x8c, 0xa},
|
||||
{0x2f9, 0x2d}, {0x313, 0x6000}, {0x30e, 0xffffffff}, {0x30f, 0xffffffff},
|
||||
{0x206, 0x43f}, {0x204, 0x80000}, {0x205, 0x240},
|
||||
{0x8e, 0xf}, {0x8f, 0xf}, {0x202, 0xcc0010},
|
||||
{0x1c4, 0}, {0x1c5, 9}, {0x1c3, 4}, {0x31c, 0x28028},
|
||||
{0x31b, 0}, {0x31d, 0}, {0x3b8, 0x9000000}, {0x1e0, 0},
|
||||
{0xc, 0},
|
||||
{0x81, 0x80000000}, {0x90, 0x80000000}, {0x94, 0x80000000},
|
||||
{0xb4, 0}, {0xb5, std::bit_cast<std::uint32_t>(1.0f)}, {0x114, 0}, {0x113, std::bit_cast<std::uint32_t>(1.0f)},
|
||||
};
|
||||
|
||||
void Register(RegisteredShader& registered, const void* code, std::uint32_t bytes, std::uint8_t type) {
|
||||
registered.shader.file_header = 0x34333231u;
|
||||
registered.shader.version = 0x18u;
|
||||
registered.shader.user_data = ®istered.userData;
|
||||
registered.shader.code = code;
|
||||
registered.shader.header_size = sizeof(RegisteredShader);
|
||||
registered.shader.shader_size = bytes;
|
||||
registered.shader.type = type;
|
||||
AgcDriverRegisterShader_nid_postfix(®istered.shader);
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(data);
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu};
|
||||
}
|
||||
|
||||
void SetRegister(std::vector<std::uint32_t>& words, std::uint32_t opcode, std::uint32_t offset, std::uint32_t value) {
|
||||
words.push_back(0xc0010000u | (opcode << 8u));
|
||||
words.push_back(offset);
|
||||
words.push_back(value);
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> Frame(const Step& step, const std::array<Vertex, 3>& vertices) {
|
||||
const auto colorAddress = reinterpret_cast<std::uintptr_t>(ColorMemory);
|
||||
const auto vertexAddress = reinterpret_cast<std::uintptr_t>(VertexCode.data());
|
||||
const auto pixelAddress = reinterpret_cast<std::uintptr_t>(PixelCode.data());
|
||||
std::vector<std::uint32_t> words;
|
||||
for (const auto& [offset, value] : ContextBaseline) SetRegister(words, SetContextReg, offset, value);
|
||||
SetRegister(words, SetContextReg, 0x207, step.control);
|
||||
SetRegister(words, SetContextReg, 0x318, static_cast<std::uint32_t>(colorAddress >> 8u));
|
||||
SetRegister(words, SetContextReg, 0x390, static_cast<std::uint32_t>(colorAddress >> 40u));
|
||||
SetRegister(words, SetContextReg, 0x3b0, ((Width - 1u) << 14u) | (Height - 1u));
|
||||
SetRegister(words, SetContextReg, 0x10f, std::bit_cast<std::uint32_t>(Width / 2.0f));
|
||||
SetRegister(words, SetContextReg, 0x110, std::bit_cast<std::uint32_t>(Width / 2.0f));
|
||||
SetRegister(words, SetContextReg, 0x111, std::bit_cast<std::uint32_t>(-(Height / 2.0f)));
|
||||
SetRegister(words, SetContextReg, 0x112, std::bit_cast<std::uint32_t>(Height / 2.0f));
|
||||
const auto extent = (Height << 16u) | Width;
|
||||
SetRegister(words, SetContextReg, 0xd, extent);
|
||||
SetRegister(words, SetContextReg, 0x82, extent);
|
||||
SetRegister(words, SetContextReg, 0x91, extent);
|
||||
SetRegister(words, SetContextReg, 0x95, extent);
|
||||
SetRegister(words, SetUconfigReg, 0x242, 4u);
|
||||
SetRegister(words, SetShReg, 0x008, static_cast<std::uint32_t>(pixelAddress >> 8u));
|
||||
SetRegister(words, SetShReg, 0x009, static_cast<std::uint32_t>(pixelAddress >> 40u));
|
||||
SetRegister(words, SetShReg, 0x00b, 0u);
|
||||
SetRegister(words, SetShReg, 0x0c8, static_cast<std::uint32_t>(vertexAddress >> 8u));
|
||||
SetRegister(words, SetShReg, 0x0c9, static_cast<std::uint32_t>(vertexAddress >> 40u));
|
||||
SetRegister(words, SetShReg, 0x08b, 4u << 1u);
|
||||
const auto descriptor = BufferDescriptor(vertices.data(), sizeof(Vertex), static_cast<std::uint32_t>(vertices.size()));
|
||||
for (std::uint32_t i = 0; i < descriptor.size(); ++i) SetRegister(words, SetShReg, 0x08c + i, descriptor[i]);
|
||||
words.insert(words.end(), {0xc0000000u | (1u << 16u) | (DrawIndexAuto << 8u), static_cast<std::uint32_t>(vertices.size()), 2u});
|
||||
const auto labelAddress = reinterpret_cast<std::uintptr_t>(&EndOfPipe);
|
||||
words.insert(words.end(), {0xc0064900u, 0x514u, (1u << 29u) | (2u << 24u), static_cast<std::uint32_t>(labelAddress), static_cast<std::uint32_t>(static_cast<std::uint64_t>(labelAddress) >> 32u), 1u, 0u, 0u});
|
||||
return words;
|
||||
}
|
||||
|
||||
void Check(const Step& step, const std::vector<std::byte>& colors) {
|
||||
const auto what = std::string("vertex clip and cull distances through the driver, ") + step.name;
|
||||
for (std::uint32_t pixel = 0; pixel < Width * Height; ++pixel) {
|
||||
const auto value = std::to_integer<std::uint8_t>(colors[pixel * 4u]);
|
||||
Require(value == 0u || value == 255u, what + ": pixel " + std::to_string(pixel) + " stored " + std::to_string(value));
|
||||
const double x = -1.0 + 2.0 * ((pixel % Width) + 0.5) / Width;
|
||||
const double y = 1.0 - 2.0 * ((pixel / Width) + 0.5) / Height;
|
||||
const bool expected = step.visible == Visible::All || (step.visible == Visible::X && x >= 0.0) || (step.visible == Visible::Y && y >= 0.0);
|
||||
const bool written = value == 255u;
|
||||
Require(written == expected, what + ": pixel (" + std::to_string(pixel % Width) + ", " + std::to_string(pixel / Width) + ") is " + (written ? "written" : "clear") + ", expected " + (expected ? "written" : "clear"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// A program that exports only pos0 is compiled without the PA_CL_VS_OUT_CNTL position export bits, at
|
||||
// registration and at draw alike; one that exports pos1 keeps them, without the bits outside the layout.
|
||||
void PositionExportLayoutTests() {
|
||||
using AgcDriver::DriverDetail::PositionExportLayout;
|
||||
constexpr std::array<std::uint32_t, 3> PositionOnly{0xf80008cfu, 0x03020100u, 0xbf810000u};
|
||||
Require(PositionExportLayout(nullptr, 0, VertexCode, 0x0040000fu) == 0x0040000fu, "a program exporting pos1 lost its position export bits");
|
||||
Require(PositionExportLayout(nullptr, 0, VertexCode, 0x0144000fu) == 0x0040000fu, "bits outside the position export layout reached the vertex info");
|
||||
Require(PositionExportLayout(nullptr, 0, PositionOnly, 0x0040000fu) == 0u, "a program exporting only pos0 kept the position export bits");
|
||||
AgcDriver::DriverDetail::ShaderSnapshot snapshot{0x10000u, 0u, 1u, {PositionOnly.begin(), PositionOnly.end()}, {}};
|
||||
for (int use = 0; use < 2; ++use) Require(PositionExportLayout(&snapshot, 0, snapshot.code, 0x0040000fu) == 0u, "a cached pos0-only program kept the position export bits");
|
||||
snapshot.code.assign(VertexCode.begin(), VertexCode.end());
|
||||
Require(PositionExportLayout(&snapshot, 2, std::span(snapshot.code).subspan(2), 0x0040000fu) == 0x0040000fu, "a program at an offset was answered from the cache of its snapshot");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
const bool noKey = argc > 1 && std::string(argv[1]) == "no-key";
|
||||
if (noKey) Require(std::getenv("APS5_NO_DRAW_KEY") != nullptr, "the no-key run must start with APS5_NO_DRAW_KEY set");
|
||||
PositionExportLayoutTests();
|
||||
ColorMemory = AllocateWatched(2 * Block);
|
||||
Register(VertexShader, VertexCode.data(), sizeof(VertexCode), 2);
|
||||
Register(PixelShader, PixelCode.data(), sizeof(PixelCode), 1);
|
||||
for (const auto& step : Steps) {
|
||||
std::array<Vertex, 3> vertices{};
|
||||
for (std::size_t i = 0; i < vertices.size(); ++i) {
|
||||
vertices[i] = {{PositionX[i], PositionY[i], 0.5f, 1.0f}, {step.component0[i], step.component1[i], step.component2[i], 0.0f}};
|
||||
}
|
||||
std::fill_n(ColorMemory, ColorBytes, std::byte{0});
|
||||
EndOfPipe = 0;
|
||||
auto words = Frame(step, vertices);
|
||||
Packet packet{words.data(), static_cast<std::uint32_t>(words.size()), 0, {}};
|
||||
Require(sceAgcDriverSubmitDcb(&packet) == 0, std::string("DCB submission failed for ") + step.name);
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
Require(EndOfPipe == 1u, std::string("the end-of-pipe label was not written for ") + step.name);
|
||||
std::vector<std::byte> colors(ColorBytes);
|
||||
AgcDriver::GuestMemory::Read(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(ColorMemory)), colors);
|
||||
Check(step, colors);
|
||||
}
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
std::puts(noKey ? "vertex clip and cull distance draws passed without the draw key" : "vertex clip and cull distance draws passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -192,6 +192,7 @@ private:
|
|||
append(key, value.fetchAttribReg);
|
||||
append(key, value.fetchBufferReg);
|
||||
append(key, value.fetchEmbedded);
|
||||
append(key, value.paClVsOutCntl);
|
||||
if (value.resourcesNum > value.resources.size()) throw std::runtime_error("Shader cache: invalid vertex resource count");
|
||||
append(key, value.resourcesNum);
|
||||
for (std::uint32_t i = 0; i < value.resourcesNum; ++i) {
|
||||
|
|
|
|||
|
|
@ -412,9 +412,10 @@ void writeVertexInfo(Writer& writer, const ShaderVertexStageInfo& info) {
|
|||
writer.WriteU32(info.fetchAttribReg);
|
||||
writer.WriteU32(info.fetchBufferReg);
|
||||
writer.WriteBool(info.fetchEmbedded);
|
||||
writer.WriteU32(info.paClVsOutCntl);
|
||||
}
|
||||
|
||||
ShaderVertexStageInfo readVertexInfo(Reader& reader) {
|
||||
ShaderVertexStageInfo readVertexInfo(Reader& reader, std::uint32_t version) {
|
||||
ShaderVertexStageInfo info{};
|
||||
for (auto& resource : info.resources) {
|
||||
for (std::uint32_t& value : resource.fields) {
|
||||
|
|
@ -431,6 +432,7 @@ ShaderVertexStageInfo readVertexInfo(Reader& reader) {
|
|||
info.fetchAttribReg = reader.ReadU32();
|
||||
info.fetchBufferReg = reader.ReadU32();
|
||||
info.fetchEmbedded = reader.ReadBool();
|
||||
if (version >= 20u) info.paClVsOutCntl = reader.ReadU32();
|
||||
return info;
|
||||
}
|
||||
|
||||
|
|
@ -575,7 +577,7 @@ GuestContext readGuestContext(Reader& reader, DeserializedRequest& result, std::
|
|||
context.pixel = result.pixel;
|
||||
}
|
||||
if (reader.ReadBool()) {
|
||||
result.vertex = readVertexInfo(reader);
|
||||
result.vertex = readVertexInfo(reader, version);
|
||||
context.vertex = result.vertex;
|
||||
}
|
||||
const auto regionCount = reader.ReadU64();
|
||||
|
|
@ -746,7 +748,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
|
|||
std::string buffer;
|
||||
Writer writer(buffer);
|
||||
writer.WriteU32(0x41505335u);
|
||||
writer.WriteU32(19u);
|
||||
writer.WriteU32(20u);
|
||||
writeShaderBinary(writer, request.shader);
|
||||
writeGuestContext(writer, request.context);
|
||||
writeSpirvTarget(writer, request.target);
|
||||
|
|
@ -777,7 +779,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const
|
|||
Reader reader(decoded);
|
||||
if (reader.ReadU32() != 0x41505335u) throw std::runtime_error("invalid recompile request signature");
|
||||
const auto version = reader.ReadU32();
|
||||
if (version < 1u || version > 19u) throw std::runtime_error("unsupported recompile request serialization version");
|
||||
if (version < 1u || version > 20u) throw std::runtime_error("unsupported recompile request serialization version");
|
||||
DeserializedRequest result{};
|
||||
result.request.shader = readShaderBinary(reader, result.shaderCode, result.shaderHeader);
|
||||
result.request.context = readGuestContext(reader, result, version);
|
||||
|
|
|
|||
|
|
@ -171,6 +171,7 @@ struct ShaderVertexStageInfo {
|
|||
std::uint32_t fetchAttribReg;
|
||||
std::uint32_t fetchBufferReg;
|
||||
bool fetchEmbedded;
|
||||
std::uint32_t paClVsOutCntl = 0;
|
||||
};
|
||||
|
||||
struct ShaderFloatMode {
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues
|
|||
vertexStorage.logicalStage = _toIrShaderStage(stage);
|
||||
vertexStorage.hostSubgroupSize = hostSubgroupSize;
|
||||
vertexStorage.fetchEmbedded = vertex.fetchEmbedded;
|
||||
vertexStorage.paClVsOutCntl = vertex.paClVsOutCntl;
|
||||
vertexStorage.fetchExternal = false;
|
||||
vertexStorage.fetchAttribReg = static_cast<int>(vertex.fetchAttribReg);
|
||||
vertexStorage.fetchBufferReg = static_cast<int>(vertex.fetchBufferReg);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
- [s_subvector_loop_begin and s_subvector_loop_end](../../../core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp) (core/shader/recompiler) - follow AMD's RDNA 2 instruction set reference and a gfx1036 (wave64) measurement, pinned in [SubvectorLoop.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/SubvectorLoop.cpp); `S0` in the end instruction's `elif(S0 == 0)` is read as its SGPR operand. A wave32 shader and a destination other than an SGPR throw because neither is measured. Measured on PC RDNA2 (Radeon 610M), not on a PS5. The [specialization pass](../../../core/shader/recompiler/SpirvBackend/src/SpirvSpecialization.cpp) keeps a constant-condition branch whose untaken target is a loop header, since folding it removes the loop's back edge, which SPIR-V requires exactly once per loop header (SPIRV-Tools rejects the module and the NVIDIA driver 615.78.08 crashes compiling it); a loop whose back-edge block becomes unreachable in another way is not handled
|
||||
- [Vertex entry merged wave info](../../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) (recompiler) - `s3` of every vertex-path shader is set to `waveSize | waveSize << 8` (vertex and primitive count of a full wave, wave index 0), or to `32 | 32 << 8` for a wave64 program on a 32-lane host subgroup, which runs it at one lane per invocation and holds no lanes 32-63. On a device whose `subgroupSupportedStages` lacks the vertex stage (lavapipe, MoltenVK), vertex and local programs run as one-lane waves: `s3` is `1 | 1 << 8`, the lane id is 0, a ballot is the invocation's own bit, a shuffle or broadcast returns the invocation's value, an elect is true and a subgroup reduction is the value itself, so no `GroupNonUniform` capability is declared. Layout taken from the prologue Mesa's ACO emits for NGG vertex shaders (`s_bfe_u64 exec` from `s3`), not measured on hardware. The driver gives every graphics program user data from `s8`, so the condition does not tell NGG from legacy vertex shaders: they just never read `s3`. A draw whose last wave is partial still reports a full wave, and so does a host wave with idle invocations (an RTX 5070 ran every vertex warp of a non-indexed triangle list with 30 of its 32 invocations).
|
||||
- [s_barrier in vertex-path programs](../../../core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp) (recompiler) - emits no instruction when the program has no LDS or GDS access and no memory write or atomic, so no wave of the subgroup can observe another; the `GS_ALLOC_REQ` such a barrier orders on NGG passthrough programs is already dropped on the vertex path. The Vertex execution model has no workgroup barrier. Programs with LDS, GDS or memory writes throw. Not measured on hardware
|
||||
- [Vertex clip and cull distances](../../../core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp) (libSceAgcDriver) - `PA_CL_VS_OUT_CNTL` enable bit `s` of `CLIP_DIST_ENA`/`CULL_DIST_ENA` is taken to select component `s` of the `pos1`/`pos2` exports, with the Vulkan array index counted by the enabled bits below it, and `VS_OUT_CCDIST0_VEC_ENA` as bit 22; this follows the recompiler's existing position-export mapping, not hardware or documentation. The guest clip distances share one `gl_ClipDistance` array with the all-zero clip position distance, which takes the index after them, so a shader with eight guest clip and cull distances exceeds the eight the device holds and fails to compile. A vertex program whose own code has no `pos1`..`pos3` export is compiled and prepared without these bits (`PositionExportLayout`), so registration and draws agree on its artifact; an export in a subroutine it calls is not seen by that scan
|
||||
|
||||
### Functional
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue