fix(shader): clip primitives with an all-zero clip position
This commit is contained in:
parent
e013f8c9c5
commit
9db923e907
|
|
@ -47,6 +47,7 @@ ShaderDeviceProfile::ShaderDeviceProfile(const ShaderRecompiler::SpirvTarget& ta
|
|||
checkFeature(spv::CapabilityFloat64, core.shaderFloat64 == VK_TRUE, "shaderFloat64");
|
||||
checkFeature(spv::CapabilityTessellation, core.tessellationShader == VK_TRUE, "tessellationShader");
|
||||
checkFeature(spv::CapabilityImageGatherExtended, core.shaderImageGatherExtended == VK_TRUE, "shaderImageGatherExtended");
|
||||
checkFeature(spv::CapabilityClipDistance, core.shaderClipDistance == VK_TRUE, "shaderClipDistance");
|
||||
checkFeature(spv::CapabilityMinLod, core.shaderResourceMinLod == VK_TRUE, "shaderResourceMinLod");
|
||||
checkFeature(spv::CapabilityStorageImageMultisample, core.shaderStorageImageMultisample == VK_TRUE, "shaderStorageImageMultisample");
|
||||
checkFeature(spv::CapabilityStorageImageReadWithoutFormat, core.shaderStorageImageReadWithoutFormat == VK_TRUE, "shaderStorageImageReadWithoutFormat");
|
||||
|
|
|
|||
|
|
@ -262,6 +262,7 @@ struct VulkanDevice::State {
|
|||
bool fragmentShaderBarycentric = false;
|
||||
bool geometryShader = false;
|
||||
bool sampleRateShading = false;
|
||||
bool shaderClipDistance = false;
|
||||
bool shaderClock = false;
|
||||
bool narrowSubgroupClock = false;
|
||||
bool float64DenormPreserve = false;
|
||||
|
|
@ -1348,6 +1349,8 @@ 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;
|
||||
state->shaderClipDistance = enabled.shaderClipDistance == VK_TRUE;
|
||||
if (enabled.shaderClipDistance) state->capabilities.push_back(spv::CapabilityClipDistance);
|
||||
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;
|
||||
|
|
@ -3012,6 +3015,7 @@ Graphics::Context VulkanDevice::buildContext() const {
|
|||
context.imageInt64Atomics = state->imageInt64Atomics;
|
||||
context.geometryShader = state->geometryShader;
|
||||
context.sampleRateShading = state->sampleRateShading;
|
||||
context.shaderClipDistance = state->shaderClipDistance;
|
||||
context.nullDescriptors = state->shaderProfile != nullptr && state->shaderProfile->NullDescriptors();
|
||||
context.bufferInt64Atomics = state->bufferInt64Atomics;
|
||||
context.placeholderImages = state->placeholderImages.get();
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ struct Context {
|
|||
bool imageInt64Atomics = false;
|
||||
bool geometryShader = false;
|
||||
bool sampleRateShading = false;
|
||||
bool shaderClipDistance = false;
|
||||
bool nullDescriptors = false;
|
||||
bool bufferInt64Atomics = false;
|
||||
const PlaceholderImages* placeholderImages = nullptr;
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ void ValidateProvokingVertex(const Context& context, const State& state, std::sp
|
|||
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);
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -732,7 +732,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);
|
||||
outputs = ValidateShaders(shaders, state, context.subgroup, context.fragmentShaderBarycentric, context.descriptorIndexing, context.imageInt64Atomics, context.geometryShader, context.sampleRateShading, context.bufferInt64Atomics, context.shaderClipDistance);
|
||||
} catch (const std::exception& error) {
|
||||
if (keyed) {
|
||||
std::lock_guard lock(validationMutex());
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ struct Module {
|
|||
std::map<std::uint32_t, std::string> outputs;
|
||||
std::optional<std::string> secondSource;
|
||||
bool position = false;
|
||||
bool clipDistance = false;
|
||||
bool primitiveIndices = false;
|
||||
bool fragmentBarycentric = false;
|
||||
std::map<std::uint32_t, std::vector<std::uint32_t>> modes;
|
||||
|
|
@ -109,6 +110,11 @@ 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;
|
||||
return;
|
||||
}
|
||||
if (stage == Stage::TessellationControl || stage == Stage::TessellationEvaluation || stage == Stage::Mesh) {
|
||||
const auto signature = Signature(type);
|
||||
if (!input && value == spv::BuiltInPosition) {
|
||||
|
|
@ -136,7 +142,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) {
|
||||
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) {
|
||||
using Stage = ShaderRecompiler::ShaderStage;
|
||||
Require(compiled.program != nullptr, "missing compiled shader");
|
||||
const auto& shader = *compiled.program;
|
||||
|
|
@ -232,6 +238,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
capability == spv::CapabilityImage1D ||
|
||||
capability == spv::CapabilityImageGatherExtended ||
|
||||
capability == spv::CapabilityImageQuery ||
|
||||
((vertex || evaluation) && shaderClipDistance && capability == spv::CapabilityClipDistance) ||
|
||||
(fragment && geometryShader && capability == spv::CapabilityGeometry) ||
|
||||
(fragment && sampleRateShading && capability == spv::CapabilitySampleRateShading) ||
|
||||
capability == spv::CapabilityImageMSArray ||
|
||||
|
|
@ -547,7 +554,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) {
|
||||
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) {
|
||||
using Stage = ShaderRecompiler::ShaderStage;
|
||||
const bool tessellation = state.stages.path == ShaderPath::Tessellation;
|
||||
const bool mesh = state.stages.path == ShaderPath::Geometry;
|
||||
|
|
@ -568,7 +575,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 == 0, "graphics resource uses a descriptor set other than zero");
|
||||
const auto current = Inspect(shaders[i], state, subgroup, fragmentShaderBarycentric, descriptorIndexing, imageInt64Atomics, geometryShader, sampleRateShading, bufferInt64Atomics);
|
||||
const auto current = Inspect(shaders[i], state, subgroup, fragmentShaderBarycentric, descriptorIndexing, imageInt64Atomics, geometryShader, sampleRateShading, bufferInt64Atomics, shaderClipDistance);
|
||||
if (i != 0) {
|
||||
for (const auto& [location, signature] : current.inputs) {
|
||||
const auto output = previous.outputs.find(location);
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ void requireInvariantPositions(const ShaderRecompiler::RecompileResult& result,
|
|||
|
||||
constexpr std::array<std::uint32_t, 3> PositionExport{0xf80008cfu, 0u, 0xbf810000u};
|
||||
|
||||
constexpr std::array<std::uint32_t, 1> VertexCapabilities{spv::CapabilityClipDistance};
|
||||
|
||||
ShaderRecompiler::RecompileRequest baseRequest(ShaderRecompiler::ShaderStage stage) {
|
||||
using namespace ShaderRecompiler;
|
||||
RecompileRequest request{};
|
||||
|
|
@ -45,6 +47,7 @@ ShaderRecompiler::RecompileRequest baseRequest(ShaderRecompiler::ShaderStage sta
|
|||
request.target.vulkanVersion = 0x00401000u;
|
||||
request.target.spirvVersion = 0x00010300u;
|
||||
request.target.subgroupSize = 64;
|
||||
request.target.supportedCapabilities = VertexCapabilities;
|
||||
request.layout.pushConstantSizeBytes = 128;
|
||||
request.useCache = false;
|
||||
return request;
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@
|
|||
namespace {
|
||||
|
||||
ShaderRecompiler::SpirvTarget BufferTarget() {
|
||||
static constexpr std::array<std::uint32_t, 5> capabilities{spv::CapabilityShader, spv::CapabilityImageGatherExtended, spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess};
|
||||
static constexpr std::array<std::uint32_t, 6> capabilities{spv::CapabilityShader, spv::CapabilityImageGatherExtended, spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, spv::CapabilityClipDistance};
|
||||
static constexpr std::array<std::string_view, 2> extensions{"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"};
|
||||
ShaderRecompiler::SpirvTarget target{};
|
||||
target.bdaAbiVersion = ShaderRecompiler::BdaAbi::Version;
|
||||
|
|
@ -1202,7 +1202,7 @@ void verifyLanesOutsideHostSubgroup() {
|
|||
|
||||
void verifyHalfWaveReduction() {
|
||||
using namespace ShaderRecompiler;
|
||||
static constexpr std::array<std::uint32_t, 4> capabilities{spv::CapabilityGroupNonUniform, spv::CapabilityGroupNonUniformBallot, spv::CapabilityGroupNonUniformShuffle, spv::CapabilityGroupNonUniformArithmetic};
|
||||
static constexpr std::array<std::uint32_t, 5> capabilities{spv::CapabilityGroupNonUniform, spv::CapabilityGroupNonUniformBallot, spv::CapabilityGroupNonUniformShuffle, spv::CapabilityGroupNonUniformArithmetic, spv::CapabilityClipDistance};
|
||||
const auto pixel = [](std::span<const std::uint32_t> code, std::uint32_t subgroupSize, bool arithmetic) {
|
||||
ShaderPixelStageInfo info{};
|
||||
info.inputAddr = PixelInputBit(PixelInput::PositionX);
|
||||
|
|
@ -1364,7 +1364,7 @@ void verifyHalfWaveReduction() {
|
|||
|
||||
void verifyNarrowVertexWave() {
|
||||
using namespace ShaderRecompiler;
|
||||
static constexpr std::array<std::uint32_t, 4> capabilities{spv::CapabilityGroupNonUniform, spv::CapabilityGroupNonUniformBallot, spv::CapabilityGroupNonUniformShuffle, spv::CapabilityGroupNonUniformArithmetic};
|
||||
static constexpr std::array<std::uint32_t, 5> capabilities{spv::CapabilityGroupNonUniform, spv::CapabilityGroupNonUniformBallot, spv::CapabilityGroupNonUniformShuffle, spv::CapabilityGroupNonUniformArithmetic, spv::CapabilityClipDistance};
|
||||
static constexpr std::array<std::uint32_t, 4> userData{0u, 0u, 0u, 0x2020u};
|
||||
const auto vertex = [](std::span<const std::uint32_t> code, std::uint32_t waveSize, std::uint32_t subgroupSize, std::uint32_t userDataBase) {
|
||||
RecompileRequest request{};
|
||||
|
|
@ -1411,7 +1411,7 @@ void verifyNarrowVertexWave() {
|
|||
const auto wide64 = prepared(culling, 64u);
|
||||
require(!wide64.empty() && reductions(wide64) == 0u, "narrow vertex wave: the NGG culling scan, keyed on lanes below 64 on 64 lanes, did not keep its DPP scan");
|
||||
require(prepares(patched({{6, 0x80060015u}}), 32u), "narrow vertex wave: the scan keyed on lanes below the vertex count in s3 did not prepare on 32 lanes");
|
||||
static constexpr std::array<std::uint32_t, 3> noArithmetic{spv::CapabilityGroupNonUniform, spv::CapabilityGroupNonUniformBallot, spv::CapabilityGroupNonUniformShuffle};
|
||||
static constexpr std::array<std::uint32_t, 4> noArithmetic{spv::CapabilityGroupNonUniform, spv::CapabilityGroupNonUniformBallot, spv::CapabilityGroupNonUniformShuffle, spv::CapabilityClipDistance};
|
||||
const auto otherKeys = patched({{7, 0x360e0a82u}});
|
||||
auto withoutArithmetic = vertex(otherKeys, 64u, 32u, 8u);
|
||||
withoutArithmetic.target.supportedCapabilities = noArithmetic;
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ void CheckStorageOnlyModule(const RecompileResult& result, std::string_view name
|
|||
}
|
||||
|
||||
SpirvTarget StorageOnlyTarget() {
|
||||
static constexpr std::array<std::uint32_t, 3> capabilities{
|
||||
spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess,
|
||||
static constexpr std::array<std::uint32_t, 4> capabilities{
|
||||
spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, spv::CapabilityClipDistance,
|
||||
};
|
||||
static constexpr std::array<std::string_view, 4> extensions{
|
||||
"SPV_EXT_descriptor_indexing", "SPV_KHR_8bit_storage", "SPV_KHR_float_controls", "SPV_KHR_physical_storage_buffer",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ namespace {
|
|||
constexpr std::array<std::uint32_t, 4> BarrierExport{0xbf8a0000u, 0xf80008cfu, 0u, 0xbf810000u};
|
||||
constexpr std::array<std::uint32_t, 6> BarrierLdsExport{0xd8340000u, 0u, 0xbf8a0000u, 0xf80008cfu, 0u, 0xbf810000u};
|
||||
|
||||
constexpr std::array<std::uint32_t, 1> VertexCapabilities{spv::CapabilityClipDistance};
|
||||
|
||||
ShaderRecompiler::RecompileRequest vertexRequest(std::span<const std::uint32_t> code) {
|
||||
using namespace ShaderRecompiler;
|
||||
RecompileRequest request{};
|
||||
|
|
@ -21,6 +23,7 @@ ShaderRecompiler::RecompileRequest vertexRequest(std::span<const std::uint32_t>
|
|||
request.target.vulkanVersion = 0x00401000u;
|
||||
request.target.spirvVersion = 0x00010300u;
|
||||
request.target.subgroupSize = 32;
|
||||
request.target.supportedCapabilities = VertexCapabilities;
|
||||
request.layout.pushConstantSizeBytes = 128;
|
||||
request.useCache = false;
|
||||
return request;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Extent = 64u;
|
||||
alignas(256) std::array<std::byte, Extent * Extent * 4u> pixels{};
|
||||
alignas(256) std::array<std::byte, 512> vertices{};
|
||||
constexpr std::array<std::uint32_t, 11> vertexCode{0xf4080100u, 0xfa000000u, 0x4a0a0a02u, 0x4a0a0b08u, 0x4a0a0a03u, 0xe00c2000u, 0x80010005u, 0xbf8c3f70u, 0xf80008cfu, 0x03020100u, 0xbf810000u};
|
||||
constexpr std::array<std::uint32_t, 5> pixelCode{0x7e0002f2u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u};
|
||||
|
||||
using Position = std::array<float, 4>;
|
||||
|
||||
struct Counts {
|
||||
unsigned total = 0;
|
||||
unsigned upper = 0;
|
||||
std::array<std::array<unsigned, 16>, 16> map{};
|
||||
};
|
||||
|
||||
Counts RunCase(AgcDriver::VulkanDevice& device, const char* name, std::uint32_t records, const std::vector<Position>& positions, std::size_t stored, VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP) {
|
||||
vertices.fill(std::byte{0});
|
||||
constexpr std::uint32_t stride = 16u;
|
||||
for (std::size_t index = 0; index < stored; ++index) std::memcpy(vertices.data() + index * stride, positions[index].data(), 16u);
|
||||
std::array<std::uint32_t, 4> descriptor{};
|
||||
const auto tableAddress = reinterpret_cast<std::uintptr_t>(descriptor.data());
|
||||
std::array<std::uint32_t, 4> userData{static_cast<std::uint32_t>(tableAddress), static_cast<std::uint32_t>(tableAddress >> 32u), 0u, 0u};
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{tableAddress, std::as_bytes(std::span(descriptor))}}};
|
||||
ShaderRecompiler::ShaderVertexStageInfo vertexInfo{};
|
||||
vertexInfo.fetchEmbedded = true;
|
||||
vertexInfo.resourcesNum = 1u;
|
||||
vertexInfo.resourcesDst[0] = {0, 4, 0, 0};
|
||||
ShaderRecompiler::RecompileRequest request{{ShaderStage::Vertex, reinterpret_cast<std::uintptr_t>(vertexCode.data()), vertexCode, 0, {}}, {32u, 0u, userData, std::nullopt, std::nullopt, vertexInfo, memory}, device.Target(), {0u, 0u, 0u, 96u}};
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(vertices.data());
|
||||
descriptor = {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>(address >> 32u) | (stride << 16u), records, 0x01000facu | (77u << 12u)};
|
||||
request.context.vertex->resources[0].fields = descriptor;
|
||||
const auto vertex = ShaderRecompiler::Recompile(request);
|
||||
const auto pushBytes = static_cast<std::uint32_t>(vertex.pushConstants.size());
|
||||
ShaderRecompiler::ShaderPixelStageInfo pixelInfo{};
|
||||
pixelInfo.wave32 = true;
|
||||
pixelInfo.targetOutputMode[0] = 9u;
|
||||
pixelInfo.targetExportMapping.fill(0xe4u);
|
||||
ShaderRecompiler::RecompileRequest pixelRequest{{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(pixelCode.data()), pixelCode, 0, {}}, {32u, 0u, {}, std::nullopt, pixelInfo, std::nullopt, {}}, device.Target(), {0u, 0u, pushBytes, 128u - pushBytes}};
|
||||
const auto pixel = ShaderRecompiler::Recompile(pixelRequest);
|
||||
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{{ShaderStage::Vertex, &vertex, 0u}, {ShaderStage::Fragment, &pixel, pushBytes}}};
|
||||
AgcDriver::Graphics::State state{};
|
||||
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt};
|
||||
state.color = {reinterpret_cast<std::uintptr_t>(pixels.data()), {Extent, Extent}, VK_FORMAT_R8G8B8A8_UNORM, pixels.size(), 0xe4u};
|
||||
state.colors = {state.color};
|
||||
state.hasColorTarget = true;
|
||||
state.renderExtent = {Extent, Extent};
|
||||
state.topology = topology;
|
||||
state.viewport = {0.0f, static_cast<float>(Extent), static_cast<float>(Extent), -static_cast<float>(Extent), 0.0f, 1.0f};
|
||||
state.scissor = {{0, 0}, {Extent, Extent}};
|
||||
state.cullMode = VK_CULL_MODE_NONE;
|
||||
state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
state.blend.colorWriteMask = 15u;
|
||||
state.blends = {state.blend};
|
||||
AgcDriver::Pm4::DrawParameters draw{0u, static_cast<std::uint32_t>(positions.size()), 0u, 1u, 0u, false};
|
||||
draw.firstInstance = 0u;
|
||||
pixels.fill(std::byte{0});
|
||||
device.Draw(state, draw, shaders);
|
||||
device.WaitIdle();
|
||||
Counts counts;
|
||||
for (std::uint32_t y = 0; y < Extent; ++y) {
|
||||
for (std::uint32_t x = 0; x < Extent; ++x) {
|
||||
const auto index = (y * Extent + x) * 4u;
|
||||
if (pixels[index] == std::byte{0} && pixels[index + 3u] == std::byte{0}) continue;
|
||||
++counts.total;
|
||||
const float ndcX = (static_cast<float>(x) + 0.5f) / Extent * 2.0f - 1.0f;
|
||||
const float ndcY = 1.0f - (static_cast<float>(y) + 0.5f) / Extent * 2.0f;
|
||||
if (ndcX + ndcY > -1.0f) ++counts.upper;
|
||||
++counts.map[y / 4u][x / 4u];
|
||||
}
|
||||
}
|
||||
std::cout << "case " << name << ": records=" << records << " total=" << counts.total << " outsideFirstTriangle(x+y>-1)=" << counts.upper << "\n";
|
||||
for (const auto& row : counts.map) {
|
||||
std::string line;
|
||||
for (const auto count : row) line += count == 0u ? '.' : count == 16u ? '#' : '+';
|
||||
std::cout << " " << line << "\n";
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
void Run(AgcDriver::VulkanDevice& device) {
|
||||
std::cout << "device: " << device.DeviceName() << "\n";
|
||||
const Position v0{-1.0f, -1.0f, 0.0f, 1.0f};
|
||||
const Position v1{0.0f, -1.0f, 0.0f, 1.0f};
|
||||
const Position v2{-1.0f, 0.0f, 0.0f, 1.0f};
|
||||
const auto make = [&](const Position& last) { return std::vector<Position>{v0, v1, v2, last}; };
|
||||
const auto a = RunCase(device, "A out-of-range vertex 3", 3u, make({0.0f, 0.0f, 0.0f, 0.0f}), 3u);
|
||||
const auto b = RunCase(device, "B in-range (0,0,0,0)", 4u, make({0.0f, 0.0f, 0.0f, 0.0f}), 4u);
|
||||
const auto c = RunCase(device, "C repeat of v1", 4u, make(v1), 4u);
|
||||
const auto d = RunCase(device, "D (0,0,0,1) at the centre", 4u, make({0.0f, 0.0f, 0.0f, 1.0f}), 4u);
|
||||
const auto e = RunCase(device, "E (1,1,0,1) real triangle", 4u, make({1.0f, 1.0f, 0.0f, 1.0f}), 4u);
|
||||
const Position zero{0.0f, 0.0f, 0.0f, 0.0f};
|
||||
const auto f = RunCase(device, "F negative zeros", 4u, make({-0.0f, 0.0f, -0.0f, -0.0f}), 4u);
|
||||
const auto g = RunCase(device, "G triangle list, second triangle has (0,0,0,0)", 6u, {v0, v1, v2, v1, v2, zero}, 6u, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||
const auto h = RunCase(device, "H strip with two (0,0,0,0) vertices", 5u, {v0, v1, v2, zero, zero}, 5u);
|
||||
const auto p = RunCase(device, "P point list, (-0.5,-0.5,0,1) and (0,0,0,0)", 2u, {{-0.5f, -0.5f, 0.0f, 1.0f}, zero}, 2u, VK_PRIMITIVE_TOPOLOGY_POINT_LIST);
|
||||
Require(e.upper > 0u, "probe cannot see the second triangle");
|
||||
Require(c.total > 0u && c.upper == 0u, "the first triangle is not drawn alone");
|
||||
Require(a.upper == 0u && a.total == c.total, "a vertex fetched past num_records must clip the primitive away");
|
||||
Require(b.upper == 0u && b.total == c.total, "a stored (0,0,0,0) vertex must clip the primitive away");
|
||||
Require(f.upper == 0u && f.total == c.total, "a stored (-0,0,-0,-0) vertex must clip the primitive away");
|
||||
Require(g.upper == 0u && g.total == c.total, "a triangle list with a (0,0,0,0) vertex must keep only the first triangle");
|
||||
Require(h.upper == 0u && h.total == c.total, "a strip with two (0,0,0,0) vertices must keep only the first triangle");
|
||||
Require(d.upper == 496u, "a (0,0,0,1) vertex must not be clipped away");
|
||||
Require(p.total == 1u, "a point at (0,0,0,0) must not be drawn");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
Run(*device);
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
|
@ -164,6 +165,7 @@ struct SpirvEmitterState {
|
|||
std::uint32_t perVertexVariable = 0;
|
||||
std::uint32_t pointSizeVariable = 0;
|
||||
std::uint32_t clipDistanceVariable = 0;
|
||||
std::optional<std::uint32_t> zeroPositionClipIndex;
|
||||
std::uint32_t cullDistanceVariable = 0;
|
||||
std::uint32_t layerVariable = 0;
|
||||
std::uint32_t viewportIndexVariable = 0;
|
||||
|
|
|
|||
|
|
@ -291,6 +291,8 @@ void DefineInputs(SpirvEmitterState& state) {
|
|||
}
|
||||
}
|
||||
|
||||
constexpr std::uint32_t MaxClipDistances = 8u;
|
||||
|
||||
void DefineOutputs(SpirvEmitterState& state) {
|
||||
state.outputs.reserve(state.program.Info().outputs.size());
|
||||
std::uint32_t clipDistanceCount = 0u;
|
||||
|
|
@ -307,6 +309,18 @@ void DefineOutputs(SpirvEmitterState& state) {
|
|||
DefineMeshOutputs(state);
|
||||
return;
|
||||
}
|
||||
const auto stage = state.program.Resources().stage;
|
||||
const bool exportsPosition = std::ranges::any_of(state.outputs, [](const SpirvOutputBinding& output) { return output.kind == StageOutputKind::Position; });
|
||||
if (exportsPosition && (stage == IrShaderStage::Vertex || stage == IrShaderStage::TessellationEvaluation)) {
|
||||
if (std::ranges::find(state.supportedCapabilities, spv::CapabilityClipDistance) == state.supportedCapabilities.end()) {
|
||||
FailEmit("clipping a primitive with an all-zero clip position needs shaderClipDistance on the target device");
|
||||
}
|
||||
state.zeroPositionClipIndex = clipDistanceCount;
|
||||
clipDistanceCount += 1u;
|
||||
if (clipDistanceCount > MaxClipDistances) {
|
||||
FailEmit("guest clip distances plus the all-zero clip position distance exceed " + std::to_string(MaxClipDistances) + " clip distances");
|
||||
}
|
||||
}
|
||||
const auto BuiltIn = [&](std::uint32_t& variable, std::uint32_t type, const char* name, std::uint32_t builtin) {
|
||||
if (variable == 0u) {
|
||||
variable = DefineInterfaceVariable(state, type, spv::StorageClassOutput, name);
|
||||
|
|
@ -355,6 +369,9 @@ void DefineOutputs(SpirvEmitterState& state) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (state.zeroPositionClipIndex) {
|
||||
BuiltIn(state.clipDistanceVariable, F32ArrayType(state, clipDistanceCount), "gl_ClipDistance", spv::BuiltInClipDistance);
|
||||
}
|
||||
if (state.program.Resources().stage == IrShaderStage::Pixel && state.inputInfo.pixel != nullptr && state.inputInfo.pixel->psAlphaToCoverage) {
|
||||
if (state.inputInfo.pixel->psSampleMaskExportEnable) throw std::runtime_error("alpha-to-coverage with a sample-mask export is unsupported");
|
||||
BuiltIn(state.sampleMaskVariable, SampleMaskArrayType(state), "gl_SampleMask", spv::BuiltInSampleMask);
|
||||
|
|
|
|||
|
|
@ -417,6 +417,32 @@ void EmitAuxPositionExport(SpirvValueEmitContext& ctx, std::uint32_t data, const
|
|||
}
|
||||
}
|
||||
|
||||
void StoreZeroPositionClipDistance(SpirvEmitterState& state, std::uint32_t position) {
|
||||
const auto bits = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpBitcast, TypeU32Vector(state, 4u), bits, position);
|
||||
std::uint32_t combined = 0u;
|
||||
for (std::uint32_t component = 0; component < 4u; component++) {
|
||||
const auto raw = state.module.AllocateId();
|
||||
const auto magnitude = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpCompositeExtract, TypeU32(state), raw, bits, component);
|
||||
state.module.AddFunction(spv::OpBitwiseAnd, TypeU32(state), magnitude, raw, ConstantU32(state, 0x7fffffffu));
|
||||
if (component == 0u) {
|
||||
combined = magnitude;
|
||||
continue;
|
||||
}
|
||||
const auto merged = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpBitwiseOr, TypeU32(state), merged, combined, magnitude);
|
||||
combined = merged;
|
||||
}
|
||||
const auto allZero = state.module.AllocateId();
|
||||
const auto distance = state.module.AllocateId();
|
||||
const auto pointer = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpIEqual, TypeBool(state), allZero, combined, ConstantU32(state, 0u));
|
||||
state.module.AddFunction(spv::OpSelect, TypeF32(state), distance, allZero, ConstantF32Value(state, -1.0f), ConstantF32Value(state, 0.0f));
|
||||
state.module.AddFunction(spv::OpAccessChain, TypePointer(state, spv::StorageClassOutput, TypeF32(state)), pointer, state.clipDistanceVariable, ConstantU32(state, *state.zeroPositionClipIndex));
|
||||
state.module.AddFunction(spv::OpStore, pointer, distance);
|
||||
}
|
||||
|
||||
std::uint32_t ConvertClipCoordinate(SpirvEmitterState& state, std::uint32_t coordinate, float scale, float offset, float halfExtent) {
|
||||
const auto window = state.module.AllocateId();
|
||||
const auto biased = state.module.AllocateId();
|
||||
|
|
@ -1312,6 +1338,9 @@ void EmitSetAttribute(SpirvValueEmitContext& ctx, const IrValue& inst) {
|
|||
value = ConvertPositionToClipSpace(state, value);
|
||||
}
|
||||
}
|
||||
if (exp.kind == ExportTargetKind::Position && state.zeroPositionClipIndex) {
|
||||
StoreZeroPositionClipDistance(state, value);
|
||||
}
|
||||
if (state.program.Resources().stage == IrShaderStage::Mesh) {
|
||||
const auto kind = exp.kind == ExportTargetKind::Position ? StageOutputKind::Position : StageOutputKind::Parameter;
|
||||
state.module.AddFunction(spv::OpStore, MeshOutputPointer(state, kind, exp.index), value);
|
||||
|
|
|
|||
|
|
@ -625,6 +625,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- The [s_swappc_b64 fetch-shader call at vertex/local/tessellation/mesh entry](../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) assumes the called fetch-shader blob ends in `s_setpc_b64` to the link pair (the GNM fetch-shader shape shadPS4's `EmitFetch` copies); the blob is not part of the shader program, so its return closes the pairing by assumption and any in-program reader of the link pair throws. Attribute fetches are inlined from the request's parsed fetch-shader plan through the same `GetAttribute` path as the embedded fetch analyzer, honoring the buffer resource `dst_sel`; an attribute writing more than four registers, or an entry call without a parsed plan, throws. The vertex/instance offset accumulator inside the fetch blob is not replayed for external blobs.
|
||||
- The scalar aperture sources [src_shared_base, src_shared_limit, src_private_base, src_private_limit](../../core/shader/recompiler/RdnaDecoder/src/RdnaInstructionDecoder.cpp) (core/shader/recompiler) decode to their own operand kinds (scalar source codes 235 to 238, named as in LLVM's `SIRegisterInfo.td`), but their value is not modelled: reading one refuses with the source it refused named, because the wave's shared and private aperture bases and limits are unknown. Both the scalar-code read and the raw-operand read refuse; a shader that only carries the value into an address still prepares, one that reads it does not
|
||||
- [VertexBufferReadSize](../../core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp) and the [vertex staging copy](../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - a vertex fetch whose index is at or past the descriptor's `num_records` no longer throws for `OOB_SELECT` 0 and 1. The copy still spans the whole fetch, but the bytes from `num_records * stride` onward read as zeros, so the out-of-range vertex is `(0, 0, 0, 0)`. Measured with `buffer_load_format_xyzw` (`idxen`) through the [hardware oracle](HW_ORACLE.md) on RDNA2 gfx1036 for PPSA01325's word 3 `0x0004dfac` (`OOB_SELECT` 0) in #2241: indices 3, 4 and 5 over 3 records each return `00000000 00000000 00000000 00000000`, W included, unlike Vulkan `robustBufferAccess`, which returns `(0, 0, 0, 1)`. The zeros come from the record check, not from memory: stored values placed past the records are ignored, and a `num_records` 6 control returns them. `RESOURCE_LEVEL` 1, `OOB_SELECT` 1 and FORMAT 74 `32_32_32_FLOAT` give the same zeros, in wave32 and wave64; gfx1013 (the console) is not measured. `OOB_SELECT` 2 returns the stored values past the records and `OOB_SELECT` 3 compares `num_records` against the byte offset, both measured on the same setup in #2241; the two keep throwing past `num_records` here, since each needs its own rule. Zero-stride descriptors (`stride` 0) keep their separate byte-count check and are not covered here. Each out-of-range fetch is copied alone and outside the draw-snapshot cache, which is neither read nor written for it: a reused snapshot would carry another draw's bytes into the zero region, and a stored zero-padded copy would hand zeros to a later in-range draw at the same address. The valid prefix is still flushed for GPU writes before it is read.
|
||||
- [EmitSetAttribute](../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) (shader recompiler) - a primitive with a vertex whose clip position is (0,0,0,0) is clipped away through an extra `gl_ClipDistance` element on vertex and tessellation evaluation shaders, matching what a Radeon 610M (RDNA2, RADV) and llvmpipe rasterise; NVIDIA 615.78.08 without it draws the vertex at the viewport centre. Measured for triangles (strip and list) and for a point at (0,0,0,0), which the Radeon 610M does not draw, NVIDIA draws at the viewport centre and llvmpipe at the top-left corner; lines with such a vertex are clipped by the same element and were not measured. Mesh, geometry and the generated rect-list and barycentric stages do not write it, a device without `shaderClipDistance` cannot compile vertex shaders (the compile throws), and guest clip distances are still refused by the pipeline validator. The PS5 rasteriser was not measured.
|
||||
- [sceImeKeyboardGetResourceId](../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) reports that no USB keyboard is connected (`0x80bc0004`, as [shadPS4](https://github.com/shadps4-emu/shadPS4) does) because IME keyboard events are not delivered to the handler, although [libSceKeyboard](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) exposes the host keyboard
|
||||
- [Player review dialog](../../core/libs/prx/libSceCdlgPlayerReview/Export.cpp) (libSceCdlgPlayerReview) shows nothing: `scePlayerReviewDialogOpen` finishes at once and the result reports that the user canceled, as `sceWebBrowserDialogOpen` does
|
||||
- [NP reachability callbacks](../../core/libs/prx/libSceNpManager/Export.cpp) retain one callback and its user data; duplicate registration and unregistering an absent callback return the errors used by [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/945dbc3cc3eee80ac3e053b438502ed936fa6bb2/src/core/libraries/np/np_manager.cpp). No callback is delivered because the existing NP model remains signed out with reachability unavailable.
|
||||
|
|
|
|||
Loading…
Reference in New Issue