Merge pull request #6530 from milescoviello/feat/smoothing-mixed-samples

feat(agc): draw radeonsi line and polygon smoothing (SAMPLE_COVERAGE, merged mixed samples)
This commit is contained in:
Adrià authored and GitHub committed 2026-10-19 06:06:26 +02:00
commit e0b8be9dc5
17 files changed
+479 -13

No files matched your search

@@ -263,7 +263,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) {
if (x.binary.stage != y.binary.stage || x.binary.codeAddress != y.binary.codeAddress || x.userDataBase != y.userDataBase || x.firstUserSgpr != y.firstUserSgpr || x.userData != y.userData || x.snapshot != y.snapshot || x.codeOffset != y.codeOffset || x.tailContinuation != y.tailContinuation) return false;
}
if (p.targetExportPacking != q.targetExportPacking || p.dualSourceBlend != q.dualSourceBlend || p.reversedBlend != q.reversedBlend || p.alphaToCoverage != q.alphaToCoverage || p.rasterPrimitive != q.rasterPrimitive || p.ditheredAlphaToCoverage != q.ditheredAlphaToCoverage || p.quadPixelMask != q.quadPixelMask || p.quadSampleMask != q.quadSampleMask || p.iteratesSamples != q.iteratesSamples || p.positionLocation != q.positionLocation || p.sampleLocations != q.sampleLocations) return false;
if (s.samples != t.samples || s.quadSampleMask != t.quadSampleMask || s.pixelEarlyZ != t.pixelEarlyZ) return false;
if (s.samples != t.samples || s.mergedCoverage != t.mergedCoverage || s.quadSampleMask != t.quadSampleMask || s.pixelEarlyZ != t.pixelEarlyZ) return false;
return true;
}
@@ -352,6 +352,7 @@ struct VulkanDevice::State {
bool textureCompressionBC = false;
bool samplerFilterMinmax = false;
bool nonSeamlessCubeMap = false;
VkSampleCountFlags mergedCoverageSampleCounts = 0;
bool fragmentShaderPixelInterlock = false;
bool conservativeRasterization = false;
bool provokingVertexLast = false;
@@ -1440,6 +1441,27 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
}
nonSeamlessCubeFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_NON_SEAMLESS_CUBE_MAP_FEATURES_EXT};
nonSeamlessCubeFeatures.nonSeamlessCubeMap = VK_TRUE;
VkPhysicalDeviceCoverageReductionModeFeaturesNV coverageReductionFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COVERAGE_REDUCTION_MODE_FEATURES_NV};
if (hasExtension(VK_NV_FRAMEBUFFER_MIXED_SAMPLES_EXTENSION_NAME) && hasExtension(VK_NV_COVERAGE_REDUCTION_MODE_EXTENSION_NAME)) {
VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &coverageReductionFeatures};
state->InstanceFunction<PFN_vkGetPhysicalDeviceFeatures2>("vkGetPhysicalDeviceFeatures2")(selected, &features);
if (coverageReductionFeatures.coverageReductionMode == VK_TRUE) {
const auto combinations = state->InstanceFunction<PFN_vkGetPhysicalDeviceSupportedFramebufferMixedSamplesCombinationsNV>("vkGetPhysicalDeviceSupportedFramebufferMixedSamplesCombinationsNV");
std::uint32_t count = 0;
check(combinations(selected, &count, nullptr), "vkGetPhysicalDeviceSupportedFramebufferMixedSamplesCombinationsNV");
std::vector<VkFramebufferMixedSamplesCombinationNV> supported(count, {VK_STRUCTURE_TYPE_FRAMEBUFFER_MIXED_SAMPLES_COMBINATION_NV});
check(combinations(selected, &count, supported.data()), "vkGetPhysicalDeviceSupportedFramebufferMixedSamplesCombinationsNV");
for (const auto& combination : supported) {
if (combination.coverageReductionMode == VK_COVERAGE_REDUCTION_MODE_MERGE_NV && combination.colorSamples == VK_SAMPLE_COUNT_1_BIT && combination.depthStencilSamples == 0) state->mergedCoverageSampleCounts |= combination.rasterizationSamples;
}
}
if (state->mergedCoverageSampleCounts != 0) {
deviceExtensions.push_back(VK_NV_FRAMEBUFFER_MIXED_SAMPLES_EXTENSION_NAME);
deviceExtensions.push_back(VK_NV_COVERAGE_REDUCTION_MODE_EXTENSION_NAME);
}
}
coverageReductionFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COVERAGE_REDUCTION_MODE_FEATURES_NV};
coverageReductionFeatures.coverageReductionMode = VK_TRUE;
VkPhysicalDeviceGraphicsPipelineLibraryFeaturesEXT libraryFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_FEATURES_EXT};
VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamicStateFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT};
VkPhysicalDeviceDynamicRenderingFeaturesKHR renderingFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR};
@@ -1707,6 +1729,10 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
nonSeamlessCubeFeatures.pNext = const_cast<void*>(deviceInfo.pNext);
deviceInfo.pNext = &nonSeamlessCubeFeatures;
}
if (state->mergedCoverageSampleCounts != 0) {
coverageReductionFeatures.pNext = const_cast<void*>(deviceInfo.pNext);
deviceInfo.pNext = &coverageReductionFeatures;
}
byteFeatures.pNext = const_cast<void*>(deviceInfo.pNext);
if (state->fragmentShaderBarycentric) {
barycentricFeatures.pNext = byteFeatures.pNext;
@@ -3628,6 +3654,7 @@ Graphics::Context VulkanDevice::buildContext() const {
context.depthBiasClamp = state->depthBiasClamp;
context.samplerFilterMinmax = state->samplerFilterMinmax;
context.nonSeamlessCubeMap = state->nonSeamlessCubeMap;
context.mergedCoverageSampleCounts = state->mergedCoverageSampleCounts;
context.conservativeRasterization = state->conservativeRasterization;
context.meshWave32 = state->meshWave32;
context.provokingVertexLast = state->provokingVertexLast;
@@ -193,6 +193,7 @@ struct Context {
bool dualSrcBlend = false;
bool samplerFilterMinmax = false;
bool nonSeamlessCubeMap = false;
VkSampleCountFlags mergedCoverageSampleCounts = 0;
bool conservativeRasterization = false;
bool meshWave32 = false;
VkBuffer emptyBuffer = VK_NULL_HANDLE;
@@ -138,6 +138,7 @@ struct State {
bool alphaToCoverage = false;
bool ditheredAlphaToCoverage = false;
bool perSampleShading = false;
bool mergedCoverage = false;
VkConservativeRasterizationModeEXT conservativeRasterization = VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT;
VkRect2D scissor;
VkCullModeFlags cullMode;
@@ -52,6 +52,7 @@ std::uint64_t DrawRenderPassKey(const Context& context, const State& state, std:
mix(state.renderExtent.width);
mix(state.renderExtent.height);
mix(state.samples);
mix(state.mergedCoverage);
mix(state.customSampleLocations);
if (state.customSampleLocations) {
mix(state.sampleLocationGrid);
@@ -242,6 +242,8 @@ bool libraryKeys(PipelineLibraryKeys& keys, const Context& context, const State&
appendKey(keys.fragmentOutput, state.samples);
appendKey(keys.fragmentShader, state.perSampleShading);
appendKey(keys.fragmentOutput, state.perSampleShading);
appendKey(keys.fragmentShader, state.mergedCoverage);
appendKey(keys.fragmentOutput, state.mergedCoverage);
const auto sampleLocationsEnabled = ValidateSampleLocations(context, state);
appendKey(keys.fragmentShader, sampleLocationsEnabled);
appendKey(keys.fragmentOutput, sampleLocationsEnabled);
@@ -314,6 +316,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
Require(state.reversedBlend == 0 || (context.dualSrcBlend && state.blends.size() <= context.limits.maxFragmentDualSrcAttachments), "blending into a color target with a reversed component order through a second source color needs dual-source blending, which the device does not support");
Require(!state.negativeOneToOne || context.depthClipControl, "negative-one-to-one depth clipping requires VK_EXT_depth_clip_control with depthClipControl enabled");
Require(!state.perSampleShading || context.sampleRateShading, "per-sample pixel shading requires the sampleRateShading device feature");
Require(!state.mergedCoverage || (context.mergedCoverageSampleCounts & state.samples) != 0, "an overrasterized draw (a multisampled rasterizer into one-sample color targets) requires VK_NV_framebuffer_mixed_samples with merged coverage reduction");
Require(state.conservativeRasterization == VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT || context.conservativeRasterization, "conservative rasterization requires VK_EXT_conservative_rasterization with at most 1/256 pixel of overestimation and degenerate triangles rasterized");
ValidateProvokingVertex(context, state, shaders);
if (state.rectList) Require(context.tessellationShader && context.limits.maxTessellationPatchSize >= 4, "rect-list requires tessellation with four output control points");
@@ -379,7 +382,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
for (std::uint32_t index = 0; index < state.colors.size(); ++index) {
VkAttachmentDescription color{};
color.format = state.colors[index].format;
color.samples = static_cast<VkSampleCountFlagBits>(state.samples);
color.samples = state.mergedCoverage ? VK_SAMPLE_COUNT_1_BIT : static_cast<VkSampleCountFlagBits>(state.samples);
color.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
color.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
color.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
@@ -412,6 +415,11 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
VkPipelineRenderingCreateInfoKHR rendering{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR};
rendering.colorAttachmentCount = static_cast<std::uint32_t>(renderingColors.size());
rendering.pColorAttachmentFormats = renderingColors.empty() ? nullptr : renderingColors.data();
const std::vector<VkSampleCountFlagBits> mergedColorSamples(renderingColors.size(), VK_SAMPLE_COUNT_1_BIT);
VkAttachmentSampleCountInfoNV attachmentSamples{VK_STRUCTURE_TYPE_ATTACHMENT_SAMPLE_COUNT_INFO_NV};
attachmentSamples.colorAttachmentCount = static_cast<std::uint32_t>(mergedColorSamples.size());
attachmentSamples.pColorAttachmentSamples = mergedColorSamples.empty() ? nullptr : mergedColorSamples.data();
if (state.mergedCoverage) rendering.pNext = &attachmentSamples;
if (state.depth) {
rendering.depthAttachmentFormat = HostDepthFormat(context, state.depth->format);
if (HasStencil(state.depth->format)) rendering.stencilAttachmentFormat = rendering.depthAttachmentFormat;
@@ -482,6 +490,12 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
const auto grid = SampleLocationGrid(state);
sampleLocations.sampleLocationsInfo = {VK_STRUCTURE_TYPE_SAMPLE_LOCATIONS_INFO_EXT, nullptr, samples.rasterizationSamples, grid, state.samples * grid.width * grid.height, locations.data()};
if (sampleLocationsEnabled) samples.pNext = &sampleLocations;
VkPipelineCoverageReductionStateCreateInfoNV coverageReduction{VK_STRUCTURE_TYPE_PIPELINE_COVERAGE_REDUCTION_STATE_CREATE_INFO_NV};
coverageReduction.coverageReductionMode = VK_COVERAGE_REDUCTION_MODE_MERGE_NV;
if (state.mergedCoverage) {
coverageReduction.pNext = samples.pNext;
samples.pNext = &coverageReduction;
}
VkPipelineDepthStencilStateCreateInfo depthStencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
depthStencil.depthTestEnable = state.depthTest;
depthStencil.depthWriteEnable = state.depthWrite;
@@ -854,6 +868,7 @@ void pipelineKey(std::vector<std::byte>& key, const Context& context, const Stat
for (const auto value : state.blendConstants) append(key, value);
append(key, state.samples);
append(key, state.perSampleShading);
append(key, state.mergedCoverage);
append(key, state.customSampleLocations);
if (state.customSampleLocations) {
append(key, state.sampleLocationGrid);
@@ -215,6 +215,7 @@ void encodeState(Writer& writer, const State& state) {
writer.Bool(state.customSampleLocations);
writer.Bool(state.sampleLocationGrid);
writer.Bool(state.perSampleShading);
writer.Bool(state.mergedCoverage);
for (const auto mask : state.quadSampleMask) writer.Raw(mask);
writer.U32(state.depthRemap.mode);
}
@@ -274,6 +275,7 @@ void decodeState(Reader& reader, State& state) {
state.customSampleLocations = reader.Bool();
state.sampleLocationGrid = reader.Bool();
state.perSampleShading = reader.Bool();
state.mergedCoverage = reader.Bool();
for (auto& mask : state.quadSampleMask) mask = reader.Raw<std::uint16_t>();
state.depthRemap.mode = reader.U32();
}
@@ -1038,6 +1040,7 @@ State CanonicalPipelineState(const State& state) {
canonical.customSampleLocations = state.customSampleLocations;
canonical.sampleLocationGrid = state.sampleLocationGrid;
canonical.perSampleShading = state.perSampleShading;
canonical.mergedCoverage = state.mergedCoverage;
canonical.quadSampleMask = state.quadSampleMask;
canonical.depthRemap.mode = state.depthRemap.mode;
return canonical;
@@ -118,7 +118,7 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con
constexpr std::uint32_t knownMask = PixelInputBit(PixelInput::PerspectiveSample) | PixelInputBit(PixelInput::PerspectiveCenter) | PixelInputBit(PixelInput::PerspectiveCentroid) |
PixelInputBit(PixelInput::LinearSample) | PixelInputBit(PixelInput::LinearCenter) | PixelInputBit(PixelInput::LinearCentroid) |
PixelInputBit(PixelInput::PositionX) | PixelInputBit(PixelInput::PositionY) | PixelInputBit(PixelInput::PositionZ) | PixelInputBit(PixelInput::PositionW) |
PixelInputBit(PixelInput::FrontFace) | PixelInputBit(PixelInput::Ancillary) | PixelInputBit(PixelInput::LineStipple) | PixelInputBit(PixelInput::PositionFixedPoint);
PixelInputBit(PixelInput::FrontFace) | PixelInputBit(PixelInput::Ancillary) | PixelInputBit(PixelInput::SampleCoverage) | PixelInputBit(PixelInput::LineStipple) | PixelInputBit(PixelInput::PositionFixedPoint);
if ((activeInputs & ~knownMask) != 0) {
char message[128];
std::snprintf(message, sizeof(message), "AGC graphics: unsupported SPI_PS_INPUT_ENA/ADDR bit combination (ena 0x%x addr 0x%x)", ena, addr);
@@ -176,6 +176,8 @@ bool ditheredAlphaLocationsSupported(const Registers& cx) {
constexpr std::uint32_t ScanModeMask = ~0x22u;
constexpr std::uint32_t ScanControlMask = ~0x76033fffu;
constexpr std::uint32_t PixelShaderIteratesSamples = 0x10000u;
constexpr std::uint32_t PixelSampleCoverageInput = 0x4000u;
constexpr std::uint32_t PixelSampleRateInputs = 0x2011u;
constexpr std::uint32_t ScreenOffsetMask = ~0x01ff01ffu;
// Bits 26/27 (ZCLIP_NEAR/FAR_DISABLE) become depth clamping; bit 19 selects the [0, 1] clip space.
constexpr std::uint32_t ClipControlMask = ~(0x80000u | 0x01000000u | 0x0c000000u);
@@ -461,6 +463,21 @@ constexpr std::uint32_t CoveredCentroidIsCenter = 1u << 29u;
constexpr std::uint32_t SampleZeroRefusedInputs = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveCentroid) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::LinearCentroid) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::SampleCoverage);
constexpr std::uint32_t CentroidInputs = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveCentroid) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::LinearCentroid);
bool overrasterized(const Registers& cx, std::uint32_t samples) {
const auto eqaa = find(cx, 0x201);
const auto mode = find(cx, 0x292);
return samples > 1u && eqaa != cx.end() && mode != cx.end() && (mode->second & 1u) != 0 && ((eqaa->second >> 24u) & 7u) == static_cast<std::uint32_t>(std::countr_zero(samples));
}
bool sampleCoverageSupported(const QueueState& queue, std::uint32_t samples, bool perSampleShading) {
if (samples == 1u || PixelProgramSkipped(queue)) return true;
const auto ena = find(queue.context, 0x1b3);
const auto addr = find(queue.context, 0x1b4);
if (ena == queue.context.end() || addr == queue.context.end()) return true;
const auto inputs = ena->second & addr->second;
return (inputs & PixelSampleCoverageInput) == 0u || (!perSampleShading && (inputs & PixelSampleRateInputs) == 0u);
}
bool coveredCentroidSupported(const QueueState& queue) {
const auto config = find(queue.context, 0x2f8);
if (config == queue.context.end() || (config->second & CoveredCentroidIsCenter) == 0 || PixelProgramSkipped(queue)) return true;
@@ -1183,6 +1200,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
}
zero(cx, 0x293, ScanControlMask, "primitive discard or out-of-order rasterization");
result.perSampleShading = result.samples > 1 && (read(cx, 0x293) & PixelShaderIteratesSamples) != 0;
Require(sampleCoverageSupported(queue, result.samples, result.perSampleShading), "SAMPLE_COVERAGE in a multisampled pixel shader that runs per sample, or reads sample-rate or ancillary inputs, is unsupported (the input holds the whole pixel's coverage, gl_SampleMaskIn only the invocation's samples)");
if (depthKeepsSampleZero && result.samples > 1) {
Require(rasterizesSampleZeroOnly(cx, result), "the depth target's sample count differs from the rasterizer's");
rasterizeSampleZero(result);
@@ -1267,8 +1285,10 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
result.customSampleLocations = true;
result.sampleLocations.fill(0);
}
if (color.samples == 1 && result.colors.empty() && !result.depth && rasterizesSampleZeroOnly(cx, result)) rasterizeSampleZero(result);
Require(color.samples == result.samples, "a color target's sample count differs from the rasterizer's");
const bool merged = color.samples == 1u && !result.depth && !result.perSampleShading && (result.colors.empty() || result.mergedCoverage) && overrasterized(cx, result.samples);
if (!merged && color.samples == 1 && result.colors.empty() && !result.depth && rasterizesSampleZeroOnly(cx, result)) rasterizeSampleZero(result);
Require((color.samples == result.samples && !result.mergedCoverage) || merged, "a color target's sample count differs from the rasterizer's");
result.mergedCoverage = merged;
APS5_LOG_OUT_DEBUG("Color %u address=0x%llx extent=%ux%u bytes=%llu VkFormat=%u", slot, static_cast<unsigned long long>(color.address), color.extent.width, color.extent.height, static_cast<unsigned long long>(color.bytes), static_cast<unsigned>(color.format));
if (result.colors.empty()) {
result.renderExtent = color.extent;
@@ -1257,6 +1257,36 @@ std::vector<spv::BuiltIn> pixelBuiltinsRead(std::uint32_t ena, std::uint32_t add
return read;
}
std::size_t sampleMaskInputs(std::uint32_t inputs, std::uint16_t quadMask) {
auto queue = makeState();
queue.context[0x1b3] = inputs;
queue.context[0x1b4] = inputs;
auto pixel = AgcDriver::Graphics::DecodePixelStageInfo(queue.context, IdentityExports);
pixel.quadSampleMask[0] = quadMask;
const auto source = ShaderRecompiler::PixelInputVgpr(inputs, ShaderRecompiler::PixelInput::SampleCoverage);
const std::array<std::uint32_t, 3> code{0xf800180fu, source * 0x01010101u, 0xbf810000u};
ShaderRecompiler::RecompileRequest request{};
request.shader = {ShaderRecompiler::ShaderStage::Fragment, 0x30000u, code, 0, {}};
request.context.waveSize = 64;
request.context.pixel = pixel;
request.target.vulkanVersion = 0x00401000u;
request.target.spirvVersion = 0x00010300u;
request.target.subgroupSize = 64;
request.target.fragmentShaderBarycentricEnabled = true;
request.layout.pushConstantSizeBytes = 128;
request.useCache = false;
const auto result = ShaderRecompiler::Recompile(request);
const auto& words = result.spirv.Words();
std::map<std::uint32_t, std::uint32_t> storage;
std::vector<std::uint32_t> sampleMasks;
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::OpVariable) storage[words[at + 2]] = words[at + 3];
if (op == spv::OpDecorate && words[at + 2] == spv::DecorationBuiltIn && words[at + 3] == spv::BuiltInSampleMask) sampleMasks.push_back(words[at + 1]);
}
return static_cast<std::size_t>(std::ranges::count_if(sampleMasks, [&](std::uint32_t id) { return storage[id] == spv::StorageClassInput; }));
}
bool readsBuiltin(const std::vector<spv::BuiltIn>& read, spv::BuiltIn builtin) {
return std::find(read.begin(), read.end(), builtin) != read.end();
}
@@ -1508,9 +1538,11 @@ void PixelInputLayoutTests() {
queue.context[0x1b8] = 0u;
pixel = decode(0x506u, 0x7afu);
Require(pixel.inputAddr == 0x7afu && !pixel.posY && pixel.posZ && PixelInputVgpr(pixel.inputAddr, PixelInput::PerspectiveCentroid) == 4u && PixelInputVgpr(pixel.inputAddr, PixelInput::PositionX) == 12u && PixelInputVgpr(pixel.inputAddr, PixelInput::PositionZ) == 14u, "ADDR-only inputs did not reserve their VGPRs");
for (const auto bit : {0x8u, 0x4000u}) {
expectFailure([&] { static_cast<void>(decode(0x2u | bit, 0x2u | bit)); }, "unsupported SPI_PS_INPUT_ENA/ADDR");
}
expectFailure([&] { static_cast<void>(decode(0xau, 0xau)); }, "unsupported SPI_PS_INPUT_ENA/ADDR");
pixel = decode(0x4002u, 0x4002u);
Require(pixel.inputAddr == 0x4002u && PixelInputVgpr(pixel.inputAddr, PixelInput::SampleCoverage) == 2u, "SAMPLE_COVERAGE did not take its VGPR after the perspective center");
Require(readsBuiltin(pixelBuiltinsRead(0x4002u, 0x4002u, 2u), spv::BuiltInSampleMask), "SAMPLE_COVERAGE is not read from gl_SampleMaskIn");
Require(sampleMaskInputs(0x4002u, 0xffffu) == 1u && sampleMaskInputs(0x4002u, 0x0003u) == 1u, "SAMPLE_COVERAGE beside a partial quad sample mask declared gl_SampleMaskIn more than once");
pixel = decode(0x80u, 0x80u);
Require(pixel.inputAddr == 0x80u && PixelInputVgpr(pixel.inputAddr, PixelInput::LineStipple) == 0u, "a pixel shader with only LINE_STIPPLE_TEX enabled was rejected");
pixel = decode(0x8082u, 0x8082u);
@@ -3124,7 +3156,43 @@ void multisampleTests() {
queue.context[0x293] = 0x10000u;
Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "PS_ITER_SAMPLE on a 2x target was refused");
Require(DecodeState(queue).perSampleShading, "PS_ITER_SAMPLE on a 2x target did not request sample shading");
queue.context[0x1b3] = 0x4002u;
queue.context[0x1b4] = 0x4002u;
expectFailure([&] { DecodeState(queue); }, "SAMPLE_COVERAGE in a multisampled pixel shader that runs per sample");
queue.context[0x293] = 0;
Require(AgcDriver::Graphics::DrawRejection(queue, false).empty() && !DecodeState(queue).perSampleShading, "SAMPLE_COVERAGE with center interpolation on a 2x target was refused");
for (const auto sampleRate : {0x1u, 0x10u, 0x2000u}) {
queue.context[0x1b3] = 0x4002u | sampleRate;
queue.context[0x1b4] = 0x4002u | sampleRate;
expectFailure([&] { DecodeState(queue); }, "SAMPLE_COVERAGE in a multisampled pixel shader that runs per sample");
queue.context[0x1b4] = 0x4002u;
Require(DecodeState(queue).samples == 2, "a sample-rate input enabled but not addressed beside SAMPLE_COVERAGE decoded differently");
}
queue.context[0x1b3] = 2;
queue.context[0x1b4] = 2;
{
auto smooth = multisampled();
smooth.context[0x2f8] = 0x0020c002u;
smooth.context[0x31d] = 0u;
smooth.context[0x201] = 0x02130000u;
smooth.context[0x1b3] = 0x4002u;
smooth.context[0x1b4] = 0x4002u;
const auto merged = DecodeState(smooth);
Require(merged.samples == 4 && merged.mergedCoverage && merged.colors.size() == 1 && merged.colors[0].samples == 1 && !merged.perSampleShading, "a radeonsi smoothing draw (4 raster samples, OVERRASTERIZATION_AMOUNT 2, one-sample target) did not decode as merged coverage");
smooth.context[0x201] = 0x01130000u;
expectFailure([&] { DecodeState(smooth); }, "a color target's sample count differs from the rasterizer's");
smooth.context[0x201] = 0x02130000u;
smooth.context[0x293] = 0x10000u;
expectFailure([&] { DecodeState(smooth); }, "SAMPLE_COVERAGE in a multisampled pixel shader");
smooth.context[0x1b3] = 2u;
smooth.context[0x1b4] = 2u;
const auto iteratedSmooth = DecodeState(smooth);
Require(iteratedSmooth.samples == 1 && !iteratedSmooth.mergedCoverage && !iteratedSmooth.perSampleShading, "an overrasterized draw that runs per sample into a one-sample target did not rasterize sample 0 alone");
smooth.context[0x293] = 0u;
smooth.context[0x292] = 2u;
const auto unmerged = DecodeState(smooth);
Require(unmerged.samples == 1 && !unmerged.mergedCoverage, "an overrasterized draw into a one-sample target without MSAA_ENABLE did not rasterize as one sample");
}
queue.context[0x302] = 0x44cc;
state = DecodeState(queue);
Require(state.customSampleLocations && state.sampleLocationGrid, "a standard 2x pattern with its samples swapped in one pixel did not decode as a grid");
@@ -3194,7 +3262,7 @@ void multisampleTests() {
Require(state.samples == 1 && !state.perSampleShading, "PS_ITER_SAMPLE with sample and position inputs (PPSA27836) did not rasterize sample 0 alone");
state = DecodeState(withInputs(iterated, {ShaderRecompiler::PixelInput::PerspectiveCentroid, ShaderRecompiler::PixelInput::LinearCentroid}));
Require(state.samples == 1, "PS_ITER_SAMPLE with centroid inputs did not rasterize sample 0 alone");
expectFailure([&] { DecodeState(withInputs(iterated, {ShaderRecompiler::PixelInput::SampleCoverage})); }, "sample count differs from the rasterizer's");
expectFailure([&] { DecodeState(withInputs(iterated, {ShaderRecompiler::PixelInput::SampleCoverage})); }, "SAMPLE_COVERAGE in a multisampled pixel shader that runs per sample");
state = DecodeState(withInputs(iterated, {ShaderRecompiler::PixelInput::Ancillary, ShaderRecompiler::PixelInput::PositionX}));
Require(state.samples == 1 && !state.perSampleShading, "PS_ITER_SAMPLE with sample-index and position inputs did not rasterize sample 0 alone");
auto iteratedStencil = withInputs(stenciled, {});
@@ -9295,6 +9363,15 @@ void pipelineJournalTests() {
BuildPipelineStoreKey(masked, context, maskState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL);
BuildPipelineStoreKey(maskReplayed, context, maskDecoded.state, maskDecoded.vertexInput, layouts.View(), shaders, maskDecoded.attachmentLayout);
Require(masked != fourSamples && masked == maskReplayed, "the decoded sample mask state keys another pipeline");
auto mergedState = fourSampleState;
mergedState.mergedCoverage = true;
GraphicsPipelineRecipe mergedDecoded;
Require(DecodeGraphicsPipelineRecipe(EncodeGraphicsPipelineRecipe(MakeGraphicsPipelineRecipe(mergedState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL)), mergedDecoded), "the merged coverage recipe does not decode");
Require(mergedDecoded.state.mergedCoverage, "the recipe dropped merged coverage");
std::vector<std::byte> mergedKey, mergedReplayed;
BuildPipelineStoreKey(mergedKey, context, mergedState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL);
BuildPipelineStoreKey(mergedReplayed, context, mergedDecoded.state, mergedDecoded.vertexInput, layouts.View(), shaders, mergedDecoded.attachmentLayout);
Require(mergedKey != fourSamples && mergedKey == mergedReplayed, "the decoded merged coverage state keys another pipeline");
auto movedState = state;
movedState.viewport.x = 5;
movedState.colors[0].address = 0x9000;
@@ -0,0 +1,294 @@
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libSceAgcDriver/Graphics/include/State.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include "prx/libc/include/GuestWriteWatch.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <sys/mman.h>
#endif
#include <algorithm>
#include <array>
#include <bit>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#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::size_t BlockBytes = 65536;
constexpr std::uint32_t Rgba32FloatInfo = 0x8738u;
constexpr std::uint32_t LinearAttrib3 = 0x09000000u;
constexpr std::uint32_t Float32AbgrExport = 9;
alignas(256) constexpr std::array<std::uint32_t, 6> VertexCode{
0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000,
};
constexpr std::array<std::array<float, 4>, 3> Triangle{{
{-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f}
}};
constexpr std::array<std::array<float, 4>, 3> HalfTriangle{{
{-1.0f, -1.0f, 0.5f, 1.0f}, {1.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 1.0f, 0.5f, 1.0f}
}};
alignas(256) std::array<std::array<std::uint32_t, 16>, 4> Programs{};
std::size_t programCount = 0;
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};
}
std::span<const std::uint32_t> Program(std::uint32_t coverageVgpr) {
Require(programCount < Programs.size(), "too many test programs");
auto& code = Programs[programCount++];
std::size_t at = 0;
code[at++] = 0x7e000c00u | (20u << 17u) | (256u + coverageVgpr);
code[at++] = 0x7e000280u | (21u << 17u);
code[at++] = 0x7e000280u | (22u << 17u);
code[at++] = 0x7e0002f2u | (23u << 17u);
code[at++] = 0xf800180fu;
code[at++] = 0x17161514u;
code[at++] = 0xbf810000u;
return std::span<const std::uint32_t>(code.data(), at);
}
struct Block {
explicit Block(std::size_t bytes) : bytes(bytes), watched(AgcDriver::GuestMemory::WriteWatched()) {
#ifdef _WIN32
if (watched) {
data = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes));
GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes);
} else {
data = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
}
#else
if (watched) {
void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Require(raw != MAP_FAILED, "cannot map the color target");
const auto begin = reinterpret_cast<std::uintptr_t>(raw);
const auto aligned = (begin + BlockBytes - 1) & ~(static_cast<std::uintptr_t>(BlockBytes) - 1);
if (aligned != begin) munmap(raw, aligned - begin);
if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast<void*>(aligned + bytes), begin + BlockBytes - aligned);
data = reinterpret_cast<std::uint8_t*>(aligned);
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes);
} else {
data = static_cast<std::uint8_t*>(std::aligned_alloc(BlockBytes, bytes));
}
#endif
Require(data != nullptr, "cannot allocate the color target");
Require(!watched || AgcDriver::GuestMemory::Watched(Address(), bytes), "the color target is not write-watched");
GuestAllocations::Mutation mutation;
mutation.Add(data, bytes, true, true, true);
}
~Block() {
AgcDriver::Graphics::StorageTexture::FlushPending(Address(), bytes, nullptr, "test release");
{
GuestAllocations::Mutation mutation;
mutation.Remove(data);
}
#ifdef _WIN32
if (watched) {
GuestArena::GuestArenaReset_nid_postfix(data, bytes);
GuestArena::GuestArenaRelease_nid_postfix(data, bytes);
} else {
VirtualFree(data, 0, MEM_RELEASE);
}
#else
if (watched) {
munmap(data, bytes);
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes);
} else {
std::free(data);
}
#endif
}
Block(const Block&) = delete;
Block& operator=(const Block&) = delete;
std::uint64_t Address() const { return reinterpret_cast<std::uintptr_t>(data); }
std::size_t bytes;
bool watched;
std::uint8_t* data = nullptr;
};
AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t info) {
AgcDriver::Registers cx;
cx[0x318] = static_cast<std::uint32_t>(block.Address() >> 8u);
cx[0x31b] = 0;
cx[0x31c] = info;
cx[0x31d] = 0;
cx[0x323] = 0;
cx[0x324] = 0;
cx[0x3b0] = ((Width - 1u) << 14u) | (Height - 1u);
cx[0x3b8] = LinearAttrib3;
cx[0x390] = static_cast<std::uint32_t>(block.Address() >> 40u);
auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0);
color.exportIndex = 0;
return color;
}
void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, std::uint32_t inputAddr, std::span<const std::uint32_t> pixelCode, std::span<const std::array<float, 4>, 3> triangle = Triangle, std::uint32_t rasterSamples = 1) {
const auto target = device.Target();
constexpr std::uint32_t waveSize = 64;
std::vector<std::uint32_t> vertexUserData(4, 0u);
const auto vertexBuffer = BufferDescriptor(triangle.data(), 16u, static_cast<std::uint32_t>(triangle.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());
ShaderRecompiler::ShaderPixelStageInfo pixel{};
pixel.inputAddr = inputAddr;
pixel.ancillary = (inputAddr & ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary)) != 0u;
pixel.hasPerspectiveCenterVgpr = (inputAddr & ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveCenter)) != 0u;
pixel.targetOutputMode.at(0) = Float32AbgrExport;
pixel.targetExportMapping.fill(0xe4u);
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, pixel, 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)}
}};
VkPipelineColorBlendAttachmentState blend{};
blend.blendEnable = VK_FALSE;
blend.colorWriteMask = 0xf;
AgcDriver::Graphics::State state{};
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt};
state.colors = {color};
state.color = 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.blends = {blend};
state.blend = blend;
state.blendConstants = {};
state.samples = rasterSamples;
state.mergedCoverage = rasterSamples > 1;
const AgcDriver::Pm4::DrawParameters draw{0, static_cast<std::uint32_t>(triangle.size()), 0, 1, 0, false};
device.Draw(state, draw, shaders);
}
std::vector<std::uint8_t> ReadBack(AgcDriver::VulkanDevice& device, std::uint64_t address, std::size_t bytes) {
AgcDriver::Graphics::StorageTexture::FlushPending(address, bytes, nullptr, "test read-back");
device.WaitIdle();
std::vector<std::uint8_t> stored(bytes);
AgcDriver::GuestMemory::Read(address, std::as_writable_bytes(std::span(stored)), 1);
return stored;
}
void Expect(AgcDriver::VulkanDevice& device, std::uint32_t inputAddr, std::uint32_t coverageVgpr, const std::string& what) {
constexpr std::size_t bytes = std::size_t{Width} * Height * 16u;
Require(AgcDriver::Graphics::ColorTargetLayout(Width, Height, AgcDriver::Graphics::ColorTileMode::Linear, 16).Bytes() == bytes, what + ": the linear target is padded");
Block block(bytes);
std::fill(block.data, block.data + bytes, std::uint8_t{0xff});
const auto color = DecodeTarget(block, Rgba32FloatInfo);
Require(color.format == VK_FORMAT_R32G32B32A32_SFLOAT && color.elementBytes == 16 && color.bytes == bytes, what + ": the target decoded to the wrong format");
Draw(device, color, inputAddr, Program(coverageVgpr));
const auto stored = ReadBack(device, block.Address(), bytes);
for (std::uint32_t y = 0; y < Height; ++y) {
for (std::uint32_t x = 0; x < Width; ++x) {
std::array<float, 4> texel{};
std::memcpy(texel.data(), stored.data() + (static_cast<std::size_t>(y) * Width + x) * 16u, sizeof(texel));
const std::array<float, 4> expected{1.0f, 0.0f, 0.0f, 1.0f};
Require(texel == expected, what + ": pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") holds (" + std::to_string(texel[0]) + ", " + std::to_string(texel[1]) + ", " + std::to_string(texel[2]) + ", " + std::to_string(texel[3]) + ")");
}
}
}
bool ExpectMerged(AgcDriver::VulkanDevice& device) {
constexpr std::size_t bytes = std::size_t{Width} * Height * 16u;
Block block(bytes);
std::fill(block.data, block.data + bytes, std::uint8_t{0xff});
const auto color = DecodeTarget(block, Rgba32FloatInfo);
try {
Draw(device, color, ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::SampleCoverage), Program(0u), HalfTriangle, 4u);
} catch (const std::exception& error) {
if (std::string_view(error.what()).find("VK_NV_framebuffer_mixed_samples") == std::string_view::npos) throw;
return false;
}
const auto stored = ReadBack(device, block.Address(), bytes);
std::uint32_t full = 0;
std::uint32_t partial = 0;
std::uint32_t untouched = 0;
for (std::uint32_t y = 0; y < Height; ++y) {
for (std::uint32_t x = 0; x < Width; ++x) {
const auto* texel = stored.data() + (static_cast<std::size_t>(y) * Width + x) * 16u;
if (std::all_of(texel, texel + 16, [](std::uint8_t byte) { return byte == 0xffu; })) {
++untouched;
continue;
}
std::array<float, 4> value{};
std::memcpy(value.data(), texel, sizeof(value));
const auto mask = static_cast<std::uint32_t>(value[0]);
Require(value[0] == static_cast<float>(mask) && mask >= 1u && mask <= 15u && value[1] == 0.0f && value[2] == 0.0f && value[3] == 1.0f, "merged coverage: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") holds (" + std::to_string(value[0]) + ", " + std::to_string(value[1]) + ", " + std::to_string(value[2]) + ", " + std::to_string(value[3]) + ")");
if (mask == 15u) ++full;
else ++partial;
}
}
Require(full != 0 && partial != 0 && untouched != 0, "merged coverage: a half-covering triangle wrote " + std::to_string(full) + " fully covered, " + std::to_string(partial) + " partly covered and left " + std::to_string(untouched) + " pixels");
return true;
}
}
int main() {
try {
using ShaderRecompiler::PixelInput;
using ShaderRecompiler::PixelInputBit;
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
Expect(*device, PixelInputBit(PixelInput::SampleCoverage), 0u, "SAMPLE_COVERAGE alone");
Expect(*device, PixelInputBit(PixelInput::PerspectiveCenter) | PixelInputBit(PixelInput::SampleCoverage), 2u, "SAMPLE_COVERAGE after the perspective center");
Expect(*device, PixelInputBit(PixelInput::Ancillary) | PixelInputBit(PixelInput::SampleCoverage), 1u, "SAMPLE_COVERAGE after ANCILLARY");
if (!ExpectMerged(*device)) std::puts("merged coverage not tested: the device lacks VK_NV_framebuffer_mixed_samples with merged coverage reduction");
std::puts("pixel sample coverage tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
@@ -53,6 +53,7 @@ enum class StageInputKind {
BaryCoordNoPerspectiveCenter,
BaryCoordSmoothSample,
BaryCoordNoPerspectiveSample,
SampleMask,
};
constexpr StageInputKind BarycentricInputOf(StageInputKind kind) {
@@ -143,6 +143,7 @@ void ValidateValueReferences(const IrProgram& program, ShaderStageInputInfo inpu
}
case StageInputKind::Layer:
case StageInputKind::SampleId:
case StageInputKind::SampleMask:
case StageInputKind::RasterPrimitive:
case StageInputKind::LayerExported:
if (program.Resources().stage != IrShaderStage::Pixel || component != 0u) {
@@ -379,6 +380,9 @@ void CollectBuiltinInputs(const IrProgram& program, ShaderInfo& info) {
case StageInputKind::SampleId:
AddInput(info, kind, 0, 1, "gl_SampleID");
break;
case StageInputKind::SampleMask:
AddInput(info, kind, 0, 1, "gl_SampleMaskIn");
break;
case StageInputKind::BaryCoordSmooth:
case StageInputKind::BaryCoordSmoothCenter:
case StageInputKind::BaryCoordSmoothSample:
@@ -77,6 +77,7 @@ std::uint32_t BuiltInForInput(StageInputKind kind) {
case StageInputKind::HelperInvocation: return spv::BuiltInHelperInvocation;
case StageInputKind::Layer: return spv::BuiltInLayer;
case StageInputKind::SampleId: return spv::BuiltInSampleId;
case StageInputKind::SampleMask: return spv::BuiltInSampleMask;
case StageInputKind::BaryCoordSmooth: return spv::BuiltInBaryCoordKHR;
case StageInputKind::BaryCoordNoPerspective: return spv::BuiltInBaryCoordNoPerspKHR;
case StageInputKind::WorkgroupId: return spv::BuiltInWorkgroupId;
@@ -241,6 +242,9 @@ void DefineInputs(SpirvEmitterState& state) {
case StageInputKind::HelperInvocation:
type = TypeBool(state);
break;
case StageInputKind::SampleMask:
type = SampleMaskArrayType(state);
break;
case StageInputKind::Parameter:
if (state.program.Resources().stage == IrShaderStage::Vertex || state.program.Resources().stage == IrShaderStage::Local) {
type = VertexParameterScalarType(state, VertexParameterScalarKind(state, input.location));
@@ -258,7 +262,7 @@ void DefineInputs(SpirvEmitterState& state) {
break;
}
input.variableId = DefineInterfaceVariable(state, type, spv::StorageClassInput, input.debugName.c_str());
if (input.kind == StageInputKind::Layer || input.kind == StageInputKind::SampleId) {
if (input.kind == StageInputKind::Layer || input.kind == StageInputKind::SampleId || input.kind == StageInputKind::SampleMask) {
state.module.AddAnnotation(spv::OpDecorate, input.variableId, spv::DecorationFlat);
}
if (input.kind == StageInputKind::Parameter) {
@@ -303,9 +307,14 @@ void DefineInputs(SpirvEmitterState& state) {
}
}
if (state.program.Resources().stage == IrShaderStage::Pixel && state.inputInfo.pixel != nullptr && state.inputInfo.pixel->QuadSampleMaskPartial()) {
state.sampleMaskInputVariable = DefineInterfaceVariable(state, SampleMaskArrayType(state), spv::StorageClassInput, "gl_SampleMaskIn");
state.module.AddAnnotation(spv::OpDecorate, state.sampleMaskInputVariable, spv::DecorationBuiltIn, spv::BuiltInSampleMask);
state.module.AddAnnotation(spv::OpDecorate, state.sampleMaskInputVariable, spv::DecorationFlat);
const auto read = std::find_if(state.inputs.begin(), state.inputs.end(), [](const SpirvInputBinding& input) { return input.kind == StageInputKind::SampleMask && input.variableId != 0u; });
if (read != state.inputs.end()) {
state.sampleMaskInputVariable = read->variableId;
} else {
state.sampleMaskInputVariable = DefineInterfaceVariable(state, SampleMaskArrayType(state), spv::StorageClassInput, "gl_SampleMaskIn");
state.module.AddAnnotation(spv::OpDecorate, state.sampleMaskInputVariable, spv::DecorationBuiltIn, spv::BuiltInSampleMask);
state.module.AddAnnotation(spv::OpDecorate, state.sampleMaskInputVariable, spv::DecorationFlat);
}
}
}
@@ -155,6 +155,15 @@ std::uint32_t EmitBuiltinU32(SpirvEmitterState& state, StageInputKind kind, std:
state.module.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
return bits;
}
if (kind == StageInputKind::SampleMask) {
const auto pointer = state.module.AllocateId();
const auto value = state.module.AllocateId();
const auto bits = state.module.AllocateId();
state.module.AddFunction(spv::OpAccessChain, TypePointer(state, spv::StorageClassInput, TypeI32(state)), pointer, variable, ConstantU32(state, 0u));
state.module.AddFunction(spv::OpLoad, TypeI32(state), value, pointer);
state.module.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
return bits;
}
if (kind == StageInputKind::FragCoord || kind == StageInputKind::TessCoord) {
const auto pointer = state.module.AllocateId();
const auto value = state.module.AllocateId();
@@ -409,6 +409,9 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO
if (loaded(PixelInput::Ancillary)) {
setVector(static_cast<VectorReg>(vgpr(PixelInput::Ancillary)), builtin(StageInputKind::PackedAncillary));
}
if (loaded(PixelInput::SampleCoverage)) {
setVector(static_cast<VectorReg>(vgpr(PixelInput::SampleCoverage)), builtin(StageInputKind::SampleMask));
}
if (loaded(PixelInput::LineStipple)) {
setVector(static_cast<VectorReg>(vgpr(PixelInput::LineStipple)), entryIr.Constant(0u));
}
@@ -123,6 +123,7 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues
place(PixelInput::PositionW, pixel.posW);
place(PixelInput::FrontFace, pixel.frontFace);
place(PixelInput::Ancillary, pixel.ancillary);
place(PixelInput::SampleCoverage, (pixel.inputAddr & PixelInputBit(PixelInput::SampleCoverage)) != 0u);
place(PixelInput::LineStipple, (pixel.inputAddr & PixelInputBit(PixelInput::LineStipple)) != 0u);
place(PixelInput::PositionFixedPoint, (pixel.inputAddr & PixelInputBit(PixelInput::PositionFixedPoint)) != 0u);
for (std::uint32_t i = 0; i < 8; ++i) {