mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
feat(agc): single-sample sample masks as a per-quad-pixel discard
With one sample, PA_SC_AA_MASK leaves one coverage bit per pixel of the 2x2 quad, so a draw can cover only some pixels of every quad (GTA V renders a target one quarter of the pixels at a time this way). The pixel stage info carries that 4-bit mask; a fragment shader whose mask is not full starts by killing the invocation unless its gl_FragCoord quad position is covered, and runs without early fragment tests. The mask is part of the recompile cache key and the serialized request (version 24). Sample masks of multisampled draws stay refused.
This commit is contained in:
1 parent
ab9a476840
commit
72518f64fb
13 files changed
+198
-13
No files matched your search
@@ -146,6 +146,9 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con
|
||||
const bool stencilRefExportEnable = ((shaderControl >> 1u) & 0x1u) != 0;
|
||||
const bool depthBeforeShader = ((shaderControl >> 12u) & 0x1u) != 0;
|
||||
const auto zOrder = (shaderControl >> 4u) & 0x3u;
|
||||
const auto aaMask = read(context, 0x30e, RegisterBank::Context);
|
||||
const auto aaMaskBottom = read(context, 0x30f, RegisterBank::Context);
|
||||
const auto quadPixelMask = static_cast<std::uint8_t>((aaMask & 1u) | (((aaMask >> 16u) & 1u) << 1u) | ((aaMaskBottom & 1u) << 2u) | (((aaMaskBottom >> 16u) & 1u) << 3u));
|
||||
const auto loaded = [&](PixelInput input) { return (activeInputs & PixelInputBit(input)) != 0; };
|
||||
return ShaderRecompiler::ShaderPixelStageInfo{
|
||||
.interpolatorCount = inputNum,
|
||||
@@ -173,7 +176,8 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con
|
||||
.orderedPixelShader = ((shaderControl >> 16u) & 0x1u) != 0,
|
||||
.targetOutputMode = targetOutputMode,
|
||||
.targetExportMapping = nullProgram ? std::array<std::uint8_t, 8>{} : exportMappings,
|
||||
.stencilRefExportEnable = stencilRefExportEnable
|
||||
.stencilRefExportEnable = stencilRefExportEnable,
|
||||
.quadPixelMask = quadPixelMask
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -816,7 +816,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
|
||||
Require(read(cx, 0x83) == 0xffffu, "clip rectangles are unsupported");
|
||||
Require((read(cx, 0x8c) & 0xfu) == 0xau, "nonstandard triangle edge rules are unsupported");
|
||||
Require(read(cx, 0x2f9) == 0x2du, "nonstandard pixel center or vertex quantization is unsupported");
|
||||
Require(read(cx, 0x30e) == 0xffffffffu && read(cx, 0x30f) == 0xffffffffu, "sample masks are unsupported");
|
||||
Require((read(cx, 0x2f8) & 7u) == 0 || (read(cx, 0x30e) == 0xffffffffu && read(cx, 0x30f) == 0xffffffffu), "sample masks of multisampled draws are unsupported");
|
||||
const auto viewportControl = read(cx, 0x206);
|
||||
if (viewportControl != 0x43fu) throw std::runtime_error(vteMessage(viewportControl));
|
||||
zero(cx, 0x204, ClipControlMask, "unsupported PA_CL_CLIP_CNTL flags");
|
||||
@@ -1391,7 +1391,7 @@ std::string DrawRejection(const QueueState& queue, bool indexed) {
|
||||
if (value(cx, 0x2f9, word) && word != 0x2du) return require(false, "nonstandard pixel center or vertex quantization is unsupported");
|
||||
if (value(cx, 0x313, word) && (word & ~0x001f6000u) != 0 && word != 0x6001u) return conservativeMessage(word);
|
||||
std::uint32_t other = 0;
|
||||
if (value(cx, 0x30e, word) && value(cx, 0x30f, other) && (word != 0xffffffffu || other != 0xffffffffu)) return require(false, "sample masks are unsupported");
|
||||
if (value(cx, 0x2f8, word) && (word & 7u) != 0 && value(cx, 0x30e, word) && value(cx, 0x30f, other) && (word != 0xffffffffu || other != 0xffffffffu)) return require(false, "sample masks of multisampled draws are unsupported");
|
||||
if (value(cx, 0x206, word) && word != 0x43fu) return vteMessage(word);
|
||||
if (auto reason = nonzero(cx, 0x204, ClipControlMask, "unsupported PA_CL_CLIP_CNTL flags"); !reason.empty()) return reason;
|
||||
std::uint32_t targetMask = 0, shaderMask = 0;
|
||||
|
||||
@@ -2780,6 +2780,21 @@ void framebufferLayerTests() {
|
||||
}
|
||||
}
|
||||
|
||||
void quadPixelMaskTests() {
|
||||
auto queue = makeState();
|
||||
queue.context[0x30e] = 0xffff0000u;
|
||||
queue.context[0x30f] = 0u;
|
||||
queue.context[0x1b3] = 2;
|
||||
queue.context[0x1b4] = 2;
|
||||
static_cast<void>(AgcDriver::Graphics::DecodeState(queue));
|
||||
Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "a single-sample quad pixel mask was refused by the precheck");
|
||||
Require(AgcDriver::Graphics::DecodePixelStageInfo(queue.context, {}).quadPixelMask == 0x2u, "PA_SC_AA_MASK did not decode to the covered quad pixel");
|
||||
queue = makeState();
|
||||
queue.context[0x1b3] = 2;
|
||||
queue.context[0x1b4] = 2;
|
||||
Require(AgcDriver::Graphics::DecodePixelStageInfo(queue.context, {}).quadPixelMask == 0xfu, "full sample masks did not cover the whole quad");
|
||||
}
|
||||
|
||||
void DepthClipTests() {
|
||||
auto queue = makeState();
|
||||
const auto direct = AgcDriver::Graphics::DecodeState(queue);
|
||||
@@ -6723,6 +6738,7 @@ int main(int argc, char** argv) {
|
||||
clipDistanceTests();
|
||||
framebufferLayerTests();
|
||||
UnwrittenSampleLocationTests();
|
||||
quadPixelMaskTests();
|
||||
ShaderStageTests();
|
||||
NggVertexWorkgroupTests();
|
||||
inactiveVertexPointSizeTests();
|
||||
|
||||
@@ -2590,6 +2590,22 @@ void verifyMeshConfiguration() {
|
||||
RecompileCacheKey::Build(vertex, unclipped);
|
||||
require(clipped != unclipped, "PA_CL_VS_OUT_CNTL is not part of the recompile cache key");
|
||||
}
|
||||
{
|
||||
RecompileRequest fragment{};
|
||||
fragment.shader = {ShaderStage::Fragment, 0x10000u, code, 0, {}};
|
||||
fragment.context.waveSize = 64;
|
||||
ShaderPixelStageInfo info{};
|
||||
info.quadPixelMask = 0x2u;
|
||||
fragment.context.pixel = info;
|
||||
const auto back = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(fragment));
|
||||
require(back.request.context.pixel.has_value() && back.request.context.pixel->quadPixelMask == 0x2u, "the quad pixel mask was lost in serialization");
|
||||
std::vector<std::uint64_t> masked;
|
||||
RecompileCacheKey::Build(fragment, masked);
|
||||
fragment.context.pixel->quadPixelMask = 0xfu;
|
||||
std::vector<std::uint64_t> full;
|
||||
RecompileCacheKey::Build(fragment, full);
|
||||
require(masked != full, "the quad pixel mask is not part of the recompile cache key");
|
||||
}
|
||||
std::vector<std::uint64_t> key;
|
||||
RecompileCacheKey::Build(request, key);
|
||||
const auto first = key;
|
||||
@@ -2970,7 +2986,7 @@ void verifyPixelRequestSerialization() {
|
||||
minimal.context.pixel->layerExported = true;
|
||||
minimal.context.pixel->ditheredAlphaToCoverage = true;
|
||||
const auto encoded = serializer.Serialize(minimal);
|
||||
require(requestPrefix(encoded, 8u) == "NVNQQRsAAAA=", "new requests did not use serialization version 27");
|
||||
require(requestPrefix(encoded, 8u) == "NVNQQRwAAAA=", "new requests did not use serialization version 28");
|
||||
const std::array<std::uint32_t, 2> fallbacks{0x40u, 0x1ac4u};
|
||||
auto withFallbacks = minimal;
|
||||
withFallbacks.context.linkedCallFallbacks = fallbacks;
|
||||
@@ -3085,10 +3101,10 @@ void verifyPixelRequestSerialization() {
|
||||
require(RecompileCacheKey::ContextHash(legacy19.request) == RecompileCacheKey::ContextHash(upgraded19.request), "upgrading version19 changed shader identity");
|
||||
}
|
||||
constexpr std::size_t mappingOffset = 8u + 37u + 18u + 163u;
|
||||
for (std::size_t bytes = 0; bytes < 24u; ++bytes) {
|
||||
expectFailure([&] { static_cast<void>(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source, alpha-to-coverage, stencil reference export flag, primitive class, layer export, dither or front-face flag was accepted");
|
||||
for (std::size_t bytes = 0; bytes < 25u; ++bytes) {
|
||||
expectFailure([&] { static_cast<void>(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source, alpha-to-coverage, stencil reference export flag, primitive class, layer export, dither or front-face flag, or quad pixel mask was accepted");
|
||||
}
|
||||
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQRwAAAA="}) {
|
||||
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQR0AAAA="}) {
|
||||
expectFailure([&] { static_cast<void>(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted");
|
||||
}
|
||||
static constexpr std::string_view version18Request{
|
||||
@@ -3213,12 +3229,23 @@ void verifyVertexInfoVersion19() {
|
||||
std::size_t allBitsOffset = 0;
|
||||
while (allBitsOffset < version26Pixel.size() && version26Pixel[allBitsOffset] == allBitsBytes[allBitsOffset]) ++allBitsOffset;
|
||||
require(allBitsOffset == flagOffset + 2u, "the front-face all-bits flag does not follow the dithered coverage flag");
|
||||
version26Pixel.erase(version26Pixel.begin() + static_cast<std::ptrdiff_t>(allBitsOffset));
|
||||
version26Pixel.erase(version26Pixel.begin() + static_cast<std::ptrdiff_t>(allBitsOffset), version26Pixel.begin() + static_cast<std::ptrdiff_t>(allBitsOffset + 2u));
|
||||
version26Pixel[4] = 26u;
|
||||
const auto replay26 = serializer.Deserialize(encodeBase64(version26Pixel));
|
||||
require(replay26.request.context.pixel.has_value() && replay26.request.context.pixel->rasterPrimitive == RasterPrimitive::Rectangle && !replay26.request.context.pixel->frontFaceAllBits, "a version 26 request lost its pixel primitive class or read a front-face all-bits flag");
|
||||
require(serializer.Serialize(replay26.request) == encodedPixel, "a version 26 request misread the fields after its dithered coverage flag");
|
||||
legacyPixel.erase(legacyPixel.begin() + static_cast<std::ptrdiff_t>(flagOffset), legacyPixel.begin() + static_cast<std::ptrdiff_t>(flagOffset + 3u));
|
||||
auto maskedPixel = pixel;
|
||||
maskedPixel.context.pixel->quadPixelMask = 0x2u;
|
||||
const auto maskedBytes = decodeBase64(serializer.Serialize(maskedPixel));
|
||||
std::size_t maskOffset = 0;
|
||||
while (maskOffset < legacyPixel.size() && legacyPixel[maskOffset] == maskedBytes[maskOffset]) ++maskOffset;
|
||||
require(maskOffset == flagOffset + 3u && maskOffset < legacyPixel.size(), "the quad pixel mask is not the byte after the front-face all-bits flag");
|
||||
auto version27 = maskedBytes;
|
||||
version27.erase(version27.begin() + static_cast<std::ptrdiff_t>(maskOffset));
|
||||
version27[4] = 27u;
|
||||
const auto replay27 = serializer.Deserialize(encodeBase64(version27));
|
||||
require(replay27.request.context.pixel.has_value() && replay27.request.context.pixel->quadPixelMask == 0xfu && serializer.Serialize(replay27.request) == encodedPixel, "a version 27 request read a quad pixel mask or misread the fields after it");
|
||||
legacyPixel.erase(legacyPixel.begin() + static_cast<std::ptrdiff_t>(flagOffset), legacyPixel.begin() + static_cast<std::ptrdiff_t>(flagOffset + 4u));
|
||||
legacyPixel.resize(legacyPixel.size() - sizeof(std::uint64_t));
|
||||
for (const auto version : {19u, 20u, 21u, 22u}) {
|
||||
auto versionPixel = legacyPixel;
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Width = 64;
|
||||
constexpr std::uint32_t Height = 32;
|
||||
alignas(256) std::array<std::byte, Width * Height * 4> Pixels{};
|
||||
alignas(256) 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}}};
|
||||
|
||||
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> PixelCode{
|
||||
0x7e0e02f2, 0xf800180f, 0x07070707, 0xbf810000,
|
||||
};
|
||||
|
||||
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(data);
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu};
|
||||
}
|
||||
|
||||
void Draw(AgcDriver::VulkanDevice& device, std::uint8_t quadPixelMask) {
|
||||
Pixels.fill(std::byte{0});
|
||||
const auto target = device.Target();
|
||||
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, {}},
|
||||
{64, 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.targetOutputMode[0] = 9;
|
||||
pixel.targetExportMapping.fill(0xe4u);
|
||||
pixel.quadPixelMask = quadPixelMask;
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> pixelMemory{{{reinterpret_cast<std::uintptr_t>(PixelCode.data()), std::as_bytes(std::span(PixelCode))}}};
|
||||
ShaderRecompiler::RecompileRequest fragment{
|
||||
{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(PixelCode.data()), PixelCode, 0, {}},
|
||||
{64, 0, {}, 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)}
|
||||
}};
|
||||
|
||||
AgcDriver::Graphics::State state{};
|
||||
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 64u, 64u, 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.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>(Triangle.size()), 0, 1, 0, false};
|
||||
device.Draw(state, draw, shaders);
|
||||
device.WaitIdle();
|
||||
}
|
||||
|
||||
void Check(std::uint8_t quadPixelMask) {
|
||||
for (std::uint32_t y = 0; y < Height; ++y) {
|
||||
for (std::uint32_t x = 0; x < Width; ++x) {
|
||||
const bool expected = ((quadPixelMask >> ((x & 1u) | ((y & 1u) << 1u))) & 1u) != 0;
|
||||
const bool written = std::to_integer<std::uint8_t>(Pixels[(y * Width + x) * 4u]) == 255u;
|
||||
Require(written == expected, "quad pixel mask 0x" + std::to_string(quadPixelMask) + ": pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") is " + (written ? "written" : "not written"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
for (const std::uint8_t mask : {std::uint8_t{0xf}, std::uint8_t{0x2}, std::uint8_t{0x9}}) {
|
||||
Draw(*device, mask);
|
||||
Check(mask);
|
||||
}
|
||||
std::puts("quad pixel mask tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -191,6 +191,7 @@ private:
|
||||
append(key, value.alphaToCoverage);
|
||||
append(key, value.stencilRefExportEnable);
|
||||
append(key, value.ditheredAlphaToCoverage);
|
||||
append(key, value.quadPixelMask);
|
||||
append(key, value.targetOutputMode);
|
||||
}
|
||||
|
||||
|
||||
@@ -340,6 +340,7 @@ void writePixelInfo(Writer& writer, const ShaderPixelStageInfo& info) {
|
||||
writer.WriteBool(info.layerExported);
|
||||
writer.WriteBool(info.ditheredAlphaToCoverage);
|
||||
writer.WriteBool(info.frontFaceAllBits);
|
||||
writer.WriteU8(info.quadPixelMask);
|
||||
}
|
||||
|
||||
ShaderPixelStageInfo readPixelInfo(Reader& reader, std::uint32_t version) {
|
||||
@@ -393,6 +394,7 @@ ShaderPixelStageInfo readPixelInfo(Reader& reader, std::uint32_t version) {
|
||||
if (version >= 23u) info.layerExported = reader.ReadBool();
|
||||
if (version >= 25u) info.ditheredAlphaToCoverage = reader.ReadBool();
|
||||
if (version >= 27u) info.frontFaceAllBits = reader.ReadBool();
|
||||
if (version >= 28u) info.quadPixelMask = reader.ReadU8();
|
||||
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) |
|
||||
@@ -764,7 +766,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
|
||||
std::string buffer;
|
||||
Writer writer(buffer);
|
||||
writer.WriteU32(0x41505335u);
|
||||
writer.WriteU32(27u);
|
||||
writer.WriteU32(28u);
|
||||
writeShaderBinary(writer, request.shader);
|
||||
writeGuestContext(writer, request.context);
|
||||
writeSpirvTarget(writer, request.target);
|
||||
@@ -797,7 +799,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 > 27u) throw std::runtime_error("unsupported recompile request serialization version");
|
||||
if (version < 1u || version > 28u) 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);
|
||||
|
||||
@@ -181,6 +181,7 @@ struct ShaderPixelInputInfo {
|
||||
bool psOrderedPixelShader = false;
|
||||
bool psAlphaToCoverage = false;
|
||||
bool psDitheredAlphaToCoverage = false;
|
||||
std::uint8_t quadPixelMask = 0xf;
|
||||
ShaderStageRuntime stage;
|
||||
|
||||
bool HasPositionInput() const {
|
||||
|
||||
@@ -237,7 +237,7 @@ bool IsPixelParameterFlat(const ShaderPixelInputInfo& pixel, std::uint32_t input
|
||||
}
|
||||
|
||||
void CollectPixelInputs(const IrProgram& program, const ShaderPixelInputInfo* pixel, ShaderInfo& info) {
|
||||
if (pixel->HasPositionInput() || pixel->psDitheredAlphaToCoverage) {
|
||||
if (pixel->HasPositionInput() || pixel->psDitheredAlphaToCoverage || pixel->quadPixelMask != 0xf) {
|
||||
AddInput(info, StageInputKind::FragCoord, 0, 4, "gl_FragCoord");
|
||||
}
|
||||
if (pixel->psFrontFace) {
|
||||
|
||||
@@ -154,6 +154,7 @@ struct ShaderPixelStageInfo {
|
||||
RasterPrimitive rasterPrimitive = RasterPrimitive::Triangle;
|
||||
bool layerExported = false;
|
||||
bool ditheredAlphaToCoverage = false;
|
||||
std::uint8_t quadPixelMask = 0xf;
|
||||
};
|
||||
|
||||
struct ShaderVertexBufferResource {
|
||||
|
||||
@@ -196,7 +196,7 @@ void DefineModule(SpirvEmitterState& state) {
|
||||
state.module.EmitExtension("SPV_EXT_shader_stencil_export");
|
||||
state.module.AddExecutionMode(state.mainFunc, spv::ExecutionModeStencilRefReplacingEXT);
|
||||
}
|
||||
if (pixel.psEarlyZ && !pixel.psAlphaToCoverage) {
|
||||
if (pixel.psEarlyZ && !pixel.psAlphaToCoverage && pixel.quadPixelMask == 0xf) {
|
||||
state.module.AddExecutionMode(state.mainFunc, spv::ExecutionModeEarlyFragmentTests);
|
||||
}
|
||||
if (pixel.psOrderedPixelShader) {
|
||||
|
||||
@@ -1180,6 +1180,23 @@ void EmitProgram(SpirvEmitterState& state) {
|
||||
state.module.AddFunction(spv::OpBeginInvocationInterlockEXT);
|
||||
}
|
||||
EmitMemoryOffsets(state);
|
||||
if (state.program.Resources().stage == IrShaderStage::Pixel && state.inputInfo.pixel != nullptr && state.inputInfo.pixel->quadPixelMask != 0xfu) {
|
||||
const auto coordinate = [&](std::uint32_t component) {
|
||||
const auto value = Unary(state, spv::OpBitcast, TypeF32(state), EmitBuiltinU32(state, StageInputKind::FragCoord, component));
|
||||
return EmitBinaryU32(state, spv::OpBitwiseAnd, Unary(state, spv::OpConvertFToU, TypeU32(state), value), ConstantU32(state, 1u));
|
||||
};
|
||||
const auto quad = EmitBinaryU32(state, spv::OpBitwiseOr, coordinate(0u), EmitBinaryU32(state, spv::OpShiftLeftLogical, coordinate(1u), ConstantU32(state, 1u)));
|
||||
const auto bit = EmitBinaryU32(state, spv::OpBitwiseAnd, EmitBinaryU32(state, spv::OpShiftRightLogical, ConstantU32(state, state.inputInfo.pixel->quadPixelMask), quad), ConstantU32(state, 1u));
|
||||
const auto uncovered = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpIEqual, TypeBool(state), uncovered, bit, ConstantU32(state, 0u));
|
||||
const auto killLabel = state.module.AllocateId();
|
||||
const auto mergeLabel = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpSelectionMerge, mergeLabel, spv::SelectionControlMaskNone);
|
||||
state.module.AddFunction(spv::OpBranchConditional, uncovered, killLabel, mergeLabel);
|
||||
EmitLabel(state, killLabel);
|
||||
state.module.AddFunction(spv::OpKill);
|
||||
EmitLabel(state, mergeLabel);
|
||||
}
|
||||
if (program.BlockOrder().empty()) {
|
||||
if (OrderedPixelShader(state)) {
|
||||
state.module.AddFunction(spv::OpEndInvocationInterlockEXT);
|
||||
|
||||
@@ -148,6 +148,7 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues
|
||||
if (pixel.ditheredAlphaToCoverage && !pixel.alphaToCoverage) throw std::runtime_error("dithered alpha-to-coverage requires enabled coverage");
|
||||
pixelStorage.psAlphaToCoverage = pixel.alphaToCoverage;
|
||||
pixelStorage.psDitheredAlphaToCoverage = pixel.ditheredAlphaToCoverage;
|
||||
pixelStorage.quadPixelMask = pixel.quadPixelMask;
|
||||
ShaderStageInputInfo result;
|
||||
result.pixel = &pixelStorage;
|
||||
return result;
|
||||
|
||||
Reference in new issue
Block a user