fix(agc): export the stencil reference from pixel shaders with VK_EXT_shader_stencil_export
This commit is contained in:
parent
d06c065657
commit
22d662eda7
|
|
@ -64,6 +64,7 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr<Vulk
|
|||
for (const bool flag : {pixel.wave32, pixel.hasPerspectiveCenterVgpr, pixel.perspectiveCentroid, pixel.posX, pixel.posY, pixel.posZ, pixel.posW, pixel.frontFace, pixel.ancillary, pixel.sampleShading, pixel.noPerspective, pixel.linearCentroid, pixel.pixelKillEnable, pixel.depthExportEnable, pixel.sampleMaskExportEnable, pixel.earlyZ, pixel.executeOnNoop}) mix(flag);
|
||||
mix(static_cast<std::uint64_t>(pixel.conservativeZExport));
|
||||
mix(pixel.orderedPixelShader);
|
||||
mix(pixel.stencilRefExportEnable);
|
||||
for (const auto value : pixel.targetOutputMode) mix(value);
|
||||
for (const auto value : pixel.targetExportMapping) mix(value);
|
||||
for (const auto value : pixel.targetExportPacking) mix(static_cast<std::uint64_t>(value));
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) {
|
|||
if (std::memcmp(&s.viewport, &t.viewport, sizeof(VkViewport)) != 0 || std::memcmp(&s.scissor, &t.scissor, sizeof(VkRect2D)) != 0) return false;
|
||||
const auto& p = a.pixel;
|
||||
const auto& q = b.pixel;
|
||||
if (p.interpolatorCount != q.interpolatorCount || p.interpolatorSettings != q.interpolatorSettings || p.wave32 != q.wave32 || p.inputAddr != q.inputAddr || p.hasPerspectiveCenterVgpr != q.hasPerspectiveCenterVgpr || p.perspectiveCentroid != q.perspectiveCentroid || p.posX != q.posX || p.posY != q.posY || p.posZ != q.posZ || p.posW != q.posW || p.frontFace != q.frontFace || p.ancillary != q.ancillary || p.sampleShading != q.sampleShading || p.noPerspective != q.noPerspective || p.linearCentroid != q.linearCentroid || p.pixelKillEnable != q.pixelKillEnable || p.depthExportEnable != q.depthExportEnable || p.sampleMaskExportEnable != q.sampleMaskExportEnable || p.earlyZ != q.earlyZ || p.executeOnNoop != q.executeOnNoop || p.conservativeZExport != q.conservativeZExport || p.orderedPixelShader != q.orderedPixelShader || p.targetOutputMode != q.targetOutputMode || p.targetExportMapping != q.targetExportMapping) return false;
|
||||
if (p.interpolatorCount != q.interpolatorCount || p.interpolatorSettings != q.interpolatorSettings || p.wave32 != q.wave32 || p.inputAddr != q.inputAddr || p.hasPerspectiveCenterVgpr != q.hasPerspectiveCenterVgpr || p.perspectiveCentroid != q.perspectiveCentroid || p.posX != q.posX || p.posY != q.posY || p.posZ != q.posZ || p.posW != q.posW || p.frontFace != q.frontFace || p.ancillary != q.ancillary || p.sampleShading != q.sampleShading || p.noPerspective != q.noPerspective || p.linearCentroid != q.linearCentroid || p.pixelKillEnable != q.pixelKillEnable || p.depthExportEnable != q.depthExportEnable || p.sampleMaskExportEnable != q.sampleMaskExportEnable || p.earlyZ != q.earlyZ || p.executeOnNoop != q.executeOnNoop || p.conservativeZExport != q.conservativeZExport || p.orderedPixelShader != q.orderedPixelShader || p.stencilRefExportEnable != q.stencilRefExportEnable || p.targetOutputMode != q.targetOutputMode || p.targetExportMapping != q.targetExportMapping) return false;
|
||||
if (a.roles != b.roles || a.programs.size() != b.programs.size()) return false;
|
||||
for (std::size_t i = 0; i < a.programs.size(); ++i) {
|
||||
const auto& x = a.programs[i];
|
||||
|
|
|
|||
|
|
@ -1095,6 +1095,11 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
|
|||
state->capabilities.push_back(spv::CapabilityFragmentShaderPixelInterlockEXT);
|
||||
state->spirvExtensions.push_back("SPV_EXT_fragment_shader_interlock");
|
||||
}
|
||||
if (hasExtension(VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME)) {
|
||||
deviceExtensions.push_back(VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME);
|
||||
state->capabilities.push_back(spv::CapabilityStencilExportEXT);
|
||||
state->spirvExtensions.push_back("SPV_EXT_shader_stencil_export");
|
||||
}
|
||||
if (state->shaderClock) {
|
||||
deviceExtensions.push_back(VK_KHR_SHADER_CLOCK_EXTENSION_NAME);
|
||||
state->capabilities.push_back(spv::CapabilityShaderClockKHR);
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con
|
|||
const bool pixelKillEnable = ((shaderControl >> 6u) & 0x1u) != 0;
|
||||
const bool depthExportEnable = (shaderControl & 0x1u) != 0;
|
||||
const bool sampleMaskExportEnable = ((shaderControl >> 8u) & 0x1u) != 0;
|
||||
const bool stencilRefExportEnable = ((shaderControl >> 1u) & 0x1u) != 0;
|
||||
const auto zOrder = (shaderControl >> 4u) & 0x3u;
|
||||
const auto loaded = [&](PixelInput input) { return (activeInputs & PixelInputBit(input)) != 0; };
|
||||
return ShaderRecompiler::ShaderPixelStageInfo{
|
||||
|
|
@ -163,12 +164,13 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con
|
|||
.pixelKillEnable = pixelKillEnable,
|
||||
.depthExportEnable = depthExportEnable,
|
||||
.sampleMaskExportEnable = sampleMaskExportEnable,
|
||||
.earlyZ = zOrder == 1u && !pixelKillEnable && !depthExportEnable && !sampleMaskExportEnable,
|
||||
.earlyZ = zOrder == 1u && !pixelKillEnable && !depthExportEnable && !sampleMaskExportEnable && !stencilRefExportEnable,
|
||||
.executeOnNoop = ((shaderControl >> 10u) & 0x1u) != 0,
|
||||
.conservativeZExport = static_cast<ShaderRecompiler::ConservativeZExport>(conservativeZExport),
|
||||
.orderedPixelShader = ((shaderControl >> 16u) & 0x1u) != 0,
|
||||
.targetOutputMode = targetOutputMode,
|
||||
.targetExportMapping = nullProgram ? std::array<std::uint8_t, 8>{} : exportMappings
|
||||
.targetExportMapping = nullProgram ? std::array<std::uint8_t, 8>{} : exportMappings,
|
||||
.stencilRefExportEnable = stencilRefExportEnable
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ struct Module {
|
|||
Require(value == spv::BuiltInPosition && signature == "f32x4" && !position, "unsupported or duplicate vertex built-in output");
|
||||
position = true;
|
||||
} else {
|
||||
Require((storage == spv::StorageClassInput && ((value == spv::BuiltInFragCoord && signature == "f32x4") || ((value == spv::BuiltInFrontFacing || value == spv::BuiltInHelperInvocation) && signature == "bool") || ((value == spv::BuiltInSampleId || value == spv::BuiltInLayer) && signature == "i32"))) || (storage == spv::StorageClassOutput && ((value == spv::BuiltInFragDepth && signature == "f32") || (value == spv::BuiltInSampleMask && (signature == "i32[1]" || signature == "u32[1]")))), "unsupported fragment built-in");
|
||||
Require((storage == spv::StorageClassInput && ((value == spv::BuiltInFragCoord && signature == "f32x4") || ((value == spv::BuiltInFrontFacing || value == spv::BuiltInHelperInvocation) && signature == "bool") || ((value == spv::BuiltInSampleId || value == spv::BuiltInLayer) && signature == "i32"))) || (storage == spv::StorageClassOutput && ((value == spv::BuiltInFragDepth && signature == "f32") || (value == spv::BuiltInFragStencilRefEXT && signature == "i32") || (value == spv::BuiltInSampleMask && (signature == "i32[1]" || signature == "u32[1]")))), "unsupported fragment built-in");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -232,6 +232,10 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
fragment &&
|
||||
capability == spv::CapabilityFragmentShaderPixelInterlockEXT;
|
||||
|
||||
const bool isStencilExportCapability =
|
||||
fragment &&
|
||||
capability == spv::CapabilityStencilExportEXT;
|
||||
|
||||
// Enabled unconditionally or by the device setup in VulkanDevice.
|
||||
const bool isFeatureCapability =
|
||||
capability == spv::CapabilitySampled1D ||
|
||||
|
|
@ -273,6 +277,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
isMeshCapability ||
|
||||
isGeometryCapability ||
|
||||
isInterlockCapability ||
|
||||
isStencilExportCapability ||
|
||||
isFeatureCapability ||
|
||||
isDescriptorIndexingCapability ||
|
||||
((imageInt64Atomics || bufferInt64Atomics) && capability == spv::CapabilityInt64Atomics) ||
|
||||
|
|
@ -293,6 +298,10 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
Require(fragment, "SPV_EXT_fragment_shader_interlock requires a fragment shader");
|
||||
break;
|
||||
}
|
||||
if (extension == "SPV_EXT_shader_stencil_export") {
|
||||
Require(fragment, "SPV_EXT_shader_stencil_export requires a fragment shader");
|
||||
break;
|
||||
}
|
||||
if (extension == "SPV_KHR_fragment_shader_barycentric") {
|
||||
Require(fragment && fragmentShaderBarycentric, "SPV_KHR_fragment_shader_barycentric requires enabled fragmentShaderBarycentric in a fragment shader");
|
||||
break;
|
||||
|
|
@ -420,7 +429,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
mode(spv::ExecutionModeOutputVertices, {3u});
|
||||
Require(module.modes.size() == 4, "unsupported geometry execution mode");
|
||||
} else if (fragment) {
|
||||
for (const auto& [name, operands] : module.modes) Require(operands.empty() && (name == spv::ExecutionModeOriginUpperLeft || name == spv::ExecutionModeEarlyFragmentTests || name == spv::ExecutionModeDepthReplacing || name == spv::ExecutionModeDepthLess || name == spv::ExecutionModeDepthGreater || name == spv::ExecutionModePixelInterlockOrderedEXT), "unsupported fragment execution mode");
|
||||
for (const auto& [name, operands] : module.modes) Require(operands.empty() && (name == spv::ExecutionModeOriginUpperLeft || name == spv::ExecutionModeEarlyFragmentTests || name == spv::ExecutionModeDepthReplacing || name == spv::ExecutionModeDepthLess || name == spv::ExecutionModeDepthGreater || name == spv::ExecutionModePixelInterlockOrderedEXT || name == spv::ExecutionModeStencilRefReplacingEXT), "unsupported fragment execution mode");
|
||||
} else Require(module.modes.empty(), "unsupported vertex execution mode");
|
||||
std::set<std::pair<std::uint32_t, std::uint32_t>> descriptors;
|
||||
bool push = false;
|
||||
|
|
|
|||
|
|
@ -119,9 +119,11 @@ bool zFormatSupported(std::uint32_t format) {
|
|||
|
||||
std::uint32_t shaderControlMask(std::uint32_t zFormat) {
|
||||
constexpr std::uint32_t zExportEnable = 0x1u;
|
||||
constexpr std::uint32_t stencilTestValExportEnable = 0x2u;
|
||||
constexpr std::uint32_t maskExportEnable = 0x100u;
|
||||
auto mask = ShaderControlMask;
|
||||
if (zFormat != 0u && zFormatSupported(zFormat)) mask &= ~zExportEnable;
|
||||
if (zFormat == 2u || zFormat == 9u) mask &= ~stencilTestValExportEnable;
|
||||
if (zFormat == 9u) mask &= ~maskExportEnable;
|
||||
return mask;
|
||||
}
|
||||
|
|
@ -154,7 +156,7 @@ bool depthPlanesAbsent(const Registers& cx) {
|
|||
return z != cx.end() && stencil != cx.end() && (z->second & 3u) == 0 && (stencil->second & 1u) == 0;
|
||||
}
|
||||
|
||||
VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint32_t refMask, bool readOnly) {
|
||||
VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint32_t refMask, bool readOnly, bool testValExport) {
|
||||
VkStencilOpState face{};
|
||||
face.compareOp = static_cast<VkCompareOp>(compare);
|
||||
face.compareMask = (refMask >> 8u) & 0xffu;
|
||||
|
|
@ -172,6 +174,10 @@ VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint
|
|||
case 2:
|
||||
case 3:
|
||||
case 4: {
|
||||
if (testValExport) {
|
||||
if (op != 3) throw std::runtime_error("AGC graphics: stencil replacement by a value other than the test value is unsupported with a pixel shader that exports the stencil test value");
|
||||
return VK_STENCIL_OP_REPLACE;
|
||||
}
|
||||
const auto value = op == 2 ? 0xffu : op == 3 ? test : opValue;
|
||||
if (((reference ^ value) & fixed & face.writeMask) != 0) {
|
||||
std::snprintf(detail, sizeof(detail), "AGC graphics: stencil replacement 0x%02x against test value 0x%02x (masks 0x%02x/0x%02x) is unsupported", value, test, face.compareMask, face.writeMask);
|
||||
|
|
@ -203,7 +209,7 @@ VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint
|
|||
return face;
|
||||
}
|
||||
|
||||
void decodeDepth(const Registers& cx, std::uint32_t depthControl, State& result) {
|
||||
void decodeDepth(const Registers& cx, std::uint32_t depthControl, bool testValExport, State& result) {
|
||||
zero(cx, 0x000, 0x00001f9cu, "depth copy, resummarize or decompress draws (DB_RENDER_CONTROL)");
|
||||
const bool depthClear = (read(cx, 0x000) & 1u) != 0;
|
||||
const bool stencilClear = (read(cx, 0x000) & 2u) != 0;
|
||||
|
|
@ -223,6 +229,7 @@ void decodeDepth(const Registers& cx, std::uint32_t depthControl, State& result)
|
|||
const bool depthReadOnly = (view & 0x01000000u) != 0;
|
||||
const bool stencilReadOnly = (view & 0x02000000u) != 0;
|
||||
Require(!stencilClear || (stencil && !stencilReadOnly), "stencil clear requires a writable stencil plane");
|
||||
Require(!stencilClear || !testValExport, "stencil clear with a pixel shader that exports the stencil test value is unsupported");
|
||||
Require(!depthClear || (zFormat != 0 && !depthReadOnly), "depth clear requires a writable depth plane");
|
||||
DepthTarget depth{};
|
||||
depth.samples = 1u << ((read(cx, 0x010) >> 2u) & 3u);
|
||||
|
|
@ -274,8 +281,8 @@ void decodeDepth(const Registers& cx, std::uint32_t depthControl, State& result)
|
|||
result.stencilBack = clear;
|
||||
} else if (result.stencilTest) {
|
||||
const auto ops = read(cx, 0x10b);
|
||||
result.stencilFront = stencilFace((depthControl >> 8u) & 7u, ops, read(cx, 0x10c), stencilReadOnly);
|
||||
result.stencilBack = (depthControl & 0x80u) != 0 ? stencilFace((depthControl >> 20u) & 7u, ops >> 12u, read(cx, 0x10d), stencilReadOnly) : result.stencilFront;
|
||||
result.stencilFront = stencilFace((depthControl >> 8u) & 7u, ops, read(cx, 0x10c), stencilReadOnly, testValExport);
|
||||
result.stencilBack = (depthControl & 0x80u) != 0 ? stencilFace((depthControl >> 20u) & 7u, ops >> 12u, read(cx, 0x10d), stencilReadOnly, testValExport) : result.stencilFront;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -667,7 +674,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
|
|||
const bool clear = renderControl != cx.end() && (renderControl->second & 3u) != 0;
|
||||
const auto tests = planeTests(cx, depthControl);
|
||||
if (clear || (tests != 0 && depthSurfaceBound(cx))) {
|
||||
decodeDepth(cx, depthControl, result);
|
||||
decodeDepth(cx, depthControl, (read(cx, 0x203) & 2u) != 0 && !PixelProgramSkipped(queue), result);
|
||||
} else if (depthPassThrough(depthControl) || ((depthControl & 0xbu) != 0 && (tests == 0 || depthPlanesAbsent(cx)))) {
|
||||
static bool reported = false;
|
||||
if (!reported) {
|
||||
|
|
|
|||
|
|
@ -3688,6 +3688,131 @@ void orderedPixelShaderTests() {
|
|||
expectFailure([&] { static_cast<void>(ShaderRecompiler::Recompile(orderedPixelRequest(0x30600u, plain))); }, "fragmentShaderPixelInterlock");
|
||||
}
|
||||
|
||||
constexpr std::array<std::uint32_t, 4> StencilRefExportCode{0x7e0e02f2u, 0xf8001882u, 0x700u, 0xbf810000u};
|
||||
constexpr std::array<std::uint32_t, 4> DepthStencilRefExportCode{0x7e0e02f2u, 0xf8001883u, 0x707u, 0xbf810000u};
|
||||
|
||||
AgcDriver::QueueState stencilRefExportQueue(std::uint32_t shaderControl, std::uint32_t zFormat) {
|
||||
auto queue = makeState();
|
||||
queue.context[0x1b3] = 0x300u;
|
||||
queue.context[0x1b4] = 0x300u;
|
||||
queue.context[0x1c4] = zFormat;
|
||||
queue.context[0x203] = shaderControl;
|
||||
return queue;
|
||||
}
|
||||
|
||||
ShaderRecompiler::RecompileRequest stencilRefExportRequest(std::uint32_t shaderControl, std::span<const std::uint32_t> capabilities, std::span<const std::uint32_t> code = StencilRefExportCode) {
|
||||
const auto queue = stencilRefExportQueue(shaderControl, 2u);
|
||||
ShaderRecompiler::RecompileRequest request{};
|
||||
request.shader = {ShaderRecompiler::ShaderStage::Fragment, 0x30000u, code, 0, {}};
|
||||
request.context.waveSize = 64;
|
||||
request.context.pixel = AgcDriver::Graphics::DecodePixelStageInfo(queue.context, IdentityExports);
|
||||
request.target.vulkanVersion = 0x00401000u;
|
||||
request.target.spirvVersion = 0x00010300u;
|
||||
request.target.subgroupSize = 64;
|
||||
request.target.supportedCapabilities = capabilities;
|
||||
request.layout.pushConstantSizeBytes = 128;
|
||||
request.useCache = false;
|
||||
return request;
|
||||
}
|
||||
|
||||
std::string stencilRefShape(std::span<const std::uint32_t> words) {
|
||||
std::string shape;
|
||||
std::uint32_t variable = 0;
|
||||
for (std::size_t at = 5; at < words.size() && (words[at] >> 16u) != 0; at += words[at] >> 16u) {
|
||||
const auto op = static_cast<spv::Op>(words[at] & 0xffffu);
|
||||
if (op == spv::OpCapability && words[at + 1] == spv::CapabilityStencilExportEXT) shape += "capability ";
|
||||
if (op == spv::OpExtension && std::string_view(reinterpret_cast<const char*>(&words[at + 1])) == "SPV_EXT_shader_stencil_export") shape += "extension ";
|
||||
if (op == spv::OpExecutionMode && words[at + 2] == spv::ExecutionModeEarlyFragmentTests) shape += "early ";
|
||||
if (op == spv::OpExecutionMode && words[at + 2] == spv::ExecutionModeDepthReplacing) shape += "depth ";
|
||||
if (op == spv::OpExecutionMode && words[at + 2] == spv::ExecutionModeStencilRefReplacingEXT) shape += "replacing ";
|
||||
if (op == spv::OpDecorate && words[at + 2] == spv::DecorationBuiltIn && words[at + 3] == spv::BuiltInFragStencilRefEXT) {
|
||||
variable = words[at + 1];
|
||||
shape += "builtin ";
|
||||
}
|
||||
if (op == spv::OpStore && variable != 0 && words[at + 1] == variable) shape += "store ";
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
||||
void stencilRefExportTests() {
|
||||
for (const auto shaderControl : {0x52u, 0x12u, 0x02u, 0x03u}) {
|
||||
for (const auto zFormat : {2u, 9u}) {
|
||||
const auto queue = stencilRefExportQueue(shaderControl, zFormat);
|
||||
static_cast<void>(AgcDriver::Graphics::DecodeState(queue));
|
||||
Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "the precheck rejected DB_SHADER_CONTROL " + std::to_string(shaderControl) + " with Z format " + std::to_string(zFormat));
|
||||
const auto pixel = AgcDriver::Graphics::DecodePixelStageInfo(queue.context, IdentityExports);
|
||||
Require(pixel.stencilRefExportEnable, "STENCIL_TEST_VAL_EXPORT_ENABLE did not reach the pixel stage");
|
||||
Require(!pixel.earlyZ, "a pixel shader that exports the stencil reference was given early tests");
|
||||
}
|
||||
for (const auto zFormat : {0u, 1u, 3u, 7u}) {
|
||||
const auto queue = stencilRefExportQueue(shaderControl, zFormat);
|
||||
expectFailure([&] { static_cast<void>(AgcDriver::Graphics::DecodeState(queue)); }, "depth export, shader coverage or ordered fragment execution");
|
||||
Require(!AgcDriver::Graphics::DrawRejection(queue, false).empty(), "the precheck accepted a stencil reference export with Z format " + std::to_string(zFormat));
|
||||
}
|
||||
}
|
||||
for (const auto shaderControl : {0x54u, 0x56u}) {
|
||||
const auto queue = stencilRefExportQueue(shaderControl, 2u);
|
||||
expectFailure([&] { static_cast<void>(AgcDriver::Graphics::DecodeState(queue)); }, "depth export, shader coverage or ordered fragment execution");
|
||||
Require(!AgcDriver::Graphics::DrawRejection(queue, false).empty(), "the precheck accepted STENCIL_OP_VAL_EXPORT_ENABLE in DB_SHADER_CONTROL " + std::to_string(shaderControl));
|
||||
}
|
||||
Require(!AgcDriver::Graphics::DecodePixelStageInfo(stencilRefExportQueue(0x50u, 2u).context, IdentityExports).stencilRefExportEnable, "a pixel stage without STENCIL_TEST_VAL_EXPORT_ENABLE exports the stencil reference");
|
||||
const std::array<std::uint32_t, 2> stencilExport{spv::CapabilityShader, spv::CapabilityStencilExportEXT};
|
||||
const std::array<std::uint32_t, 1> plain{spv::CapabilityShader};
|
||||
const ShaderRecompiler::RequestSerializer serializer;
|
||||
std::set<std::vector<std::uint64_t>> keys;
|
||||
const std::array<std::pair<std::uint32_t, std::string_view>, 4> cases{{{0x52u, "capability extension replacing builtin store "}, {0x12u, "capability extension replacing builtin store "}, {0x50u, ""}, {0x10u, "early "}}};
|
||||
for (const auto& [shaderControl, expected] : cases) {
|
||||
const auto request = stencilRefExportRequest(shaderControl, stencilExport);
|
||||
const auto replayed = serializer.Deserialize(serializer.Serialize(request));
|
||||
Require(replayed.request.context.pixel->stencilRefExportEnable == ((shaderControl & 2u) != 0), "the serialized request lost STENCIL_TEST_VAL_EXPORT_ENABLE");
|
||||
const auto result = ShaderRecompiler::Recompile(request);
|
||||
Require(stencilRefShape(result.spirv.Words()) == expected && stencilRefShape(ShaderRecompiler::Recompile(replayed.request).spirv.Words()) == expected, "DB_SHADER_CONTROL " + std::to_string(shaderControl) + " declared the wrong stencil reference export: " + stencilRefShape(result.spirv.Words()));
|
||||
std::vector<std::uint64_t> key;
|
||||
ShaderRecompiler::RecompileCacheKey::Build(request, key);
|
||||
keys.insert(key);
|
||||
AgcDriver::Graphics::State state{};
|
||||
state.stages.path = AgcDriver::Graphics::ShaderPath::Vertex;
|
||||
ShaderRecompiler::RecompileResult vertex;
|
||||
vertex.spirv = makeModule({});
|
||||
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &result, 0}}};
|
||||
AgcDriver::Graphics::ValidateShaders(shaders, state, VkPhysicalDeviceSubgroupProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES}, false);
|
||||
}
|
||||
Require(keys.size() == cases.size(), "the recompile cache key ignores STENCIL_TEST_VAL_EXPORT_ENABLE");
|
||||
const auto both = stencilRefShape(ShaderRecompiler::Recompile(stencilRefExportRequest(0x53u, stencilExport, DepthStencilRefExportCode)).spirv.Words());
|
||||
Require(both == "capability extension depth replacing builtin store ", "a depth and stencil reference export declared the wrong outputs: " + both);
|
||||
const auto depthOnly = stencilRefShape(ShaderRecompiler::Recompile(stencilRefExportRequest(0x51u, stencilExport, DepthStencilRefExportCode)).spirv.Words());
|
||||
Require(depthOnly == "depth ", "an MRTZ stencil channel without STENCIL_TEST_VAL_EXPORT_ENABLE declared the wrong outputs: " + depthOnly);
|
||||
expectFailure([&] { static_cast<void>(ShaderRecompiler::Recompile(stencilRefExportRequest(0x52u, plain))); }, "VK_EXT_shader_stencil_export");
|
||||
auto queue = stencilRefExportQueue(0x52u, 2u);
|
||||
queue.context[0x000] = 0;
|
||||
queue.context[0x002] = 0;
|
||||
queue.context[0x007] = (1u << 16u) | 3u;
|
||||
queue.context[0x00a] = 7;
|
||||
queue.context[0x00b] = 0;
|
||||
queue.context[0x010] = 0x22900983;
|
||||
queue.context[0x011] = 0x20000181;
|
||||
for (const auto offset : {0x012u, 0x014u}) queue.context[offset] = 0x100;
|
||||
for (const auto offset : {0x013u, 0x015u}) queue.context[offset] = 0x200;
|
||||
queue.context[0x10b] = 0x00030030;
|
||||
queue.context[0x10c] = 0x05ffff02;
|
||||
queue.context[0x10d] = 0x05ffff02;
|
||||
queue.context[0x200] = 0x00200211;
|
||||
auto state = AgcDriver::Graphics::DecodeState(queue);
|
||||
Require(state.stencilTest && state.stencilFront.compareOp == VK_COMPARE_OP_EQUAL && state.stencilFront.passOp == VK_STENCIL_OP_REPLACE, "a stencil reference export changed the REPLACE_TEST decode");
|
||||
for (const auto ops : {0x00020020u, 0x00040040u, 0x00000200u, 0x00000004u}) {
|
||||
queue.context[0x10b] = ops;
|
||||
expectFailure([&] { static_cast<void>(AgcDriver::Graphics::DecodeState(queue)); }, "exports the stencil test value");
|
||||
}
|
||||
queue.context[0x10b] = 0x00050050;
|
||||
queue.context[0x10c] = 0x01ffff02;
|
||||
state = AgcDriver::Graphics::DecodeState(queue);
|
||||
Require(state.stencilFront.passOp == VK_STENCIL_OP_INCREMENT_AND_CLAMP, "a stencil reference export changed the increment decode");
|
||||
queue.context[0x000] = 2;
|
||||
expectFailure([&] { static_cast<void>(AgcDriver::Graphics::DecodeState(queue)); }, "stencil clear with a pixel shader that exports the stencil test value");
|
||||
queue.context[0x203] = 0x50u;
|
||||
static_cast<void>(AgcDriver::Graphics::DecodeState(queue));
|
||||
}
|
||||
|
||||
void ProvokingVertexTests() {
|
||||
using namespace AgcDriver::Graphics;
|
||||
constexpr std::uint32_t lastVertex = 1u << 19u;
|
||||
|
|
@ -4705,6 +4830,7 @@ int main(int argc, char** argv) {
|
|||
DepthBoundsBiasTests();
|
||||
conservativeZExportTests();
|
||||
orderedPixelShaderTests();
|
||||
stencilRefExportTests();
|
||||
ProvokingVertexTests();
|
||||
ProvokingVertexRenderPassTests();
|
||||
ConservativeRasterizationTests();
|
||||
|
|
|
|||
|
|
@ -1542,7 +1542,7 @@ void verifyPixelRequestSerialization() {
|
|||
value.posZ, value.posW, value.frontFace, value.ancillary, value.sampleShading,
|
||||
value.noPerspective, value.linearCentroid, value.pixelKillEnable, value.depthExportEnable,
|
||||
value.sampleMaskExportEnable, value.earlyZ, value.executeOnNoop, value.conservativeZExport, value.orderedPixelShader,
|
||||
value.targetOutputMode, value.targetExportMapping, value.alphaToCoverage);
|
||||
value.targetOutputMode, value.targetExportMapping, value.alphaToCoverage, value.stencilRefExportEnable);
|
||||
};
|
||||
const std::array<std::uint32_t, 1> code{0xbf810000u};
|
||||
const std::array<std::uint32_t, 3> userData{0x12345678u, 0xabcdef01u, 0x87654321u};
|
||||
|
|
@ -1586,6 +1586,7 @@ void verifyPixelRequestSerialization() {
|
|||
};
|
||||
for (std::uint32_t i = 0; i < pixel.interpolatorSettings.size(); ++i) pixel.interpolatorSettings[i] = 0x10101010u + i;
|
||||
pixel.alphaToCoverage = true;
|
||||
pixel.stencilRefExportEnable = true;
|
||||
const std::array<std::array<std::uint8_t, 8>, 3> mappings{{
|
||||
{0x00u, 0xe4u, 0xc6u, 0x1bu, 0xffu, 0x80u, 0x55u, 0xaau},
|
||||
{0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u},
|
||||
|
|
@ -1614,6 +1615,10 @@ void verifyPixelRequestSerialization() {
|
|||
withoutCoverage.context.pixel->alphaToCoverage = false;
|
||||
RecompileCacheKey::Build(withoutCoverage, replayKey);
|
||||
require(key != replayKey && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(withoutCoverage), "alpha-to-coverage did not change the static shader identity");
|
||||
auto withoutStencilRef = request;
|
||||
withoutStencilRef.context.pixel->stencilRefExportEnable = false;
|
||||
RecompileCacheKey::Build(withoutStencilRef, replayKey);
|
||||
require(key != replayKey && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(withoutStencilRef), "the stencil reference export did not change the static shader identity");
|
||||
}
|
||||
request.context.pixel.reset();
|
||||
request.shader.stage = ShaderStage::Vertex;
|
||||
|
|
@ -1625,12 +1630,12 @@ void verifyPixelRequestSerialization() {
|
|||
minimal.context.waveSize = 64;
|
||||
minimal.context.pixel = ShaderPixelStageInfo{};
|
||||
const auto encoded = serializer.Serialize(minimal);
|
||||
require(requestPrefix(encoded, 8u) == "NVNQQRAAAAA=", "new requests did not use serialization version 16");
|
||||
require(requestPrefix(encoded, 8u) == "NVNQQREAAAA=", "new requests did not use serialization version 17");
|
||||
constexpr std::size_t mappingOffset = 8u + 37u + 18u + 163u;
|
||||
for (std::size_t bytes = 0; bytes < 18u; ++bytes) {
|
||||
expectFailure([&] { static_cast<void>(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source or alpha-to-coverage flag was accepted");
|
||||
for (std::size_t bytes = 0; bytes < 19u; ++bytes) {
|
||||
expectFailure([&] { static_cast<void>(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source, alpha-to-coverage or stencil reference export flag was accepted");
|
||||
}
|
||||
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQREAAAA="}) {
|
||||
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQRIAAAA="}) {
|
||||
expectFailure([&] { static_cast<void>(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted");
|
||||
}
|
||||
}
|
||||
|
|
@ -1720,6 +1725,7 @@ void verifyLegacyPixelRequests() {
|
|||
require(pixel.targetExportPacking == std::array<ColorExportPacking, 8>{}, "a legacy request packed a color export");
|
||||
require(!pixel.dualSourceBlend, "a legacy request enabled dual-source blending");
|
||||
require(!pixel.alphaToCoverage, "a legacy request enabled alpha-to-coverage");
|
||||
require(!pixel.stencilRefExportEnable, "a legacy request exported a stencil reference");
|
||||
require(pixel.inputAddr == 2u && pixel.hasPerspectiveCenterVgpr && pixel.targetOutputMode[0] == 9u, "legacy pixel layout was misread");
|
||||
require(pixel.conservativeZExport == (version >= 7u ? ConservativeZExport::GreaterThanZ : ConservativeZExport::AnyZ), "legacy conservative Z layout was misread");
|
||||
require(!pixel.orderedPixelShader, "a legacy request became a primitive-ordered pixel shader");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,159 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Width = 64;
|
||||
constexpr std::uint32_t Height = 16;
|
||||
constexpr std::uint32_t WaveSize = 64;
|
||||
constexpr std::byte Kept{0x40};
|
||||
alignas(256) std::array<std::byte, Width * Height * 4> Pixels{};
|
||||
alignas(256) std::array<std::byte, 128 * 128 * 4> DepthMemory{};
|
||||
alignas(256) std::array<std::byte, 256 * 256> StencilMemory{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 6> VertexCode{
|
||||
0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 4> PlainPixelCode{
|
||||
0x7e0e02f2, 0xf800180f, 0x07070707, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 7> ColumnPixelCode{
|
||||
0x7e040f00, 0x7e0e02f2, 0xf8000082, 0x00000200, 0xf800180f, 0x07070707, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 7> RowPixelCode{
|
||||
0x7e040f01, 0x7e0e02f2, 0xf8000082, 0x00000200, 0xf800180f, 0x07070707, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::array<float, 4>, 3> Vertices{{
|
||||
{-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f},
|
||||
}};
|
||||
|
||||
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};
|
||||
}
|
||||
|
||||
ShaderRecompiler::ShaderPixelStageInfo PixelInfo(std::uint32_t shaderControl) {
|
||||
const AgcDriver::Registers context{
|
||||
{0x1b3, 0x300u}, {0x1b4, 0x300u}, {0x1b6, 0u}, {0x1c5, 9u}, {0x203, shaderControl},
|
||||
};
|
||||
return AgcDriver::Graphics::DecodePixelStageInfo(context, {0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u});
|
||||
}
|
||||
|
||||
void Draw(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> pixelCode, std::uint32_t shaderControl, const VkStencilOpState& stencil) {
|
||||
Pixels.fill(Kept);
|
||||
const auto target = device.Target();
|
||||
|
||||
std::vector<std::uint32_t> vertexUserData(4, 0u);
|
||||
const auto vertexBuffer = BufferDescriptor(Vertices.data(), 16u, 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::RecompileRequest vertex{
|
||||
{ShaderStage::Vertex, reinterpret_cast<std::uintptr_t>(VertexCode.data()), VertexCode, 0, {}},
|
||||
{WaveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory},
|
||||
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());
|
||||
|
||||
const std::vector<std::uint32_t> pixelUserData(8, 0u);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> pixelMemory{{{reinterpret_cast<std::uintptr_t>(pixelCode.data()), std::as_bytes(pixelCode)}}};
|
||||
ShaderRecompiler::RecompileRequest fragment{
|
||||
{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(pixelCode.data()), pixelCode, 0, {}},
|
||||
{WaveSize, 0, pixelUserData, std::nullopt, PixelInfo(shaderControl), std::nullopt, pixelMemory},
|
||||
target,
|
||||
PixelPushLayout(vertexPush, 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, target)}
|
||||
}};
|
||||
|
||||
AgcDriver::Graphics::State state{};
|
||||
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, WaveSize, WaveSize, std::nullopt, std::nullopt};
|
||||
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.depth = AgcDriver::Graphics::DepthTarget{reinterpret_cast<std::uintptr_t>(DepthMemory.data()), reinterpret_cast<std::uintptr_t>(StencilMemory.data()), {Width, Height}, VK_FORMAT_D32_SFLOAT_S8_UINT, 1.0f, 0};
|
||||
state.stencilTest = true;
|
||||
state.stencilFront = stencil;
|
||||
state.stencilBack = stencil;
|
||||
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 std::function<bool(std::uint32_t, std::uint32_t)>& drawn, std::string_view what) {
|
||||
for (std::uint32_t y = 0; y < Height; ++y) {
|
||||
for (std::uint32_t x = 0; x < Width; ++x) {
|
||||
const auto expected = drawn(x, y) ? std::byte{255} : Kept;
|
||||
const auto actual = Pixels[(y * Width + x) * 4u];
|
||||
Require(actual == expected, std::string(what) + ": pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") is " + std::to_string(static_cast<unsigned>(actual)) + ", expected " + (drawn(x, y) ? "drawn" : "stencil-rejected"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
if (!TargetHasCapability(device->Target(), spv::CapabilityStencilExportEXT)) {
|
||||
std::puts("skipped, the device lacks VK_EXT_shader_stencil_export");
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
constexpr std::uint32_t exported = 0x52u;
|
||||
constexpr std::uint32_t plain = 0x50u;
|
||||
const VkStencilOpState replace{VK_STENCIL_OP_KEEP, VK_STENCIL_OP_REPLACE, VK_STENCIL_OP_KEEP, VK_COMPARE_OP_ALWAYS, 0xffu, 0xffu, 0u};
|
||||
const auto equal = [](std::uint32_t reference) { return VkStencilOpState{VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_COMPARE_OP_EQUAL, 0xffu, 0u, reference}; };
|
||||
Draw(*device, ColumnPixelCode, exported, replace);
|
||||
Check([](std::uint32_t, std::uint32_t) { return true; }, "the draw that replaces the stencil with the exported column");
|
||||
for (const auto column : {0u, 5u, 37u, 63u}) {
|
||||
Draw(*device, PlainPixelCode, plain, equal(column));
|
||||
Check([&](std::uint32_t x, std::uint32_t) { return x == column; }, "stencil equal to " + std::to_string(column) + " after the exported column was stored");
|
||||
}
|
||||
Draw(*device, ColumnPixelCode, exported, equal(200u));
|
||||
Check([](std::uint32_t, std::uint32_t) { return true; }, "the exported column tested against the stored column");
|
||||
Draw(*device, RowPixelCode, exported, equal(200u));
|
||||
Check([](std::uint32_t x, std::uint32_t y) { return x == y; }, "the exported row tested against the stored column");
|
||||
Draw(*device, ColumnPixelCode, plain, equal(9u));
|
||||
Check([](std::uint32_t x, std::uint32_t) { return x == 9u; }, "a column export without STENCIL_TEST_VAL_EXPORT_ENABLE");
|
||||
std::puts("stencil reference export tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -177,6 +177,7 @@ private:
|
|||
append(key, value.conservativeZExport);
|
||||
append(key, value.orderedPixelShader);
|
||||
append(key, value.alphaToCoverage);
|
||||
append(key, value.stencilRefExportEnable);
|
||||
append(key, value.targetOutputMode);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -325,6 +325,7 @@ void writePixelInfo(Writer& writer, const ShaderPixelStageInfo& info) {
|
|||
}
|
||||
writer.WriteBool(info.dualSourceBlend);
|
||||
writer.WriteBool(info.alphaToCoverage);
|
||||
writer.WriteBool(info.stencilRefExportEnable);
|
||||
}
|
||||
|
||||
ShaderPixelStageInfo readPixelInfo(Reader& reader, std::uint32_t version) {
|
||||
|
|
@ -373,6 +374,7 @@ ShaderPixelStageInfo readPixelInfo(Reader& reader, std::uint32_t version) {
|
|||
}
|
||||
if (version >= 15u) info.dualSourceBlend = reader.ReadBool();
|
||||
if (version >= 16u) info.alphaToCoverage = reader.ReadBool();
|
||||
if (version >= 17u) info.stencilRefExportEnable = reader.ReadBool();
|
||||
if (version < 5u) {
|
||||
const auto input = [](PixelInput value, bool present) { return present ? PixelInputBit(value) : 0u; };
|
||||
info.inputAddr = input(PixelInput::PerspectiveSample, info.hasPerspectiveCenterVgpr && inputAddrOrCenterVgpr == 2u) | input(PixelInput::PerspectiveCenter, info.hasPerspectiveCenterVgpr) |
|
||||
|
|
@ -734,7 +736,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
|
|||
std::string buffer;
|
||||
Writer writer(buffer);
|
||||
writer.WriteU32(0x41505335u);
|
||||
writer.WriteU32(16u);
|
||||
writer.WriteU32(17u);
|
||||
writeShaderBinary(writer, request.shader);
|
||||
writeGuestContext(writer, request.context);
|
||||
writeSpirvTarget(writer, request.target);
|
||||
|
|
@ -762,7 +764,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 > 16u) throw std::runtime_error("unsupported recompile request serialization version");
|
||||
if (version < 1u || version > 17u) 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);
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ enum class StageOutputKind {
|
|||
Mrt,
|
||||
Depth,
|
||||
SampleMask,
|
||||
StencilRef,
|
||||
PointSize,
|
||||
ClipDistance,
|
||||
CullDistance,
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ struct ShaderPixelInputInfo {
|
|||
bool psPixelKillEnable = false;
|
||||
bool psDepthExportEnable = false;
|
||||
bool psSampleMaskExportEnable = false;
|
||||
bool psStencilRefExportEnable = false;
|
||||
bool psSampleShading = false;
|
||||
bool psEarlyZ = false;
|
||||
bool psExecuteOnNoop = false;
|
||||
|
|
|
|||
|
|
@ -408,6 +408,9 @@ void CollectOutputs(const IrProgram& program, ShaderStageInputInfo inputInfo, Sh
|
|||
if (program.Resources().stage == IrShaderStage::Pixel && (exportInfo.en & 0x4u) != 0u && inputInfo.pixel->psSampleMaskExportEnable) {
|
||||
AddOutput(info, StageOutputKind::SampleMask, 0, 0, "gl_SampleMask");
|
||||
}
|
||||
if (program.Resources().stage == IrShaderStage::Pixel && (exportInfo.en & 0x2u) != 0u && inputInfo.pixel->psStencilRefExportEnable) {
|
||||
AddOutput(info, StageOutputKind::StencilRef, 0, 0, "gl_FragStencilRefARB");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (exportInfo.en == 0u) {
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ struct ShaderPixelStageInfo {
|
|||
std::array<ColorExportPacking, 8> targetExportPacking;
|
||||
bool dualSourceBlend;
|
||||
bool alphaToCoverage;
|
||||
bool stencilRefExportEnable;
|
||||
};
|
||||
|
||||
struct ShaderVertexBufferResource {
|
||||
|
|
|
|||
|
|
@ -171,6 +171,7 @@ struct SpirvEmitterState {
|
|||
std::uint32_t viewportIndexVariable = 0;
|
||||
std::uint32_t depthVariable = 0;
|
||||
std::uint32_t sampleMaskVariable = 0;
|
||||
std::uint32_t stencilRefVariable = 0;
|
||||
std::vector<SpirvInputBinding> inputs;
|
||||
std::vector<SpirvOutputBinding> outputs;
|
||||
std::vector<std::uint32_t> interfaceVariables;
|
||||
|
|
|
|||
|
|
@ -359,6 +359,9 @@ void DefineOutputs(SpirvEmitterState& state) {
|
|||
case StageOutputKind::SampleMask:
|
||||
binding.variableId = BuiltIn(state.sampleMaskVariable, SampleMaskArrayType(state), "gl_SampleMask", spv::BuiltInSampleMask);
|
||||
break;
|
||||
case StageOutputKind::StencilRef:
|
||||
binding.variableId = BuiltIn(state.stencilRefVariable, TypeI32(state), "gl_FragStencilRefARB", spv::BuiltInFragStencilRefEXT);
|
||||
break;
|
||||
case StageOutputKind::Parameter:
|
||||
case StageOutputKind::Mrt: {
|
||||
const bool uintOutput = binding.kind == StageOutputKind::Mrt && state.program.Resources().stage == IrShaderStage::Pixel && binding.index < std::size(PixelInfo(state).targetOutputMode) && PixelInfo(state).targetOutputMode[binding.index] == 7u;
|
||||
|
|
|
|||
|
|
@ -185,7 +185,15 @@ void DefineModule(SpirvEmitterState& state) {
|
|||
state.module.AddExecutionMode(state.mainFunc, spv::ExecutionModeDepthGreater);
|
||||
}
|
||||
}
|
||||
if (pixel.psEarlyZ && !pixel.psPixelKillEnable && !pixel.psDepthExportEnable && !pixel.psSampleMaskExportEnable && !pixel.psAlphaToCoverage) {
|
||||
if (state.stencilRefVariable != 0) {
|
||||
if (std::find(state.supportedCapabilities.begin(), state.supportedCapabilities.end(), static_cast<std::uint32_t>(spv::CapabilityStencilExportEXT)) == state.supportedCapabilities.end()) {
|
||||
throw std::runtime_error("a pixel shader that exports the stencil reference needs VK_EXT_shader_stencil_export, which the device lacks");
|
||||
}
|
||||
state.module.EmitCapability(spv::CapabilityStencilExportEXT);
|
||||
state.module.EmitExtension("SPV_EXT_shader_stencil_export");
|
||||
state.module.AddExecutionMode(state.mainFunc, spv::ExecutionModeStencilRefReplacingEXT);
|
||||
}
|
||||
if (pixel.psEarlyZ && !pixel.psPixelKillEnable && !pixel.psDepthExportEnable && !pixel.psSampleMaskExportEnable && !pixel.psStencilRefExportEnable && !pixel.psAlphaToCoverage) {
|
||||
state.module.AddExecutionMode(state.mainFunc, spv::ExecutionModeEarlyFragmentTests);
|
||||
}
|
||||
if (pixel.psOrderedPixelShader) {
|
||||
|
|
|
|||
|
|
@ -1298,6 +1298,12 @@ void EmitSetAttribute(SpirvValueEmitContext& ctx, const IrValue& inst) {
|
|||
state.module.AddFunction(spv::OpBitcast, TypeF32(state), f32, raw);
|
||||
state.module.AddFunction(spv::OpStore, state.depthVariable, f32);
|
||||
}
|
||||
if ((exp.en & 2u) != 0u && state.stencilRefVariable != 0u) {
|
||||
const auto raw = ExportRawComponent(ctx, data, 1);
|
||||
const auto value = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpBitcast, TypeI32(state), value, raw);
|
||||
state.module.AddFunction(spv::OpStore, state.stencilRefVariable, value);
|
||||
}
|
||||
if ((exp.en & 4u) != 0u && state.sampleMaskVariable != 0u) {
|
||||
const auto raw = ExportRawComponent(ctx, data, 2);
|
||||
const auto value = state.module.AllocateId();
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues
|
|||
pixelStorage.psPixelKillEnable = pixel.pixelKillEnable;
|
||||
pixelStorage.psDepthExportEnable = pixel.depthExportEnable;
|
||||
pixelStorage.psSampleMaskExportEnable = pixel.sampleMaskExportEnable;
|
||||
pixelStorage.psStencilRefExportEnable = pixel.stencilRefExportEnable;
|
||||
pixelStorage.psSampleShading = pixel.sampleShading;
|
||||
pixelStorage.psEarlyZ = pixel.earlyZ;
|
||||
pixelStorage.psExecuteOnNoop = pixel.executeOnNoop;
|
||||
|
|
|
|||
|
|
@ -541,6 +541,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- The length-changing path of the [instruction rewriter](../../core/relinker/codegen/src/x86/X64InstructionRewriter.cpp) is not used by the [converter](../../core/relinker/codegen/src/Amd64OnlyConverter.cpp): it does not adjust data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length.
|
||||
- In executables with a `PT_SCE_DYNLIBDATA` segment, the PS4 layout, the executable segment also holds read-only data after the code (CUSA00900: 37 MiB after its last function). The [relinker pipeline](../../core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp) therefore scans for syscalls and import call sites only up to the end of the PLT that follows the last function of the unwind table; code after the PLT without an unwind entry is not scanned. `--to-intel` and the `unused-filter=1` and `unused-filter=2` analyses are not limited this way.
|
||||
- [DB_SHADER_CONTROL.CONSERVATIVE_Z_EXPORT](../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp) (libSceAgcDriver) becomes `DepthLess`/`DepthGreater`. For a pixel shader that breaks the promise, the host driver decides whether its interpolated Z is depth-tested before shading; what the console does then was not measured.
|
||||
- [DB_SHADER_CONTROL.STENCIL_TEST_VAL_EXPORT_ENABLE](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) becomes `FragStencilRefEXT`, taken from the second MRTZ channel of the 32_GR and 32_ABGR formats as Mesa's AMD drivers write it ([`emit_ps_mrtz_export`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/3e32b0e1f132231b7a972c1b0b5b06c7e16cf8b9/src/amd/common/nir/ac_nir_lower_ps_late.c#L194)); the console was not measured. The host value is the test value of both faces and what `REPLACE_TEST` writes, so a stencil operation that writes all ones or the operation value throws with it, as do a stencil clear draw, the UINT16_ABGR layout, `STENCIL_OP_VAL_EXPORT_ENABLE` and a device without `VK_EXT_shader_stencil_export` (NVIDIA's driver 610.57 offers none).
|
||||
- [DB_SHADER_CONTROL.PRIMITIVE_ORDERED_PIXEL_SHADER](../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) (shader recompiler) - the host pixel interlock spans the whole shader, not only up to `s_sendmsg` ORDERED_PS_DONE.
|
||||
- [POPS collision SGPRs](../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) (shader recompiler) - the collision wave ID and intrawave collision mask read 0, so a title skips its POPS wait and runs its per-collision loop once.
|
||||
- [Image loads](../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) (shader recompiler) - an image_load reads a sampled binding, also of an image the shader stores to, so in a primitive-ordered pixel shader it misses an earlier overlapping primitive's image_store in the same draw.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ LIMITATIONS = [
|
|||
"skipped, the device lacks fragmentShaderPixelInterlock",
|
||||
"skipped, the device lacks geometryShader",
|
||||
"skipped, the device lacks shaderSubgroupClock",
|
||||
"skipped, the device lacks VK_EXT_shader_stencil_export",
|
||||
"skipped, the device does not import guest memory",
|
||||
"skipped, the device imports no host memory",
|
||||
"skipped, the device flushes f64 subnormals without DenormPreserve",
|
||||
|
|
|
|||
Loading…
Reference in New Issue