feat(shader): implement triangle-patch tessellation
DecodeShaderStages accepts LS, HS and an NGG passthrough ES, reads the 3-bit VGT_TF_PARAM fields, refuses its other bits but DISTRIBUTION_MODE, and needs HOS levels that keep [1, 63]; 0x286-0x287 join the draw key. Local and domain programs are prepared with their stage group: its headers are merged over one set of defaults, and a local program left unbound by the hull header is prepared at its first draw. The local program ends at its jump into the hull half. TessellationLowering lowers LDS and ring addresses that are affine in the control point and relative index to tessellation interfaces with strides from the code, and refuses accesses outside the patch, per-patch writes from other control points and a modified primitive export. Hull lanes read their own saved exec bit, also of host-subgroup-masked copies; other masks are refused, gl_InvocationID loads as a scalar and factors clamp to 63.
This commit is contained in:
parent
09bd4aa1a3
commit
77dcb74ccd
|
|
@ -81,7 +81,7 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const
|
|||
};
|
||||
const auto& graphics = product->state;
|
||||
if (graphics.stages.path == Graphics::ShaderPath::Tessellation) {
|
||||
append(0x148, 5, Stage::Local, 0x10b, 0x10c, Role::Local);
|
||||
if (!staticAbi || programAddress(0x148) != 0) append(0x148, 5, Stage::Local, 0x10b, 0x10c, Role::Local);
|
||||
append(0x108, 7, Stage::TessellationControl, 0x10b, 0x10c, Role::Hull);
|
||||
initializeMerged(programs.back(), 0x102, true);
|
||||
append(0x0c8, 2, Stage::TessellationEvaluation, 0x08b, 0x08c, Role::Domain);
|
||||
|
|
|
|||
|
|
@ -474,9 +474,8 @@ Shader ReadHeader(const ShaderSnapshot& snapshot) {
|
|||
return header;
|
||||
}
|
||||
|
||||
RegisteredShaderState DecodeRegisteredState(const ShaderSnapshot& snapshot) {
|
||||
RegisteredShaderState DecodeRegisteredState(const ShaderSnapshot& snapshot, RegisteredShaderState state = {{}, InitialContextRegisters(), {{0x24a, 0}, {0x24b, 0}}}) {
|
||||
const auto header = ReadHeader(snapshot);
|
||||
RegisteredShaderState state{{}, InitialContextRegisters(), {{0x24a, 0}, {0x24b, 0}}};
|
||||
for (const auto reg : ReadHeaderArray(snapshot, header.sh_registers, header.num_sh_registers)) {
|
||||
state.shader.insert_or_assign(reg.offset, reg.value);
|
||||
}
|
||||
|
|
@ -531,7 +530,7 @@ std::unique_ptr<RegisteredPreparation> PlanRegistered(const ShaderSnapshot& snap
|
|||
case 1: stage = Stage::Fragment; programRegister = 0x008; resourceRegister = 0x00b; break;
|
||||
case 2: stage = Stage::Vertex; programRegister = 0x0c8; resourceRegister = 0x08b; firstUser = 8; break;
|
||||
case 4: stage = Stage::Mesh; programRegister = 0x0c8; resourceRegister = 0x08b; break;
|
||||
case 5: stage = Stage::Local; programRegister = 0x148; resourceRegister = 0x10b; firstUser = 8; break;
|
||||
case 5: return {};
|
||||
case 6: stage = Stage::Mesh; programRegister = 0x088; resourceRegister = 0x08b; break;
|
||||
case 7: stage = Stage::TessellationControl; programRegister = 0x108; resourceRegister = 0x10b; break;
|
||||
default:
|
||||
|
|
@ -586,8 +585,8 @@ std::unique_ptr<RegisteredPreparation> PlanRegistered(const ShaderSnapshot& snap
|
|||
} else {
|
||||
const auto routing = RegisterValue(state.context, 0x2d5);
|
||||
wave = (routing & 0x00400000u) != 0 ? 32u : 64u;
|
||||
if ((routing & 0x20u) != 0 || stage == Stage::Mesh || (routing & 4u) != 0) {
|
||||
if (registration) return nullptr;
|
||||
if ((routing & 0x20u) != 0 || stage == Stage::Mesh || (routing & 4u) != 0 || ((routing >> 3u) & 3u) == 1u) {
|
||||
if (registration) return {};
|
||||
auto stageState = state;
|
||||
stageState.context[0x1b6] = 0;
|
||||
const auto stages = Graphics::DecodeShaderStages(stageState);
|
||||
|
|
@ -756,8 +755,10 @@ std::vector<PreparedGraphicsStage> PrepareGraphicsStages(const DrawDecode& decod
|
|||
require(decoded.programs.size() == decoded.roles.size(), "graphics ABI program roles are incomplete");
|
||||
for (const auto& program : decoded.programs) require(program.snapshot != nullptr, "graphics ABI has no registered shader snapshot");
|
||||
std::vector<ShaderRecompiler::ProgramRole> expected;
|
||||
if (decoded.state.stages.path == Graphics::ShaderPath::Tessellation) expected = {ShaderRecompiler::ProgramRole::Local, ShaderRecompiler::ProgramRole::Hull, ShaderRecompiler::ProgramRole::Domain};
|
||||
else {
|
||||
if (decoded.state.stages.path == Graphics::ShaderPath::Tessellation) {
|
||||
if (!decoded.roles.empty() && decoded.roles.front() == ShaderRecompiler::ProgramRole::Local) expected.push_back(ShaderRecompiler::ProgramRole::Local);
|
||||
expected.insert(expected.end(), {ShaderRecompiler::ProgramRole::Hull, ShaderRecompiler::ProgramRole::Domain});
|
||||
} else {
|
||||
expected.push_back(ShaderRecompiler::ProgramRole::Main);
|
||||
if (decoded.state.stages.path == Graphics::ShaderPath::Geometry && !decoded.programs.empty() && decoded.programs.front().snapshot->type == 4) expected.push_back(ShaderRecompiler::ProgramRole::GeometryBack);
|
||||
}
|
||||
|
|
@ -831,7 +832,7 @@ void Driver::ResolveGraphicsStagesAbi(std::span<const Shader* const> stages, std
|
|||
if (owner == nullptr) owner = selected;
|
||||
const auto& snapshot = *selected;
|
||||
require(snapshot.registeredState != nullptr, "registered shader state is missing");
|
||||
const auto& registered = *snapshot.registeredState;
|
||||
const auto registered = DecodeRegisteredState(snapshot, {});
|
||||
for (const auto& [offset, value] : registered.shader) state.shader.insert_or_assign(offset, value);
|
||||
for (const auto& [offset, value] : registered.context) state.context.insert_or_assign(offset, value);
|
||||
for (const auto& [offset, value] : registered.userConfig) state.userConfig.insert_or_assign(offset, value);
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ struct DrawKeyRange {
|
|||
std::uint32_t first;
|
||||
std::uint32_t count;
|
||||
};
|
||||
inline constexpr std::array<DrawKeyRange, 47> DrawKeyRegisters{{
|
||||
inline constexpr std::array<DrawKeyRange, 48> DrawKeyRegisters{{
|
||||
{RegisterBank::Context, 0x000, 1}, {RegisterBank::Context, 0x002, 1}, {RegisterBank::Context, 0x005, 1}, {RegisterBank::Context, 0x007, 7}, {RegisterBank::Context, 0x010, 6}, {RegisterBank::Context, 0x01a, 5},
|
||||
{RegisterBank::Context, 0x080, 4}, {RegisterBank::Context, 0x08c, 4}, {RegisterBank::Context, 0x090, 2}, {RegisterBank::Context, 0x094, 2}, {RegisterBank::Context, 0x0b4, 2}, {RegisterBank::Context, 0x105, 4}, {RegisterBank::Context, 0x10b, 3}, {RegisterBank::Context, 0x10f, 6},
|
||||
// SPI_PS_INPUT_CNTL_0..31, SPI_PS_INPUT_ENA/ADDR, SPI_PS_IN_CONTROL, SPI_SHADER_POS/Z/COL_FORMAT,
|
||||
|
|
@ -171,7 +171,7 @@ inline constexpr std::array<DrawKeyRange, 47> DrawKeyRegisters{{
|
|||
{RegisterBank::Context, 0x191, 32}, {RegisterBank::Context, 0x1b3, 2}, {RegisterBank::Context, 0x1b6, 1}, {RegisterBank::Context, 0x1c3, 3}, {RegisterBank::Context, 0x1e0, 8}, {RegisterBank::Context, 0x1ff, 1},
|
||||
// DB_DEPTH_CONTROL .. PA_CL_VS_OUT_CNTL, PA_SC_MODE_CNTL_0/1, VGT_GS_MODE, VGT_GS_VERT_ITEMSIZE,
|
||||
// PA_SU_VTX_CNTL, the sample masks, PA_SC_CONSERVATIVE_RASTERIZATION_CNTL.
|
||||
{RegisterBank::Context, 0x200, 8}, {RegisterBank::Context, 0x292, 2}, {RegisterBank::Context, 0x29b, 1}, {RegisterBank::Context, 0x2ab, 1}, {RegisterBank::Context, 0x2ce, 1}, {RegisterBank::Context, 0x2d5, 2}, {RegisterBank::Context, 0x2db, 2}, {RegisterBank::Context, 0x2de, 6}, {RegisterBank::Context, 0x2f8, 2}, {RegisterBank::Context, 0x30e, 2}, {RegisterBank::Context, 0x313, 1},
|
||||
{RegisterBank::Context, 0x200, 8}, {RegisterBank::Context, 0x286, 2}, {RegisterBank::Context, 0x292, 2}, {RegisterBank::Context, 0x29b, 1}, {RegisterBank::Context, 0x2ab, 1}, {RegisterBank::Context, 0x2ce, 1}, {RegisterBank::Context, 0x2d5, 2}, {RegisterBank::Context, 0x2db, 2}, {RegisterBank::Context, 0x2de, 6}, {RegisterBank::Context, 0x2f8, 2}, {RegisterBank::Context, 0x30e, 2}, {RegisterBank::Context, 0x313, 1},
|
||||
// CB_COLOR0..7_BASE .. DCC_BASE (15 words a slot), CB_COLOR0..7_BASE_EXT, DCC_BASE_EXT, ATTRIB2, ATTRIB3.
|
||||
{RegisterBank::Context, 0x318, 0x78}, {RegisterBank::Context, 0x390, 8}, {RegisterBank::Context, 0x398, 8}, {RegisterBank::Context, 0x3a8, 0x18},
|
||||
// The pixel program address, RSRC2 and user words; the geometry-back user pointer and program
|
||||
|
|
|
|||
|
|
@ -504,12 +504,14 @@ ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup) {
|
|||
validate((value & ~0x0247a010u) == 0, "unsupported vertex routing, scheduling or wave-ID state");
|
||||
} else if (path == ShaderPath::Tessellation) {
|
||||
validate((value & 0x00600020u) == 0, "wave32 tessellation or geometry amplification is unsupported");
|
||||
validate((value & ~0x0007ed0du) == 0 && (value & 3u) == 1u && ((value >> 3u) & 3u) == 1u, "unsupported tessellation routing");
|
||||
validate((value & ~0x0207ed0du) == 0 && (value & 0x02002000u) == 0x02002000u && (value & 3u) == 1u && ((value >> 3u) & 3u) == 1u, "unsupported tessellation routing");
|
||||
const auto config = read(queue.context, 0x2d6);
|
||||
const auto parameters = read(queue.context, 0x2db);
|
||||
ShaderRecompiler::TessellationConfiguration tess{(config >> 8u) & 0x3fu, (config >> 14u) & 0x3fu, parameters & 3u, (parameters >> 2u) & 3u, (parameters >> 5u) & 3u};
|
||||
ShaderRecompiler::TessellationConfiguration tess{(config >> 8u) & 0x3fu, (config >> 14u) & 0x3fu, parameters & 3u, (parameters >> 2u) & 7u, (parameters >> 5u) & 7u};
|
||||
validate(tess.inputControlPoints != 0 && tess.inputControlPoints <= 32 && tess.outputControlPoints != 0 && tess.outputControlPoints <= 32, "invalid tessellation control-point counts");
|
||||
if ((parameters & ~0x000600ffu) != 0) Require(false, zeroMessage(0x2db, parameters, "VGT_TF_PARAM donut, factor detection, memory type or reserved state"));
|
||||
validate(tess.domain == 1 && tess.partitioning == 2 && tess.outputTopology == 2, "only triangular, fractional-odd, clockwise tessellation is supported by the reference path");
|
||||
Require(readFloat(queue.context, 0x287) <= 1.0f && readFloat(queue.context, 0x286) >= 63.0f, "tessellation level clamps inside the fractional-odd range [1, 63] are unsupported");
|
||||
result.tessellation = tess;
|
||||
} else {
|
||||
validate((value & ~0x0047ec30u) == 0, "unsupported geometry routing, fast launch or wave-ID state");
|
||||
|
|
|
|||
|
|
@ -487,13 +487,39 @@ void ShaderStageTests() {
|
|||
queue.context[0x2d5] = 0x200d;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "Patch topology and HS_EN disagree");
|
||||
queue.userConfig[0x242] = 9;
|
||||
queue.context[0x2d6] = (3u << 8u) | (3u << 14u);
|
||||
queue.context[0x2db] = 1u | (2u << 2u) | (2u << 5u);
|
||||
stages = AgcDriver::Graphics::DecodeState(queue).stages;
|
||||
Require(stages.path == AgcDriver::Graphics::ShaderPath::Tessellation && stages.tessellation && stages.tessellation->inputControlPoints == 3, "tessellation routing changed");
|
||||
queue.context[0x2d6] = 0xc355;
|
||||
queue.context[0x2db] = 0x40049;
|
||||
queue.context[0x286] = 0x42800000;
|
||||
queue.context[0x287] = 0x3f800000;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "unsupported tessellation routing");
|
||||
queue.context[0x2d5] = 0x0200010d;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "unsupported tessellation routing");
|
||||
queue.context[0x2d5] = 0x0200210d;
|
||||
{
|
||||
std::vector<AgcDriver::Graphics::RegisterRead> tessellationLog;
|
||||
AgcDriver::Graphics::RegisterReadLog() = &tessellationLog;
|
||||
stages = AgcDriver::Graphics::DecodeState(queue).stages;
|
||||
AgcDriver::Graphics::RegisterReadLog() = nullptr;
|
||||
for (const auto read : tessellationLog) Require(AgcDriver::Graphics::DrawKeyCovers(read), "DrawKeyRegisters lacks a tessellation register the decoder reads: " + std::to_string(read.offset));
|
||||
}
|
||||
Require(stages.path == AgcDriver::Graphics::ShaderPath::Tessellation && stages.tessellation, "tessellation routing changed");
|
||||
const auto& tessellation = *stages.tessellation;
|
||||
Require(tessellation.inputControlPoints == 3 && tessellation.outputControlPoints == 3 && tessellation.domain == 1 && tessellation.partitioning == 2 && tessellation.outputTopology == 2, "the passthrough triangle-patch configuration decoded differently");
|
||||
for (const auto [parameters, reason] : {std::pair{0x44049u, "VGT_TF_PARAM"}, std::pair{0x140049u, "VGT_TF_PARAM"}, std::pair{0x2040049u, "VGT_TF_PARAM"}, std::pair{0x40059u, "only triangular"}, std::pair{0x40069u, "only triangular"}, std::pair{0x4006au, "only triangular"}}) {
|
||||
queue.context[0x2db] = parameters;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, reason);
|
||||
}
|
||||
queue.context[0x2db] = 0x40049;
|
||||
for (const auto [maximum, minimum] : {std::pair{0x41100000u, 0x3f800000u}, std::pair{0x42800000u, 0x40000000u}}) {
|
||||
queue.context[0x286] = maximum;
|
||||
queue.context[0x287] = minimum;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "tessellation level clamps");
|
||||
}
|
||||
queue.context[0x286] = 0x42800000;
|
||||
queue.context[0x287] = 0x3f800000;
|
||||
queue.context[0x2d5] = 0x202d;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "combined tessellation and geometry");
|
||||
queue.context[0x2d5] = 0x200d;
|
||||
queue.context[0x2d5] = 0x0200210d;
|
||||
queue.context[0x2d6] = 0;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "control-point counts");
|
||||
queue = makeState();
|
||||
|
|
@ -3411,10 +3437,12 @@ void ConservativeRasterizationTests() {
|
|||
expectFailure([&] { AgcDriver::Graphics::DecodeState(geometry); }, rejected);
|
||||
}
|
||||
auto tessellation = queue;
|
||||
tessellation.context[0x2d5] = 0x200d;
|
||||
tessellation.context[0x2d5] = 0x0200210d;
|
||||
tessellation.userConfig[0x242] = 9;
|
||||
tessellation.context[0x2d6] = (3u << 8u) | (3u << 14u);
|
||||
tessellation.context[0x2d6] = 0xc355;
|
||||
tessellation.context[0x2db] = 1u | (2u << 2u) | (2u << 5u);
|
||||
tessellation.context[0x286] = 0x42800000;
|
||||
tessellation.context[0x287] = 0x3f800000;
|
||||
const auto tessellated = AgcDriver::Graphics::DecodeState(tessellation);
|
||||
Require(tessellated.stages.path == AgcDriver::Graphics::ShaderPath::Tessellation && tessellated.conservativeRasterization == VK_CONSERVATIVE_RASTERIZATION_MODE_OVERESTIMATE_EXT, "OVER_RAST_ENABLE did not overestimate clockwise tessellated triangles");
|
||||
for (const auto& [parameters, rejected] : std::array<std::pair<std::uint32_t, std::string_view>, 3>{{{1u | (2u << 2u), "conservative rasterization of points is unsupported (VGT_TF_PARAM=0x9)"}, {1u | (2u << 2u) | (1u << 5u), "conservative rasterization of lines is unsupported (VGT_TF_PARAM=0x29)"}, {(2u << 2u) | (1u << 5u), "conservative rasterization of lines is unsupported (VGT_TF_PARAM=0x28)"}}}) {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ struct Fixture {
|
|||
header.shader.type = type;
|
||||
header.shader.user_data = &header.users;
|
||||
code.fill(0xffffffffu);
|
||||
code.back() = 0xbf810000u;
|
||||
code.back() = type == 5u ? 0xbefd2106u : 0xbf810000u;
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(code.data());
|
||||
const auto headerAddress = reinterpret_cast<std::uintptr_t>(&header);
|
||||
snapshot = std::make_shared<AgcDriver::DriverDetail::ShaderSnapshot>();
|
||||
|
|
@ -114,7 +114,7 @@ void Check(AgcDriver::VulkanDevice& device, AgcDriver::Graphics::ShaderPath path
|
|||
for (std::size_t index = 0; index < prepared.programs.size(); ++index) {
|
||||
const auto& expected = prepared.programs[index];
|
||||
const auto& actual = draw.programs[index];
|
||||
Require(expected.codeOffset == 64u && expected.binary.code.size() == (prepared.roles[index] == ProgramRole::Fragment ? pixelCode.size() : 1u) && expected.binary.code[0] == (prepared.roles[index] == ProgramRole::Fragment ? pixelCode[0] : 0xbf810000u), "graphics entry point did not trim the code prefix");
|
||||
Require(expected.codeOffset == 64u && expected.binary.code.size() == (prepared.roles[index] == ProgramRole::Fragment ? pixelCode.size() : 1u) && expected.binary.code[0] == (prepared.roles[index] == ProgramRole::Fragment ? pixelCode[0] : prepared.roles[index] == ProgramRole::Local ? 0xbefd2106u : 0xbf810000u), "graphics entry point did not trim the code prefix");
|
||||
Require(expected.binary.stage == actual.binary.stage && expected.firstUserSgpr == actual.firstUserSgpr && expected.userData.size() == actual.userData.size(), "graphics preparation changed the user SGPR ABI");
|
||||
}
|
||||
auto target = device.Target();
|
||||
|
|
@ -243,6 +243,36 @@ void Check(AgcDriver::VulkanDevice& device, AgcDriver::Graphics::ShaderPath path
|
|||
static_cast<void>(InvocationFor(*program.snapshot, program.codeOffset, request));
|
||||
Require(program.snapshot->prepared->entries.size() == predicted + 1, "an artifact prepared at a draw was not reused");
|
||||
}
|
||||
if (tessellation) {
|
||||
Fixture late;
|
||||
late.Initialize(5u);
|
||||
registry.emplace(late.snapshot->codeAddress, late.snapshot);
|
||||
auto unbound = queue;
|
||||
unbound.shader[0x148u] = 0u;
|
||||
unbound.shader[0x149u] = 0u;
|
||||
DrawDecode resolved{};
|
||||
resolved.state = prepared.state;
|
||||
resolved.pixel = prepared.pixel;
|
||||
DecodeGraphicsPrograms(resolved, unbound, registry, true, true);
|
||||
Require(resolved.programs.size() == 3u && resolved.roles.front() == ProgramRole::Hull, "an unbound local program was not left to the draw");
|
||||
Require(PrepareGraphicsStages(resolved, target).size() == 3u, "graphics preparation without the local program compiled the wrong stages");
|
||||
auto bound = queue;
|
||||
late.Bind(bound, 0x148u, 0x10bu);
|
||||
DrawDecode lateDraw{};
|
||||
lateDraw.state = prepared.state;
|
||||
lateDraw.pixel = prepared.pixel;
|
||||
DecodeGraphicsPrograms(lateDraw, bound, registry, false, true);
|
||||
const auto& program = lateDraw.programs.front();
|
||||
Require(program.snapshot == late.snapshot && late.snapshot->prepared->entries.empty(), "the late local program was decoded from another shader");
|
||||
const auto vertex = AgcDriver::Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true);
|
||||
RecompileRequest request{program.binary, {prepared.state.stages.vertexWaveSize, program.firstUserSgpr, program.userData, {}, std::nullopt, vertex, memory}, target, {0, 0, 0, 128u}, GraphicsCompileContext{program.firstUserSgpr, linked, std::nullopt, prepared.state.stages.tessellation, {0, 3, 4, 1}}};
|
||||
static_cast<void>(InvocationFor(*late.snapshot, program.codeOffset, request));
|
||||
static_cast<void>(InvocationFor(*late.snapshot, program.codeOffset, request));
|
||||
Require(late.snapshot->prepared->entries.size() == 1u, "the local program was not prepared once at its first draw");
|
||||
++request.graphics->tessellation->outputControlPoints;
|
||||
Reject([&] { static_cast<void>(InvocationFor(*late.snapshot, program.codeOffset, request)); }, "artifact is missing");
|
||||
registry.erase(late.snapshot->codeAddress);
|
||||
}
|
||||
ShaderRegistry invalidRegistry;
|
||||
invalidRegistry[1] = {std::make_shared<const ShaderSnapshot>(ShaderSnapshot{1, 0, 2, {}, {}})};
|
||||
DrawDecode invalid{};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,340 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "SceShaders.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Width = 256;
|
||||
constexpr std::uint32_t Height = 80;
|
||||
constexpr std::array<std::uint8_t, 4> Background{16, 24, 40, 255};
|
||||
alignas(256) std::array<std::byte, Width * Height * 4> Pixels{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 29> LocalCode{
|
||||
0xd5480004, 0x02090502, 0xd5480005, 0x02090102, 0xd5480006, 0x02110902, 0xd5480007, 0x02111102,
|
||||
0x7e080d04, 0x7e0a0d05, 0x7e0c0d06, 0x7e0e0d07, 0x10100cff, 0x3f19999a, 0x061010ff, 0xbf666666,
|
||||
0x3e1008ff, 0x3e800000, 0xd54b0009, 0x03c5e105, 0x161406a0, 0xd9340000, 0x0000080a, 0xd8340008,
|
||||
0x0000070a, 0xd8340010, 0x0000070a, 0xbf8cc07f, 0xbefd2106,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 49> HullCode{
|
||||
0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014,
|
||||
0x02151101, 0x362402ff, 0x000000ff, 0x160028a0, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000,
|
||||
0x02000015, 0x162c2890, 0xd5430016, 0x045a24ff, 0x00000030, 0xbf8cc07f, 0xe0781000, 0x02020216,
|
||||
0x7da82881, 0xbe94047e, 0xb06a0060, 0x162602f9, 0x0086066a, 0xd8d80008, 0x06000013, 0xd8dc1008,
|
||||
0x07000013, 0xbf8cc07f, 0x7e300306, 0x7e320306, 0x7e340306, 0x7e360306, 0x34142484, 0xbefe0414,
|
||||
0xe0781000, 0x0403180a, 0x4a162481, 0x16161690, 0x4c1616ff, 0x00008000, 0xe0741000, 0x0202070b,
|
||||
0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 51> HullRestoredExecSaved{
|
||||
0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014,
|
||||
0x02151101, 0x362402ff, 0x000000ff, 0x160028a0, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000,
|
||||
0x02000015, 0x162c2890, 0xd5430016, 0x045a24ff, 0x00000030, 0xbf8cc07f, 0xe0781000, 0x02020216,
|
||||
0x7da82881, 0xbe94047e, 0xb06a0060, 0x162602f9, 0x0086066a, 0xd8d80008, 0x06000013, 0xd8dc1008,
|
||||
0x07000013, 0xbf8cc07f, 0x7e300306, 0x7e320306, 0x7e340306, 0x7e360306, 0x34142484, 0xbefe0414,
|
||||
0xbe94047e, 0xbefe0414, 0xe0781000, 0x0403180a, 0x4a162481, 0x16161690, 0x4c1616ff, 0x00008000,
|
||||
0xe0741000, 0x0202070b, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 30> DomainCode{
|
||||
0xf8000941, 0x00000000, 0x34020e84, 0x16020283, 0xe0341000, 0x04030a01, 0xe0341010, 0x04030c01,
|
||||
0xe0341020, 0x04030e01, 0xbf8c3f70, 0x08040af2, 0x08040d02, 0x10201505, 0x3e201906, 0x3e201d02,
|
||||
0x10221705, 0x3e221b06, 0x3e221f02, 0xd5510012, 0x040a0d05, 0x102424ff, 0x40400000, 0x7e260280,
|
||||
0x7e2802f2, 0xf80008cf, 0x14131110, 0xf800020f, 0x14131214, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 29> LocalIndexAsData{
|
||||
0xd5480004, 0x02090502, 0xd5480005, 0x02090102, 0xd5480006, 0x02110902, 0xd5480007, 0x02111102,
|
||||
0x7e080d04, 0x7e0a0d05, 0x7e0c0d06, 0x7e0e0d07, 0x10100cff, 0x3f19999a, 0x061010ff, 0xbf666666,
|
||||
0x3e1008ff, 0x3e800000, 0xd54b0009, 0x03c5e105, 0x161406a0, 0xd9340000, 0x0000080a, 0xd8340008,
|
||||
0x0000030a, 0xd8340010, 0x0000070a, 0xbf8cc07f, 0xbefd2106,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 46> HullUnguardedFactors{
|
||||
0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014,
|
||||
0x02151101, 0x362402ff, 0x000000ff, 0x160028a0, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000,
|
||||
0x02000015, 0x162c2890, 0xd5430016, 0x045a24ff, 0x00000030, 0xbf8cc07f, 0xe0781000, 0x02020216,
|
||||
0xb06a0060, 0x162602f9, 0x0086066a, 0xd8d80008, 0x06000013, 0xd8dc1008, 0x07000013, 0xbf8cc07f,
|
||||
0x7e300306, 0x7e320306, 0x7e340306, 0x7e360306, 0x34142484, 0xe0781000, 0x0403180a, 0x4a162481,
|
||||
0x16161690, 0x4c1616ff, 0x00008000, 0xe0741000, 0x0202070b, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 47> HullSquaredAddress{
|
||||
0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014,
|
||||
0x02151101, 0x362402ff, 0x000000ff, 0x16002914, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000,
|
||||
0x02000015, 0x162c2890, 0xd5430016, 0x045a24ff, 0x00000030, 0xbf8cc07f, 0xe0781000, 0x02020216,
|
||||
0x7da82881, 0xb06a0060, 0x162602f9, 0x0086066a, 0xd8d80008, 0x06000013, 0xd8dc1008, 0x07000013,
|
||||
0xbf8cc07f, 0x7e300306, 0x7e320306, 0x7e340306, 0x7e360306, 0x34142484, 0xe0781000, 0x0403180a,
|
||||
0x4a162481, 0x16161690, 0x4c1616ff, 0x00008000, 0xe0741000, 0x0202070b, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 49> HullAndSaveexec{
|
||||
0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014,
|
||||
0x02151101, 0x362402ff, 0x000000ff, 0x160028a0, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000,
|
||||
0x02000015, 0x162c2890, 0xd5430016, 0x045a24ff, 0x00000030, 0xbf8cc07f, 0x7d8a2887, 0xbe94246a,
|
||||
0xe0781000, 0x02020216, 0x7da82881, 0xb06a0060, 0x162602f9, 0x0086066a, 0xd8d80008, 0x06000013,
|
||||
0xd8dc1008, 0x07000013, 0xbf8cc07f, 0x7e300306, 0x7e320306, 0x7e340306, 0x7e360306, 0x34142484,
|
||||
0xe0781000, 0x0403180a, 0x4a162481, 0x16161690, 0x4c1616ff, 0x00008000, 0xe0741000, 0x0202070b,
|
||||
0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 36> DomainPatchRead{
|
||||
0xf8000941, 0x00000000, 0x34020e84, 0x16020283, 0xe0341000, 0x04030a01, 0xe0341010, 0x04030c01,
|
||||
0xe0341020, 0x04030e01, 0x4a060e81, 0x16060690, 0x4c0606ff, 0x00008000, 0xe0341000, 0x04030c03,
|
||||
0xbf8c3f70, 0x08040af2, 0x08040d02, 0x10201505, 0x3e201906, 0x3e201d02, 0x10221705, 0x3e221b06,
|
||||
0x3e221f02, 0xd5510012, 0x040a0d05, 0x102424ff, 0x40400000, 0x7e260280, 0x7e2802f2, 0xf80008cf,
|
||||
0x14131110, 0xf800020f, 0x14131214, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 32> DomainCulledPrimitive{
|
||||
0x380000ff, 0x80000000, 0xf8000941, 0x00000000, 0x34020e84, 0x16020283, 0xe0341000, 0x04030a01,
|
||||
0xe0341010, 0x04030c01, 0xe0341020, 0x04030e01, 0xbf8c3f70, 0x08040af2, 0x08040d02, 0x10201505,
|
||||
0x3e201906, 0x3e201d02, 0x10221705, 0x3e221b06, 0x3e221f02, 0xd5510012, 0x040a0d05, 0x102424ff,
|
||||
0x40400000, 0x7e260280, 0x7e2802f2, 0xf80008cf, 0x14131110, 0xf800020f, 0x14131214, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 7> PixelCode{
|
||||
0xc8020002, 0xc8060102, 0xc80a0202, 0xc80e0302, 0xf800180f, 0x03020100, 0xbf810000,
|
||||
};
|
||||
|
||||
constexpr ShaderRecompiler::TessellationConfiguration Patches{3u, 3u, 1u, 2u, 2u};
|
||||
constexpr std::array<std::uint32_t, 3> PatchLevels{1u, 0u, 5u};
|
||||
|
||||
constexpr std::uint16_t Corner(std::uint32_t patch, std::uint32_t x, std::uint32_t y) {
|
||||
return static_cast<std::uint16_t>((PatchLevels[patch] << 8u) | (patch << 4u) | (x << 2u) | y);
|
||||
}
|
||||
|
||||
alignas(256) constexpr std::array<std::uint16_t, 9> Indices{
|
||||
Corner(0, 0, 0), Corner(0, 2, 0), Corner(0, 1, 2),
|
||||
Corner(1, 0, 0), Corner(1, 2, 0), Corner(1, 1, 2),
|
||||
Corner(2, 0, 0), Corner(2, 2, 0), Corner(2, 1, 2),
|
||||
};
|
||||
|
||||
struct Stages {
|
||||
ShaderRecompiler::RecompileResult local;
|
||||
ShaderRecompiler::RecompileResult hull;
|
||||
ShaderRecompiler::RecompileResult domain;
|
||||
ShaderRecompiler::RecompileResult fragment;
|
||||
};
|
||||
|
||||
ShaderRecompiler::RecompileResult Compile(const ShaderRecompiler::SpirvTarget& target, ShaderStage stage, std::span<const std::uint32_t> code, std::uint32_t& pushCursor) {
|
||||
static const std::array<std::uint32_t, 16> hullUserData{0x1000u};
|
||||
static const std::array<std::uint32_t, 8> domainUserData{};
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
|
||||
const bool hull = stage == ShaderStage::TessellationControl;
|
||||
const auto userData = hull ? std::span<const std::uint32_t>(hullUserData) : stage == ShaderStage::TessellationEvaluation ? std::span<const std::uint32_t>(domainUserData) : std::span<const std::uint32_t>();
|
||||
std::optional<ShaderRecompiler::ShaderPixelStageInfo> pixel;
|
||||
std::optional<ShaderRecompiler::ShaderVertexStageInfo> vertex;
|
||||
std::optional<ShaderRecompiler::GraphicsCompileContext> graphics;
|
||||
if (stage == ShaderStage::Fragment) {
|
||||
ShaderRecompiler::ShaderPixelStageInfo info{};
|
||||
info.interpolatorCount = 1;
|
||||
info.interpolatorSettings[0] = 0x400u;
|
||||
info.targetOutputMode[0] = 9;
|
||||
info.targetExportMapping.fill(0xe4u);
|
||||
pixel = info;
|
||||
} else {
|
||||
vertex = ShaderRecompiler::ShaderVertexStageInfo{};
|
||||
graphics = ShaderRecompiler::GraphicsCompileContext{hull ? 0u : 8u, {}, std::nullopt, Patches, {reinterpret_cast<std::uintptr_t>(Indices.data()), static_cast<std::uint32_t>(Indices.size()), 2, 1}};
|
||||
}
|
||||
const ShaderRecompiler::RecompileRequest request{
|
||||
{stage, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
|
||||
{64, hull ? 0u : 8u, userData, std::nullopt, pixel, vertex, memory},
|
||||
target,
|
||||
{0, 0, pushCursor, AgcDriver::Graphics::PipelinePushConstantBytes - pushCursor},
|
||||
graphics
|
||||
};
|
||||
auto result = ShaderRecompiler::Recompile(request);
|
||||
pushCursor += static_cast<std::uint32_t>(result.pushConstants.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
void ExpectRefusal(const ShaderRecompiler::SpirvTarget& target, ShaderStage stage, std::span<const std::uint32_t> code, const std::string& reason) {
|
||||
std::uint32_t cursor = 0;
|
||||
try {
|
||||
static_cast<void>(Compile(target, stage, code, cursor));
|
||||
} catch (const std::exception& error) {
|
||||
Require(std::string(error.what()).find(reason) != std::string::npos, "the refusal named another reason: " + std::string(error.what()));
|
||||
return;
|
||||
}
|
||||
Require(false, "a tessellation program was accepted although " + reason);
|
||||
}
|
||||
|
||||
std::array<std::uint8_t, 4> PixelAt(std::uint32_t x, std::uint32_t y) {
|
||||
const auto offset = (static_cast<std::size_t>(y) * Width + x) * 4u;
|
||||
return {std::to_integer<std::uint8_t>(Pixels[offset]), std::to_integer<std::uint8_t>(Pixels[offset + 1]), std::to_integer<std::uint8_t>(Pixels[offset + 2]), std::to_integer<std::uint8_t>(Pixels[offset + 3])};
|
||||
}
|
||||
|
||||
std::string PixelText(const std::array<std::uint8_t, 4>& pixel) {
|
||||
char text[64];
|
||||
std::snprintf(text, sizeof(text), "(%u, %u, %u, %u)", pixel[0], pixel[1], pixel[2], pixel[3]);
|
||||
return text;
|
||||
}
|
||||
|
||||
std::uint32_t Column(std::uint32_t patch, float offset) {
|
||||
return static_cast<std::uint32_t>((-0.9f + 0.6f * static_cast<float>(patch) + offset + 1.0f) * 0.5f * Width);
|
||||
}
|
||||
|
||||
std::uint32_t Row(float y) {
|
||||
return static_cast<std::uint32_t>((1.0f - y) * 0.5f * Height);
|
||||
}
|
||||
|
||||
void Draw(AgcDriver::VulkanDevice& device, const Stages& stages, VkCullModeFlags cull, VkFrontFace front) {
|
||||
for (std::size_t i = 0; i < Pixels.size(); i += 4) {
|
||||
for (std::size_t c = 0; c < 4; ++c) Pixels[i + c] = std::byte{Background[c]};
|
||||
}
|
||||
std::uint32_t cursor = 0;
|
||||
const auto offset = [&](const ShaderRecompiler::RecompileResult& result) {
|
||||
const auto at = result.pushConstants.empty() ? 0u : cursor;
|
||||
cursor += static_cast<std::uint32_t>(result.pushConstants.size());
|
||||
return at;
|
||||
};
|
||||
const std::array<AgcDriver::Graphics::CompiledShader, 4> shaders{{
|
||||
{ShaderStage::Local, &stages.local, offset(stages.local)},
|
||||
{ShaderStage::TessellationControl, &stages.hull, offset(stages.hull)},
|
||||
{ShaderStage::TessellationEvaluation, &stages.domain, offset(stages.domain)},
|
||||
{ShaderStage::Fragment, &stages.fragment, offset(stages.fragment)},
|
||||
}};
|
||||
AgcDriver::Graphics::State state{};
|
||||
state.stages = {AgcDriver::Graphics::ShaderPath::Tessellation, 0x0200210du, 64, 64, std::nullopt, Patches};
|
||||
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_PATCH_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 = cull;
|
||||
state.frontFace = front;
|
||||
state.blend.colorWriteMask = 15;
|
||||
state.blends = {state.blend};
|
||||
state.blendConstants = {};
|
||||
device.Draw(state, {reinterpret_cast<std::uintptr_t>(Indices.data()), static_cast<std::uint32_t>(Indices.size()), 2, 1, 0, true}, shaders);
|
||||
device.WaitIdle();
|
||||
}
|
||||
|
||||
std::uint32_t CoveredPixels(std::uint32_t patch, bool interior) {
|
||||
std::uint32_t count = 0;
|
||||
for (std::uint32_t y = Row(0.5f); y <= Row(-0.5f) && y < Height; ++y) {
|
||||
for (std::uint32_t x = Column(patch, 0.0f); x <= Column(patch, 0.5f) && x < Width; ++x) {
|
||||
const auto pixel = PixelAt(x, y);
|
||||
if (pixel == Background) continue;
|
||||
Require(pixel[0] == 255 && pixel[2] == 0 && pixel[3] == 255, "patch " + std::to_string(patch) + " pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") is " + PixelText(pixel));
|
||||
if (!interior || pixel[1] > 32) ++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void RequirePatches(AgcDriver::VulkanDevice& device, const ShaderRecompiler::SpirvTarget& target, std::span<const std::uint32_t> hull, const std::string& program) {
|
||||
std::uint32_t cursor = 0;
|
||||
const Stages stages{
|
||||
Compile(target, ShaderStage::Local, LocalCode, cursor),
|
||||
Compile(target, ShaderStage::TessellationControl, hull, cursor),
|
||||
Compile(target, ShaderStage::TessellationEvaluation, DomainCode, cursor),
|
||||
Compile(target, ShaderStage::Fragment, PixelCode, cursor),
|
||||
};
|
||||
const auto center = Row(-0.5f + 1.0f / 3.0f);
|
||||
for (const auto cull : {VK_CULL_MODE_NONE, VK_CULL_MODE_BACK_BIT}) {
|
||||
Draw(device, stages, cull, VK_FRONT_FACE_COUNTER_CLOCKWISE);
|
||||
const std::string name = program + (cull == VK_CULL_MODE_NONE ? ", unculled" : ", back-face culled");
|
||||
Require(CoveredPixels(0, false) > 100 && CoveredPixels(0, true) == 0, name + ": the level-1 patch was not drawn as one flat triangle, centre " + PixelText(PixelAt(Column(0, 0.25f), center)));
|
||||
Require(CoveredPixels(1, false) == 0, name + ": the patch with level 0 was not culled");
|
||||
Require(CoveredPixels(2, false) > 100 && PixelAt(Column(2, 0.25f), center)[1] > 32, name + ": the level-5 patch has no interior domain points at its centre, " + PixelText(PixelAt(Column(2, 0.25f), center)));
|
||||
Require(PixelAt(0, 0) == Background && PixelAt(Width - 1, Height - 1) == Background, name + ": the corners changed");
|
||||
}
|
||||
Draw(device, stages, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE);
|
||||
Require(CoveredPixels(0, false) == 0 && CoveredPixels(2, false) == 0, program + ": tessellated triangles did not keep the control points' winding");
|
||||
}
|
||||
|
||||
struct RegisteredStage {
|
||||
Shader shader{};
|
||||
std::array<ShaderRegister, 6> registers{};
|
||||
std::array<ShaderRegister, 4> context{};
|
||||
ShaderSpecialRegs specials{};
|
||||
ShaderUserData users{};
|
||||
};
|
||||
|
||||
template<std::size_t TCount>
|
||||
void Describe(RegisteredStage& stage, const std::array<std::uint32_t, TCount>& code, std::uint8_t type, std::initializer_list<ShaderRegister> registers, std::initializer_list<ShaderRegister> context, std::uint32_t routing) {
|
||||
stage.shader.file_header = 0x34333231u;
|
||||
stage.shader.version = 0x18u;
|
||||
stage.shader.header_size = sizeof(RegisteredStage);
|
||||
stage.shader.shader_size = sizeof(code);
|
||||
stage.shader.code = code.data();
|
||||
stage.shader.type = type;
|
||||
stage.shader.user_data = &stage.users;
|
||||
std::copy(registers.begin(), registers.end(), stage.registers.begin());
|
||||
stage.shader.sh_registers = stage.registers.data();
|
||||
stage.shader.num_sh_registers = static_cast<std::uint8_t>(registers.size());
|
||||
std::copy(context.begin(), context.end(), stage.context.begin());
|
||||
stage.shader.cx_registers = stage.context.data();
|
||||
stage.shader.num_cx_registers = static_cast<std::uint8_t>(context.size());
|
||||
stage.specials.ge_cntl = {0x25bu, 0u};
|
||||
stage.specials.vgt_shader_stages_en = {0x2d5u, routing};
|
||||
stage.specials.vgt_gs_out_prim_type = {0x29bu, 0u};
|
||||
stage.specials.ge_user_vgpr_en = {0x262u, 0u};
|
||||
stage.shader.specials = &stage.specials;
|
||||
}
|
||||
|
||||
void RequireRegisteredStages() {
|
||||
const auto low = [](const auto& code) { return static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(code.data()) >> 8u); };
|
||||
const auto high = [](const auto& code) { return static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(code.data()) >> 40u); };
|
||||
RegisteredStage local;
|
||||
RegisteredStage hull;
|
||||
RegisteredStage domain;
|
||||
Describe(local, LocalCode, 5u, {{0x10au, 0u}, {0x10bu, 0u}}, {}, 0u);
|
||||
Describe(hull, HullCode, 7u, {{0x148u, 0u}, {0x149u, 0u}, {0x10au, 0u}, {0x10bu, 16u}, {0x108u, low(HullCode)}, {0x109u, high(HullCode)}}, {{0x2dbu, 0x40049u}, {0x286u, 0x42800000u}, {0x287u, 0x3f800000u}, {0x2d6u, 0xc355u}}, 0x105u);
|
||||
Describe(domain, DomainCode, 2u, {{0xc8u, low(DomainCode)}, {0xc9u, high(DomainCode)}, {0x8au, 0u}, {0x8bu, 16u}}, {}, 0x02002008u);
|
||||
for (const auto* stage : {&local, &hull, &domain}) AgcDriverRegisterShader_nid_postfix(&stage->shader);
|
||||
const std::array<const Shader*, 2> stages{&hull.shader, &domain.shader};
|
||||
const std::array<ShaderRegister, 2> context{{{0x2d5u, 0x0200210du}, {0x29bu, 0u}}};
|
||||
const std::array<ShaderRegister, 3> primitive{{{0x25bu, 0u}, {0x262u, 0u}, {0x242u, 9u}}};
|
||||
AgcDriverResolveGraphicsStagesAbi_nid_postfix(stages, context, primitive);
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
const auto target = device->Target();
|
||||
if (!target.tessellation.has_value()) {
|
||||
std::puts("Tessellation tests skipped: the device has no tessellation shaders");
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
ExpectRefusal(target, ShaderStage::TessellationControl, HullUnguardedFactors, "not written by control point 0 alone");
|
||||
ExpectRefusal(target, ShaderStage::TessellationControl, HullSquaredAddress, "not affine");
|
||||
ExpectRefusal(target, ShaderStage::TessellationControl, HullAndSaveexec, "lane mask");
|
||||
ExpectRefusal(target, ShaderStage::TessellationEvaluation, DomainPatchRead, "patch constants");
|
||||
ExpectRefusal(target, ShaderStage::TessellationEvaluation, DomainCulledPrimitive, "passthrough primitive");
|
||||
ExpectRefusal(target, ShaderStage::Local, LocalIndexAsData, "outside a tessellation ring or LDS address");
|
||||
|
||||
RequirePatches(*device, target, HullCode, "hull program");
|
||||
RequirePatches(*device, target, HullRestoredExecSaved, "hull program saving its restored exec");
|
||||
device.reset();
|
||||
RequireRegisteredStages();
|
||||
std::puts("Tessellation tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -99,6 +99,7 @@ function(add_shader_recompiler target runtimeTarget)
|
|||
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/HostInterpolationChecker.cpp
|
||||
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/ReadLaneEliminator.cpp
|
||||
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/MaskedSelectEliminator.cpp
|
||||
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/TessellationLowering.cpp
|
||||
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/ResourceTracker.cpp
|
||||
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/ResourceMaterializer.cpp
|
||||
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Optimization/src/BindingAllocator.cpp
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ struct IrProgramMetadata {
|
|||
bool shaderInfoComplete = false;
|
||||
IrBindingLayout bindings;
|
||||
bool bindingLayoutComplete = false;
|
||||
std::uint32_t tessellationLocalStride = 0;
|
||||
std::uint32_t tessellationControlStride = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,13 @@ enum class TessellationAttribute {
|
|||
Factor
|
||||
};
|
||||
|
||||
enum class TessellationBaseKind {
|
||||
OffChip,
|
||||
Factor,
|
||||
RelativeIndex,
|
||||
PassthroughPrimitive
|
||||
};
|
||||
|
||||
enum class StageInputKind {
|
||||
VertexIndex,
|
||||
InvocationId,
|
||||
|
|
|
|||
|
|
@ -109,8 +109,6 @@ struct ShaderMeshInputInfo: ShaderWorkgroupInputInfo {
|
|||
struct ShaderTessellationInputInfo {
|
||||
std::uint32_t inputControlPoints = 0;
|
||||
std::uint32_t outputControlPoints = 0;
|
||||
std::uint32_t lsStride = 0;
|
||||
std::uint32_t hsStride = 0;
|
||||
std::uint32_t domain = 0;
|
||||
std::uint32_t partitioning = 0;
|
||||
std::uint32_t outputTopology = 0;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef CORE_SHADER_RECOMPILIER_OPTIMIZATION_INCLUDE_OPTIMIZATION_TESSELLATIONLOWERING_HPP
|
||||
#define CORE_SHADER_RECOMPILIER_OPTIMIZATION_INCLUDE_OPTIMIZATION_TESSELLATIONLOWERING_HPP
|
||||
|
||||
#include "IntermediateRepresentation/IrProgram.hpp"
|
||||
#include "Optimization/ShaderStageInputInfo.hpp"
|
||||
|
||||
namespace ShaderRecompiler {
|
||||
|
||||
class TessellationLowering {
|
||||
public:
|
||||
void Lower(IrProgram& program, const ShaderTessellationInputInfo& tessellation) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,621 @@
|
|||
#include "Optimization/TessellationLowering.hpp"
|
||||
#include "Optimization/DeadCodeEliminator.hpp"
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace ShaderRecompiler {
|
||||
namespace {
|
||||
|
||||
constexpr std::int64_t PatchIndexCount = 256;
|
||||
constexpr std::int64_t LocalVertexIndexCount = 8192;
|
||||
constexpr std::int64_t TriangleFactorBytes = 16;
|
||||
constexpr std::int64_t AddressLimit = 0xffffffffll;
|
||||
|
||||
struct Affine {
|
||||
std::int64_t vertex = 0;
|
||||
std::int64_t relative = 0;
|
||||
std::int64_t constant = 0;
|
||||
|
||||
[[nodiscard]] bool Scalar() const { return vertex == 0 && relative == 0; }
|
||||
bool operator==(const Affine& other) const = default;
|
||||
};
|
||||
|
||||
[[noreturn]] void Fail(const char* reason, std::uint32_t pc) {
|
||||
char text[256];
|
||||
std::snprintf(text, sizeof(text), "tessellation lowering: %s at pc 0x%x", reason, pc);
|
||||
throw std::runtime_error(text);
|
||||
}
|
||||
|
||||
bool IsImmediate(const IrValue* raw, std::uint32_t expected) {
|
||||
const IrValue* value = raw->Resolve();
|
||||
return value->HasImmediate() && value->Type() == IrType::U32 && value->ImmediateU32() == expected;
|
||||
}
|
||||
|
||||
bool IsBase(const IrValue* raw, TessellationBaseKind kind) {
|
||||
const IrValue* value = raw->Resolve();
|
||||
return value->Opcode() == IrOpcode::TessellationBase && IsImmediate(value->Argument(0), static_cast<std::uint32_t>(kind));
|
||||
}
|
||||
|
||||
std::int64_t FloorDiv(std::int64_t value, std::int64_t divisor) {
|
||||
const auto quotient = value / divisor;
|
||||
return quotient * divisor > value ? quotient - 1 : quotient;
|
||||
}
|
||||
|
||||
IrValue* VariableOperand(IrValue* value, std::uint32_t constant) {
|
||||
if (IsImmediate(value->Argument(1), constant)) return value->Argument(0)->Resolve();
|
||||
if (IsImmediate(value->Argument(0), constant)) return value->Argument(1)->Resolve();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool IsLaneId(const IrValue* value) {
|
||||
return value != nullptr && value->Resolve()->Opcode() == IrOpcode::LaneId;
|
||||
}
|
||||
|
||||
IrValue* BallotSource(IrValue* raw, std::uint32_t word) {
|
||||
IrValue* value = raw->Resolve();
|
||||
if (value->Opcode() != IrOpcode::CompositeExtractU32x4 || !IsImmediate(value->Argument(1), word)) return nullptr;
|
||||
IrValue* ballot = value->Argument(0)->Resolve();
|
||||
return ballot->Opcode() == IrOpcode::Ballot ? ballot->Argument(0)->Resolve() : nullptr;
|
||||
}
|
||||
|
||||
bool IsTrue(const IrValue* value) {
|
||||
return value != nullptr && value->HasImmediate() && value->Type() == IrType::Bool && value->ImmediateBool();
|
||||
}
|
||||
|
||||
IrValue* HostLaneWord(IrValue* raw, std::uint32_t word) {
|
||||
IrValue* value = raw->Resolve();
|
||||
while (value->Opcode() == IrOpcode::BitwiseAnd32 && IsTrue(BallotSource(value->Argument(1), word))) value = value->Argument(0)->Resolve();
|
||||
return value;
|
||||
}
|
||||
|
||||
IrValue* SavedLaneBit(IrValue* value) {
|
||||
if (value->Opcode() != IrOpcode::INotEqual32) return nullptr;
|
||||
IrValue* masked = VariableOperand(value, 0u);
|
||||
if (masked == nullptr || masked->Opcode() != IrOpcode::BitwiseAnd32) return nullptr;
|
||||
IrValue* shifted = VariableOperand(masked, 1u);
|
||||
if (shifted == nullptr || shifted->Opcode() != IrOpcode::ShiftRightLogical32) return nullptr;
|
||||
IrValue* bit = shifted->Argument(1)->Resolve();
|
||||
if (bit->Opcode() != IrOpcode::BitwiseAnd32 || !IsLaneId(VariableOperand(bit, 31u))) return nullptr;
|
||||
IrValue* word = shifted->Argument(0)->Resolve();
|
||||
if (word->Opcode() != IrOpcode::SelectU32) return BallotSource(HostLaneWord(word, 0u), 0u);
|
||||
IrValue* low = word->Argument(0)->Resolve();
|
||||
if (low->Opcode() != IrOpcode::ULessThan32 || !IsLaneId(low->Argument(0)) || !IsImmediate(low->Argument(1), 32u)) return nullptr;
|
||||
IrValue* source = BallotSource(HostLaneWord(word->Argument(1), 0u), 0u);
|
||||
return source != nullptr && BallotSource(HostLaneWord(word->Argument(2), 1u), 1u) == source ? source : nullptr;
|
||||
}
|
||||
|
||||
bool Implies(IrValue* rawPredicate, IrValue* rawCondition) {
|
||||
IrValue* predicate = rawPredicate->Resolve();
|
||||
IrValue* condition = rawCondition->Resolve();
|
||||
if (predicate == condition) return true;
|
||||
if (IsTrue(condition)) return true;
|
||||
return predicate->Opcode() == IrOpcode::LogicalAnd && (Implies(predicate->Argument(0), condition) || Implies(predicate->Argument(1), condition));
|
||||
}
|
||||
|
||||
class AddressEvaluator {
|
||||
public:
|
||||
AddressEvaluator(std::uint32_t vertexCount, std::int64_t relativeCount) : vertexCount(vertexCount), relativeCount(relativeCount) {}
|
||||
|
||||
std::optional<Affine> Evaluate(IrValue* value, IrValue* predicate) {
|
||||
memo.clear();
|
||||
active = predicate;
|
||||
return evaluate(value);
|
||||
}
|
||||
|
||||
bool IsInvocation(IrValue* value) {
|
||||
const auto address = Evaluate(value, nullptr);
|
||||
return address && *address == Affine{1, 0, 0};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::int64_t Lowest(const Affine& value) const {
|
||||
return value.constant + std::min<std::int64_t>(0, value.vertex * lastVertex()) + std::min<std::int64_t>(0, value.relative * (relativeCount - 1));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::int64_t Highest(const Affine& value) const {
|
||||
return value.constant + std::max<std::int64_t>(0, value.vertex * lastVertex()) + std::max<std::int64_t>(0, value.relative * (relativeCount - 1));
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::int64_t lastVertex() const { return vertexCount == 0 ? 0 : static_cast<std::int64_t>(vertexCount) - 1; }
|
||||
|
||||
[[nodiscard]] std::optional<Affine> bounded(const Affine& value) const {
|
||||
if (Lowest(value) < 0 || Highest(value) > AddressLimit) return std::nullopt;
|
||||
return value;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<Affine> scale(const Affine& value, std::int64_t factor) const {
|
||||
if (factor < 0 || factor >= (1ll << 31)) return std::nullopt;
|
||||
return bounded({value.vertex * factor, value.relative * factor, value.constant * factor});
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<std::pair<Affine, Affine>> split(const Affine& value, std::int64_t divisor) const {
|
||||
Affine high;
|
||||
Affine low;
|
||||
(value.vertex % divisor == 0 ? high.vertex : low.vertex) = value.vertex;
|
||||
(value.relative % divisor == 0 ? high.relative : low.relative) = value.relative;
|
||||
high.constant = FloorDiv(value.constant, divisor) * divisor;
|
||||
low.constant = value.constant - high.constant;
|
||||
if (Lowest(low) < 0 || Highest(low) >= divisor) return std::nullopt;
|
||||
return std::pair{high, low};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<Affine> shiftRight(const Affine& value, std::int64_t shift) const {
|
||||
if (shift < 0 || shift >= 32) return std::nullopt;
|
||||
const auto divisor = 1ll << shift;
|
||||
const auto parts = split(value, divisor);
|
||||
if (!parts) return std::nullopt;
|
||||
return bounded({parts->first.vertex / divisor, parts->first.relative / divisor, parts->first.constant / divisor});
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<Affine> mask(const Affine& value, std::int64_t bits) const {
|
||||
if (value.Scalar()) return Affine{0, 0, value.constant & bits};
|
||||
const auto width = std::bit_width(static_cast<std::uint64_t>(bits));
|
||||
if (width >= 32) return bits == AddressLimit ? std::optional(value) : std::nullopt;
|
||||
const auto divisor = 1ll << width;
|
||||
const auto parts = split(value, divisor);
|
||||
if (!parts) return std::nullopt;
|
||||
if (parts->second.Scalar()) return Affine{0, 0, parts->second.constant & bits};
|
||||
return bits == divisor - 1 ? std::optional(parts->second) : std::nullopt;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<Affine> disjoint(const Affine& high, const Affine& low) const {
|
||||
if (Lowest(low) < 0) return std::nullopt;
|
||||
const auto divisor = 1ll << std::bit_width(static_cast<std::uint64_t>(Highest(low)));
|
||||
if (high.vertex % divisor != 0 || high.relative % divisor != 0 || high.constant % divisor != 0) return std::nullopt;
|
||||
return bounded({high.vertex + low.vertex, high.relative + low.relative, high.constant + low.constant});
|
||||
}
|
||||
|
||||
std::optional<Affine> evaluate(IrValue* raw) {
|
||||
IrValue* value = raw->Resolve();
|
||||
if (value->HasImmediate()) {
|
||||
if (value->Type() != IrType::U32) return std::nullopt;
|
||||
return Affine{0, 0, value->ImmediateU32()};
|
||||
}
|
||||
if (const auto found = memo.find(value); found != memo.end()) return found->second;
|
||||
memo.emplace(value, std::nullopt);
|
||||
const auto result = compute(value);
|
||||
memo[value] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<Affine> compute(IrValue* value) {
|
||||
const auto argument = [&](std::size_t index) { return evaluate(value->Argument(index)); };
|
||||
const auto scalar = [&](std::size_t index) -> std::optional<std::int64_t> {
|
||||
const auto operand = argument(index);
|
||||
if (!operand || !operand->Scalar()) return std::nullopt;
|
||||
return operand->constant;
|
||||
};
|
||||
switch (value->Opcode()) {
|
||||
case IrOpcode::TessellationBase:
|
||||
return IsBase(value, TessellationBaseKind::RelativeIndex) ? std::optional(Affine{0, 1, 0}) : std::nullopt;
|
||||
case IrOpcode::GetBuiltin:
|
||||
return vertexCount != 0 && IsImmediate(value->Argument(0), static_cast<std::uint32_t>(StageInputKind::InvocationId)) ? std::optional(Affine{1, 0, 0}) : std::nullopt;
|
||||
case IrOpcode::IAdd32:
|
||||
case IrOpcode::ISub32: {
|
||||
const auto lhs = argument(0);
|
||||
const auto rhs = argument(1);
|
||||
if (!lhs || !rhs) return std::nullopt;
|
||||
const std::int64_t sign = value->Opcode() == IrOpcode::IAdd32 ? 1 : -1;
|
||||
return bounded({lhs->vertex + sign * rhs->vertex, lhs->relative + sign * rhs->relative, lhs->constant + sign * rhs->constant});
|
||||
}
|
||||
case IrOpcode::IMul32: {
|
||||
const auto lhs = argument(0);
|
||||
const auto rhs = argument(1);
|
||||
if (!lhs || !rhs) return std::nullopt;
|
||||
if (rhs->Scalar()) return scale(*lhs, rhs->constant);
|
||||
if (lhs->Scalar()) return scale(*rhs, lhs->constant);
|
||||
return std::nullopt;
|
||||
}
|
||||
case IrOpcode::ShiftLeftLogical32: {
|
||||
const auto lhs = argument(0);
|
||||
const auto shift = scalar(1);
|
||||
if (!lhs || !shift || *shift >= 31) return std::nullopt;
|
||||
return scale(*lhs, 1ll << *shift);
|
||||
}
|
||||
case IrOpcode::ShiftRightLogical32: {
|
||||
const auto lhs = argument(0);
|
||||
const auto shift = scalar(1);
|
||||
if (!lhs || !shift) return std::nullopt;
|
||||
return shiftRight(*lhs, *shift);
|
||||
}
|
||||
case IrOpcode::BitwiseAnd32: {
|
||||
const auto lhs = argument(0);
|
||||
const auto rhs = argument(1);
|
||||
if (!lhs || !rhs) return std::nullopt;
|
||||
if (rhs->Scalar()) return mask(*lhs, rhs->constant);
|
||||
if (lhs->Scalar()) return mask(*rhs, lhs->constant);
|
||||
return std::nullopt;
|
||||
}
|
||||
case IrOpcode::BitwiseOr32: {
|
||||
const auto lhs = argument(0);
|
||||
const auto rhs = argument(1);
|
||||
if (!lhs || !rhs) return std::nullopt;
|
||||
if (const auto result = disjoint(*lhs, *rhs)) return result;
|
||||
return disjoint(*rhs, *lhs);
|
||||
}
|
||||
case IrOpcode::BitFieldUExtract: {
|
||||
const auto source = argument(0);
|
||||
const auto offset = scalar(1);
|
||||
const auto count = scalar(2);
|
||||
if (!source || !offset || !count) return std::nullopt;
|
||||
if (*count == 0) return Affine{};
|
||||
const auto shifted = shiftRight(*source, *offset);
|
||||
if (!shifted || *count >= 32) return shifted;
|
||||
return mask(*shifted, (1ll << *count) - 1);
|
||||
}
|
||||
case IrOpcode::SelectU32: {
|
||||
if (active != nullptr && Implies(active, value->Argument(0))) return argument(1);
|
||||
const auto lhs = argument(1);
|
||||
const auto rhs = argument(2);
|
||||
return lhs && rhs && *lhs == *rhs ? lhs : std::nullopt;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!value->IsPhi() || value->ArgumentCount() == 0) return std::nullopt;
|
||||
const auto first = argument(0);
|
||||
for (std::size_t index = 1; first && index < value->ArgumentCount(); ++index) {
|
||||
const auto other = argument(index);
|
||||
if (!other || !(*other == *first)) return std::nullopt;
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
std::uint32_t vertexCount;
|
||||
std::int64_t relativeCount;
|
||||
IrValue* active = nullptr;
|
||||
std::unordered_map<IrValue*, std::optional<Affine>> memo;
|
||||
};
|
||||
|
||||
bool FirstInvocationOnly(IrValue* raw, AddressEvaluator& evaluator, std::unordered_set<IrValue*>& path) {
|
||||
IrValue* value = raw->Resolve();
|
||||
if (value->HasImmediate()) return value->Type() == IrType::Bool && !value->ImmediateBool();
|
||||
if (!path.insert(value).second) return true;
|
||||
const auto invocation = [&](std::size_t index) { return evaluator.IsInvocation(value->Argument(index)); };
|
||||
const auto immediate = [&](std::size_t index, std::uint32_t expected) { return IsImmediate(value->Argument(index), expected); };
|
||||
const bool result = [&] {
|
||||
switch (value->Opcode()) {
|
||||
case IrOpcode::LogicalAnd:
|
||||
return FirstInvocationOnly(value->Argument(0), evaluator, path) || FirstInvocationOnly(value->Argument(1), evaluator, path);
|
||||
case IrOpcode::ULessThan32:
|
||||
return invocation(0) && immediate(1, 1u);
|
||||
case IrOpcode::UGreaterThan32:
|
||||
return immediate(0, 1u) && invocation(1);
|
||||
case IrOpcode::ULessThanEqual32:
|
||||
return invocation(0) && immediate(1, 0u);
|
||||
case IrOpcode::UGreaterThanEqual32:
|
||||
return immediate(0, 0u) && invocation(1);
|
||||
case IrOpcode::IEqual32:
|
||||
return (invocation(0) && immediate(1, 0u)) || (immediate(0, 0u) && invocation(1));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!value->IsPhi() || value->ArgumentCount() == 0) return false;
|
||||
for (std::size_t index = 0; index < value->ArgumentCount(); ++index) {
|
||||
if (!FirstInvocationOnly(value->Argument(index), evaluator, path)) return false;
|
||||
}
|
||||
return true;
|
||||
}();
|
||||
path.erase(value);
|
||||
return result;
|
||||
}
|
||||
|
||||
struct Access {
|
||||
IrBlock* block = nullptr;
|
||||
IrValue* inst = nullptr;
|
||||
TessellationAttribute kind = TessellationAttribute::LocalOutput;
|
||||
bool write = false;
|
||||
std::uint32_t components = 1;
|
||||
Affine address;
|
||||
IrValue* predicate = nullptr;
|
||||
std::uint32_t pc = 0;
|
||||
};
|
||||
|
||||
std::uint32_t ComponentCount(IrOpcode opcode) {
|
||||
switch (opcode) {
|
||||
case IrOpcode::LoadSharedU32x2:
|
||||
case IrOpcode::WriteSharedU32x2:
|
||||
case IrOpcode::LoadBufferU32x2:
|
||||
case IrOpcode::StoreBufferU32x2:
|
||||
return 2u;
|
||||
case IrOpcode::LoadSharedU32x3:
|
||||
case IrOpcode::WriteSharedU32x3:
|
||||
case IrOpcode::LoadBufferU32x3:
|
||||
case IrOpcode::StoreBufferU32x3:
|
||||
return 3u;
|
||||
case IrOpcode::LoadSharedU32x4:
|
||||
case IrOpcode::WriteSharedU32x4:
|
||||
case IrOpcode::LoadBufferU32x4:
|
||||
case IrOpcode::StoreBufferU32x4:
|
||||
return 4u;
|
||||
case IrOpcode::LoadSharedU32:
|
||||
case IrOpcode::WriteSharedU32:
|
||||
case IrOpcode::LoadBufferU32:
|
||||
case IrOpcode::StoreBufferU32:
|
||||
return 1u;
|
||||
default:
|
||||
return 0u;
|
||||
}
|
||||
}
|
||||
|
||||
class Lowering {
|
||||
public:
|
||||
Lowering(IrProgram& program, const ShaderTessellationInputInfo& tessellation)
|
||||
: program(program), tessellation(tessellation), stage(program.Resources().stage), evaluator(stage == IrShaderStage::TessellationControl ? tessellation.outputControlPoints : 0u, stage == IrShaderStage::Local ? LocalVertexIndexCount : PatchIndexCount) {}
|
||||
|
||||
void Run() {
|
||||
for (IrBlock* block : program.BlockOrder()) {
|
||||
for (IrValue* inst : block->Instructions()) {
|
||||
if (IrValue* source = SavedLaneBit(inst)) inst->ReplaceAllUsesWith(source);
|
||||
}
|
||||
}
|
||||
collect();
|
||||
derive();
|
||||
for (const auto& access : accesses) rewrite(access);
|
||||
if (stage == IrShaderStage::TessellationEvaluation) removePrimitiveExports();
|
||||
constexpr DeadCodeEliminator deadCodeEliminator;
|
||||
deadCodeEliminator.Eliminate(program);
|
||||
std::vector<IrValue*> relative;
|
||||
for (IrBlock* block : program.BlockOrder()) {
|
||||
for (IrValue* inst : block->Instructions()) {
|
||||
if (stage == IrShaderStage::TessellationControl && inst->Opcode() == IrOpcode::Ballot && inst->HasUses()) Fail("the hull program uses a lane mask other than its own lane's bit", 0u);
|
||||
if (inst->Opcode() != IrOpcode::TessellationBase || !inst->HasUses()) continue;
|
||||
if (IsBase(inst, TessellationBaseKind::RelativeIndex)) {
|
||||
relative.push_back(inst);
|
||||
continue;
|
||||
}
|
||||
if (IsBase(inst, TessellationBaseKind::PassthroughPrimitive)) Fail("the primitive VGPR is used other than as the passthrough primitive export", 0u);
|
||||
Fail("a tessellation ring base is used outside a lowered ring access", 0u);
|
||||
}
|
||||
}
|
||||
requireRelativeIndependence(relative);
|
||||
for (IrValue* base : relative) base->ReplaceAllUsesWith(&constant(0u));
|
||||
deadCodeEliminator.Eliminate(program);
|
||||
program.Metadata().tessellationLocalStride = localStride;
|
||||
program.Metadata().tessellationControlStride = controlStride;
|
||||
}
|
||||
|
||||
private:
|
||||
void collect() {
|
||||
const auto& memoryInfo = program.Resources().memoryInfo;
|
||||
for (IrBlock* block : program.BlockOrder()) {
|
||||
for (IrValue* inst : block->Instructions()) {
|
||||
const auto opcode = inst->Opcode();
|
||||
const auto shared = SharedAccessOf(opcode);
|
||||
const auto buffer = BufferAccessOf(opcode);
|
||||
if (shared == SharedAccess::None && buffer == BufferAccess::None) continue;
|
||||
const auto flags = inst->Flags<MemoryFlags>();
|
||||
const auto& memory = memoryInfo.at(flags.index);
|
||||
Access access{block, inst, TessellationAttribute::LocalOutput, false, ComponentCount(opcode), {}, inst->Argument(inst->ArgumentCount() - 1u), flags.pc};
|
||||
IrValue* address = nullptr;
|
||||
if (shared != SharedAccess::None) {
|
||||
if (memory.kind != ResourceKind::Lds) continue;
|
||||
access.write = shared == SharedAccess::Write;
|
||||
if (access.components == 0u || (shared != SharedAccess::Read && shared != SharedAccess::Write)) Fail("only 32-bit LDS reads and writes are supported in tessellation stages", flags.pc);
|
||||
if (stage == IrShaderStage::Local && access.write) {
|
||||
access.kind = TessellationAttribute::LocalOutput;
|
||||
} else if (stage == IrShaderStage::TessellationControl && !access.write) {
|
||||
access.kind = TessellationAttribute::ControlInput;
|
||||
} else {
|
||||
Fail("unsupported LDS access in a tessellation stage", flags.pc);
|
||||
}
|
||||
address = inst->Argument(0);
|
||||
} else {
|
||||
if (memory.kind != ResourceKind::Buffer || (!IsBase(inst->Argument(3), TessellationBaseKind::OffChip) && !IsBase(inst->Argument(3), TessellationBaseKind::Factor))) continue;
|
||||
if (buffer == BufferAccess::Atomic || access.components == 0u || memory.dataBits != 32u || memory.formatted || memory.typed || memory.d16 || memory.idxen) Fail("only untyped 32-bit tessellation ring accesses are supported", flags.pc);
|
||||
access.write = buffer == BufferAccess::Write;
|
||||
const bool factor = IsBase(inst->Argument(3), TessellationBaseKind::Factor);
|
||||
if (stage == IrShaderStage::TessellationControl && access.write) {
|
||||
access.kind = factor ? TessellationAttribute::Factor : TessellationAttribute::ControlOutput;
|
||||
} else if (stage == IrShaderStage::TessellationEvaluation && !access.write && !factor) {
|
||||
access.kind = TessellationAttribute::EvaluationInput;
|
||||
} else {
|
||||
Fail("unsupported tessellation ring access", flags.pc);
|
||||
}
|
||||
address = memory.offen ? inst->Argument(2) : nullptr;
|
||||
}
|
||||
if (address != nullptr) {
|
||||
const auto evaluated = evaluator.Evaluate(address, access.predicate);
|
||||
if (!evaluated) Fail("the tessellation ring or LDS address is not affine in the control point and relative patch", flags.pc);
|
||||
access.address = *evaluated;
|
||||
}
|
||||
access.address.constant += memory.offset;
|
||||
if ((access.address.constant & 3) != 0) Fail("unaligned tessellation ring or LDS address", flags.pc);
|
||||
accesses.push_back(access);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void agree(std::uint32_t& stride, std::int64_t value, std::uint32_t pc) const {
|
||||
if (value <= 0 || (value & 3) != 0 || value > 0xffff || (stride != 0u && stride != value)) Fail("inconsistent tessellation stride", pc);
|
||||
stride = static_cast<std::uint32_t>(value);
|
||||
}
|
||||
|
||||
void derive() {
|
||||
const auto inputs = static_cast<std::int64_t>(tessellation.inputControlPoints);
|
||||
const auto outputs = static_cast<std::int64_t>(tessellation.outputControlPoints);
|
||||
for (const auto& access : accesses) {
|
||||
if (access.kind == TessellationAttribute::LocalOutput) agree(localStride, access.address.relative, access.pc);
|
||||
if (access.kind == TessellationAttribute::ControlInput && access.address.vertex != 0) agree(localStride, access.address.vertex, access.pc);
|
||||
if (access.kind == TessellationAttribute::ControlOutput && access.address.vertex != 0) agree(controlStride, access.address.vertex, access.pc);
|
||||
if (access.kind == TessellationAttribute::EvaluationInput) {
|
||||
if (access.address.relative <= 0 || access.address.relative % outputs != 0) Fail("domain shader reads of patch constants or other patches are unsupported", access.pc);
|
||||
agree(controlStride, access.address.relative / outputs, access.pc);
|
||||
}
|
||||
}
|
||||
std::optional<std::int64_t> patchStride;
|
||||
std::int64_t patchLowest = AddressLimit;
|
||||
std::int64_t patchHighest = 0;
|
||||
for (auto& access : accesses) {
|
||||
const auto& address = access.address;
|
||||
const auto end = address.constant + 4 * static_cast<std::int64_t>(access.components);
|
||||
switch (access.kind) {
|
||||
case TessellationAttribute::LocalOutput:
|
||||
if (address.vertex != 0 || address.constant < 0 || end > localStride) Fail("the local stage writes outside its vertex's LDS slot", access.pc);
|
||||
break;
|
||||
case TessellationAttribute::ControlInput:
|
||||
if (localStride == 0u) Fail("the hull shader reads no LDS per control point, so the local stride is unknown", access.pc);
|
||||
if ((address.vertex != 0 && address.vertex != localStride) || address.relative != inputs * localStride || address.constant < 0 || end + address.vertex * (outputs - 1) > inputs * localStride) Fail("the hull shader reads LDS outside its patch", access.pc);
|
||||
break;
|
||||
case TessellationAttribute::ControlOutput:
|
||||
if (address.vertex == 0) {
|
||||
if (controlStride == 0u || address.constant < outputs * controlStride) Fail("the hull shader writes off-chip memory outside its control points and patch constants", access.pc);
|
||||
if (patchStride && *patchStride != address.relative) Fail("inconsistent patch-constant stride", access.pc);
|
||||
patchStride = address.relative;
|
||||
patchLowest = std::min(patchLowest, address.constant);
|
||||
patchHighest = std::max(patchHighest, end);
|
||||
access.kind = TessellationAttribute::PatchOutput;
|
||||
requireFirstInvocation(access);
|
||||
} else if (address.relative != outputs * controlStride || address.constant < 0 || end > controlStride) {
|
||||
Fail("the hull shader writes off-chip memory outside its control point", access.pc);
|
||||
}
|
||||
break;
|
||||
case TessellationAttribute::EvaluationInput:
|
||||
if (address.constant < 0 || end > outputs * controlStride) Fail("the domain shader reads off-chip memory outside its patch's control points", access.pc);
|
||||
break;
|
||||
case TessellationAttribute::Factor:
|
||||
if (tessellation.domain != 1u || address.vertex != 0 || address.relative != TriangleFactorBytes || address.constant < 0 || end > TriangleFactorBytes) Fail("the hull shader writes tessellation factors outside its patch's triangle factors", access.pc);
|
||||
requireFirstInvocation(access);
|
||||
break;
|
||||
case TessellationAttribute::PatchOutput:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (patchStride && (*patchStride == 0 || std::abs(*patchStride) < patchHighest - patchLowest)) Fail("patch-constant records of neighbouring patches overlap", 0u);
|
||||
}
|
||||
|
||||
void requireRelativeIndependence(const std::vector<IrValue*>& bases) {
|
||||
std::vector<IrValue*> pending(bases.begin(), bases.end());
|
||||
std::unordered_set<IrValue*> visited(bases.begin(), bases.end());
|
||||
while (!pending.empty()) {
|
||||
IrValue* value = pending.back();
|
||||
pending.pop_back();
|
||||
for (IrValue* user : value->Uses()) {
|
||||
switch (user->Opcode()) {
|
||||
case IrOpcode::Identity:
|
||||
case IrOpcode::IAdd32:
|
||||
case IrOpcode::ISub32:
|
||||
case IrOpcode::IMul32:
|
||||
case IrOpcode::ShiftLeftLogical32:
|
||||
case IrOpcode::ShiftRightLogical32:
|
||||
case IrOpcode::BitwiseAnd32:
|
||||
case IrOpcode::BitwiseOr32:
|
||||
case IrOpcode::BitFieldUExtract:
|
||||
case IrOpcode::SelectU32:
|
||||
case IrOpcode::Phi:
|
||||
if (visited.insert(user).second) pending.push_back(user);
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const auto address = evaluator.Evaluate(value, nullptr);
|
||||
if (!address || address->relative != 0) Fail("the relative patch or vertex index is used outside a tessellation ring or LDS address", 0u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void requireFirstInvocation(const Access& access) {
|
||||
std::unordered_set<IrValue*> path;
|
||||
if (!FirstInvocationOnly(access.predicate, evaluator, path)) Fail("per-patch tessellation output is not written by control point 0 alone", access.pc);
|
||||
}
|
||||
|
||||
IrValue& constant(std::uint32_t value) {
|
||||
IrValue& result = program.CreateValue(IrOpcode::Void, IrType::U32);
|
||||
result.SetImmediateU32(value);
|
||||
return result;
|
||||
}
|
||||
|
||||
IrValue& insert(const Access& access, IrOpcode opcode, IrType type, std::initializer_list<IrValue*> arguments) {
|
||||
IrValue& value = program.CreateValue(opcode, type);
|
||||
for (IrValue* argument : arguments) value.AddArgument(argument);
|
||||
access.block->InsertInstructionBefore(access.inst, &value);
|
||||
return value;
|
||||
}
|
||||
|
||||
IrValue& componentAddress(const Access& access, std::uint32_t component) {
|
||||
IrValue& offset = constant(static_cast<std::uint32_t>(access.address.constant) + 4u * component);
|
||||
if (access.kind != TessellationAttribute::ControlInput || access.address.vertex == 0) return offset;
|
||||
IrValue& invocation = insert(access, IrOpcode::GetBuiltin, IrType::U32, {&constant(static_cast<std::uint32_t>(StageInputKind::InvocationId)), &constant(0u)});
|
||||
IrValue& scaled = insert(access, IrOpcode::IMul32, IrType::U32, {&invocation, &constant(static_cast<std::uint32_t>(access.address.vertex))});
|
||||
return insert(access, IrOpcode::IAdd32, IrType::U32, {&scaled, &offset});
|
||||
}
|
||||
|
||||
void rewrite(const Access& access) {
|
||||
IrValue& kind = constant(static_cast<std::uint32_t>(access.kind));
|
||||
const bool shared = SharedAccessOf(access.inst->Opcode()) != SharedAccess::None;
|
||||
if (access.write) {
|
||||
for (std::uint32_t component = 0; component < access.components; ++component) {
|
||||
IrValue* data = nullptr;
|
||||
if (shared) {
|
||||
data = access.inst->Argument(1u + component);
|
||||
} else if (access.components == 1u) {
|
||||
data = access.inst->Argument(4u);
|
||||
} else {
|
||||
const auto extract = access.components == 2u ? IrOpcode::CompositeExtractU32x2 : access.components == 3u ? IrOpcode::CompositeExtractU32x3 : IrOpcode::CompositeExtractU32x4;
|
||||
data = &insert(access, extract, IrType::U32, {access.inst->Argument(4u), &constant(component)});
|
||||
}
|
||||
static_cast<void>(insert(access, IrOpcode::SetTessellationAttribute, IrType::Void, {&kind, &componentAddress(access, component), data, access.predicate}));
|
||||
}
|
||||
} else {
|
||||
std::vector<IrValue*> values;
|
||||
for (std::uint32_t component = 0; component < access.components; ++component) {
|
||||
values.push_back(&insert(access, IrOpcode::GetTessellationAttribute, IrType::U32, {&kind, &componentAddress(access, component), access.predicate}));
|
||||
}
|
||||
IrValue* replacement = values.front();
|
||||
if (access.components == 2u) replacement = &insert(access, IrOpcode::CompositeConstructU32x2, IrType::U32x2, {values[0], values[1]});
|
||||
if (access.components == 3u) replacement = &insert(access, IrOpcode::CompositeConstructU32x3, IrType::U32x3, {values[0], values[1], values[2]});
|
||||
if (access.components == 4u) replacement = &insert(access, IrOpcode::CompositeConstructU32x4, IrType::U32x4, {values[0], values[1], values[2], values[3]});
|
||||
access.inst->ReplaceAllUsesWith(replacement);
|
||||
}
|
||||
access.block->RemoveInstruction(access.inst);
|
||||
access.inst->Invalidate();
|
||||
}
|
||||
|
||||
void removePrimitiveExports() {
|
||||
const auto& exports = program.Metadata().exportInfo;
|
||||
for (IrBlock* block : program.BlockOrder()) {
|
||||
auto& instructions = block->Instructions();
|
||||
for (auto it = instructions.begin(); it != instructions.end();) {
|
||||
IrValue* inst = *it;
|
||||
const auto flags = inst->Flags<ExportFlags>();
|
||||
if (inst->Opcode() != IrOpcode::SetAttribute || exports.at(flags.index).kind != ExportTargetKind::Primitive) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
const IrValue* data = inst->Argument(0)->Resolve();
|
||||
if ((exports.at(flags.index).en & 1u) == 0u || data->Opcode() != IrOpcode::CompositeConstructU32x4 || !IsBase(data->Argument(0), TessellationBaseKind::PassthroughPrimitive)) Fail("the domain shader exports a primitive other than the passthrough primitive", flags.pc);
|
||||
it = instructions.erase(it);
|
||||
inst->SetParent(nullptr);
|
||||
inst->Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IrProgram& program;
|
||||
const ShaderTessellationInputInfo& tessellation;
|
||||
IrShaderStage stage;
|
||||
AddressEvaluator evaluator;
|
||||
std::vector<Access> accesses;
|
||||
std::uint32_t localStride = 0;
|
||||
std::uint32_t controlStride = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void TessellationLowering::Lower(IrProgram& program, const ShaderTessellationInputInfo& tessellation) const {
|
||||
const auto stage = program.Resources().stage;
|
||||
if (stage != IrShaderStage::Local && stage != IrShaderStage::TessellationControl && stage != IrShaderStage::TessellationEvaluation) return;
|
||||
Lowering(program, tessellation).Run();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@
|
|||
#include "Optimization/include/Optimization/ShaderInfoCollector.hpp"
|
||||
#include "Optimization/include/Optimization/SrtWalker.hpp"
|
||||
#include "Optimization/include/Optimization/SsaBuilder.hpp"
|
||||
#include "Optimization/include/Optimization/TessellationLowering.hpp"
|
||||
#include "SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp"
|
||||
#if ANYPS5_ENABLE_SPIRV_TOOLS
|
||||
#include "SpirvBackend/SpirvOptimizer.hpp"
|
||||
|
|
@ -92,6 +93,14 @@ std::uint32_t HostSubgroupSize(const RecompileRequest& request) {
|
|||
return request.target.subgroupSize;
|
||||
}
|
||||
|
||||
RdnaProgram DecodeStageProgram(ShaderStageKind stage, std::span<const std::uint32_t> code) {
|
||||
if (stage != ShaderStageKind::Local) return RdnaInstructionDecoder{}.Decode(code);
|
||||
auto program = DecodeRdnaFrontProgram(code);
|
||||
static constexpr std::array<std::uint32_t, 1> EndProgram{0xbf810000u};
|
||||
program.instructions.back() = DecodeRdnaInstruction(program.instructions.back().programCounter, EndProgram, 0u);
|
||||
return program;
|
||||
}
|
||||
|
||||
ShaderStageInputInfo RequestInputInfo(const RecompileRequest& request) {
|
||||
const auto* mesh = request.graphics && request.graphics->mesh ? &*request.graphics->mesh : nullptr;
|
||||
const auto* tessellation = request.graphics && request.graphics->tessellation ? &*request.graphics->tessellation : nullptr;
|
||||
|
|
@ -109,7 +118,7 @@ std::uint32_t DeviceMemoryLdsBytes(const RecompileRequest& request) {
|
|||
struct PreparedControlFlow {
|
||||
PreparedControlFlow(const RecompileRequest& request, const SwappcInfo& swappcInfo)
|
||||
: stage(request.shader.stage), swappc(swappcInfo), code(request.shader.code.begin(), request.shader.code.end()),
|
||||
decoded(RdnaInstructionDecoder{}.Decode(code)), cfg(GraphBuilder{}.Build(decoded, &swappc)) {
|
||||
decoded(DecodeStageProgram(toShaderStageKind(request.shader.stage), code)), cfg(GraphBuilder{}.Build(decoded, &swappc)) {
|
||||
Structurizer{}.Structurize(cfg);
|
||||
}
|
||||
|
||||
|
|
@ -226,6 +235,11 @@ IrProgram PrepareResourceProgram(const RecompileRequest& request, ShaderPreparat
|
|||
deadCodeEliminator.Eliminate(program);
|
||||
}
|
||||
|
||||
if (inputInfo.vertex != nullptr) {
|
||||
constexpr TessellationLowering tessellationLowering;
|
||||
tessellationLowering.Lower(program, inputInfo.vertex->tess);
|
||||
}
|
||||
|
||||
constexpr SrtWalker srtWalker;
|
||||
srtWalker.BuildPlan(program);
|
||||
deadCodeEliminator.Eliminate(program);
|
||||
|
|
|
|||
|
|
@ -820,7 +820,14 @@ std::uint32_t EmitWqmU64(SpirvEmitterState& state, std::uint32_t value) {
|
|||
}
|
||||
|
||||
std::uint32_t EmitLaneId(SpirvEmitterState& state) {
|
||||
return state.program.Resources().stage == IrShaderStage::TessellationControl ? EmitInputComponentU32(state, StageInputKind::InvocationId, 0) : EmitSubgroupLocalInvocationId(state);
|
||||
if (state.program.Resources().stage != IrShaderStage::TessellationControl) return EmitSubgroupLocalInvocationId(state);
|
||||
const auto variable = InputVariableForKind(state, StageInputKind::InvocationId);
|
||||
if (variable == 0) FailEmit("stage input InvocationId was not declared");
|
||||
const auto value = state.module.AllocateId();
|
||||
const auto bits = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpLoad, TypeI32(state), value, variable);
|
||||
state.module.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
|
||||
return bits;
|
||||
}
|
||||
|
||||
std::uint32_t EmitBallot(SpirvValueEmitContext& ctx, const IrValue* predicate) {
|
||||
|
|
|
|||
|
|
@ -479,7 +479,6 @@ namespace {
|
|||
std::uint32_t TessellationPointer(SpirvValueEmitContext& ctx, const IrValue& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto kind = static_cast<TessellationAttribute>(inst.Argument(0)->ImmediateU32());
|
||||
const auto& tess = state.inputInfo.vertex->tess;
|
||||
const auto variable = state.tessVariables.at(static_cast<std::uint32_t>(kind));
|
||||
if (variable == 0u) {
|
||||
throw std::runtime_error("tessellation attribute has no interface variable");
|
||||
|
|
@ -504,7 +503,7 @@ std::uint32_t TessellationPointer(SpirvValueEmitContext& ctx, const IrValue& ins
|
|||
address = EmitBinaryU32(state, spv::OpISub, address, ConstantU32(state, state.tessPatchBase));
|
||||
}
|
||||
const bool local = kind == TessellationAttribute::LocalOutput || kind == TessellationAttribute::ControlInput;
|
||||
const auto stride = local ? tess.lsStride : tess.hsStride;
|
||||
const auto stride = local ? state.program.Metadata().tessellationLocalStride : state.program.Metadata().tessellationControlStride;
|
||||
const auto offset = kind == TessellationAttribute::PatchOutput ? address : EmitBinaryU32(state, spv::OpUMod, address, ConstantU32(state, stride));
|
||||
const auto attribute = EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 4u));
|
||||
const auto component = EmitBinaryU32(state, spv::OpBitwiseAnd, EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 2u)), ConstantU32(state, 3u));
|
||||
|
|
@ -562,7 +561,7 @@ void DefineTessellationInterfaces(SpirvEmitterState& state) {
|
|||
type = array(TypeU32Vector(state, 4u), (patchEnd - patchBegin + 15u) / 16u);
|
||||
} else {
|
||||
const bool local = kind == TessellationAttribute::LocalOutput || kind == TessellationAttribute::ControlInput;
|
||||
const auto stride = local ? tess.lsStride : tess.hsStride;
|
||||
const auto stride = local ? state.program.Metadata().tessellationLocalStride : state.program.Metadata().tessellationControlStride;
|
||||
type = array(TypeU32Vector(state, 4u), (stride + 15u) / 16u);
|
||||
if (kind != TessellationAttribute::LocalOutput) {
|
||||
type = array(type, local ? tess.inputControlPoints : tess.outputControlPoints);
|
||||
|
|
@ -576,7 +575,7 @@ void DefineTessellationInterfaces(SpirvEmitterState& state) {
|
|||
state.module.AddAnnotation(spv::OpDecorate, state.tessInnerVariable, spv::DecorationBuiltIn, spv::BuiltInTessLevelInner);
|
||||
state.module.AddAnnotation(spv::OpDecorate, state.tessInnerVariable, spv::DecorationPatch);
|
||||
} else {
|
||||
state.module.AddAnnotation(spv::OpDecorate, variable, spv::DecorationLocation, kind == TessellationAttribute::PatchOutput ? (tess.hsStride + 15u) / 16u : 0u);
|
||||
state.module.AddAnnotation(spv::OpDecorate, variable, spv::DecorationLocation, kind == TessellationAttribute::PatchOutput ? (state.program.Metadata().tessellationControlStride + 15u) / 16u : 0u);
|
||||
}
|
||||
if (kind == TessellationAttribute::Factor || kind == TessellationAttribute::PatchOutput) {
|
||||
state.module.AddAnnotation(spv::OpDecorate, variable, spv::DecorationPatch);
|
||||
|
|
@ -1163,9 +1162,17 @@ void EmitSetTessellationAttribute(SpirvValueEmitContext& ctx, const IrValue& ins
|
|||
EmitIfCondition(ctx.state, ctx.Arg(inst, 3), [&]() {
|
||||
auto value = ctx.Arg(inst, 2);
|
||||
if (inst.Argument(0)->ImmediateU32() == static_cast<std::uint32_t>(TessellationAttribute::Factor)) {
|
||||
const auto floating = ctx.state.module.AllocateId();
|
||||
ctx.state.module.AddFunction(spv::OpBitcast, TypeF32(ctx.state), floating, value);
|
||||
value = floating;
|
||||
auto& state = ctx.state;
|
||||
const bool outer = inst.Argument(1)->HasImmediate() && inst.Argument(1)->ImmediateU32() / 4u < 3u;
|
||||
const auto floating = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpBitcast, TypeF32(state), floating, value);
|
||||
const auto lower = ConstantF32Value(state, outer ? 0.0f : 1.0f);
|
||||
const auto inside = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpFOrdGreaterThan, TypeBool(state), inside, floating, lower);
|
||||
const auto clamped = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpExtInst, TypeF32(state), clamped, GlslStd450(state), GLSLstd450FMin, floating, ConstantF32Value(state, 63.0f));
|
||||
value = state.module.AllocateId();
|
||||
state.module.AddFunction(spv::OpSelect, TypeF32(state), value, inside, clamped, outer ? floating : lower);
|
||||
}
|
||||
ctx.state.module.AddFunction(spv::OpStore, TessellationPointer(ctx, inst), value);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -170,6 +170,9 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO
|
|||
IrValue& index = builtin(kind);
|
||||
return folded < 0 ? index : entryIr.ISub(index, entryIr.GetUserData(static_cast<ScalarReg>(folded)));
|
||||
};
|
||||
const auto tessellationBase = [&entryIr](TessellationBaseKind kind) -> IrValue& {
|
||||
return entryIr.Emit(IrOpcode::TessellationBase, IrOpcodeType(IrOpcode::TessellationBase), {&entryIr.Constant(static_cast<std::uint32_t>(kind))});
|
||||
};
|
||||
|
||||
for (std::uint32_t index = 0; index < options.userDataCount; index++) {
|
||||
const auto reg = static_cast<ScalarReg>(options.userDataBaseRegister + index);
|
||||
|
|
@ -305,21 +308,22 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO
|
|||
} else if (options.stage == ShaderStageKind::Local) {
|
||||
entryIr.SetScalarReg(static_cast<ScalarReg>(3), entryIr.Constant(64u));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(2), drawIndex(StageInputKind::VertexIndex));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(3), entryIr.Constant(0u));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(3), tessellationBase(TessellationBaseKind::RelativeIndex));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(5), drawIndex(StageInputKind::InstanceIndex));
|
||||
} else if (options.stage == ShaderStageKind::TessellationControl) {
|
||||
const auto& tess = options.inputInfo.vertex->tess;
|
||||
entryIr.SetScalarReg(static_cast<ScalarReg>(2), entryIr.Emit(IrOpcode::TessellationBase, IrOpcodeType(IrOpcode::TessellationBase), {&entryIr.Constant(0u)}));
|
||||
entryIr.SetScalarReg(static_cast<ScalarReg>(4), entryIr.Emit(IrOpcode::TessellationBase, IrOpcodeType(IrOpcode::TessellationBase), {&entryIr.Constant(1u)}));
|
||||
entryIr.SetScalarReg(static_cast<ScalarReg>(2), tessellationBase(TessellationBaseKind::OffChip));
|
||||
entryIr.SetScalarReg(static_cast<ScalarReg>(4), tessellationBase(TessellationBaseKind::Factor));
|
||||
entryIr.SetScalarReg(static_cast<ScalarReg>(3), entryIr.Constant(0x81010000u | tess.inputControlPoints | (tess.outputControlPoints << 8u)));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(0), builtin(StageInputKind::PrimitiveId));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(1), entryIr.ShiftLeftLogical(builtin(StageInputKind::InvocationId), entryIr.Constant(8u)));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(1), entryIr.BitwiseOr(entryIr.ShiftLeftLogical(builtin(StageInputKind::InvocationId), entryIr.Constant(8u)), tessellationBase(TessellationBaseKind::RelativeIndex)));
|
||||
} else if (options.stage == ShaderStageKind::TessellationEvaluation) {
|
||||
entryIr.SetScalarReg(static_cast<ScalarReg>(3), entryIr.Constant(64u));
|
||||
entryIr.SetScalarReg(static_cast<ScalarReg>(4), entryIr.Emit(IrOpcode::TessellationBase, IrOpcodeType(IrOpcode::TessellationBase), {&entryIr.Constant(0u)}));
|
||||
entryIr.SetScalarReg(static_cast<ScalarReg>(4), tessellationBase(TessellationBaseKind::OffChip));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(0), tessellationBase(TessellationBaseKind::PassthroughPrimitive));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(5), builtin(StageInputKind::TessCoord, 0u));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(6), builtin(StageInputKind::TessCoord, 1u));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(7), entryIr.Constant(0u));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(7), tessellationBase(TessellationBaseKind::RelativeIndex));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(8), builtin(StageInputKind::PrimitiveId));
|
||||
} else if (options.stage == ShaderStageKind::Pixel) {
|
||||
const auto* ps = options.inputInfo.pixel;
|
||||
|
|
|
|||
|
|
@ -502,6 +502,11 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [libSceAudiodec](../../core/libs/prx/libSceAudiodec/Export.cpp) throws for the 24-bit PCM word size (`iBwPcm` 0): its sample layout is unknown. ATRAC9 decoding is covered only by configuration and error tests, as no ATRAC9 encoder is available for a fixture.
|
||||
- [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) throws when the original file length is requested (`parse_ofl` non-zero) for a layer other than III, for a CRC-protected frame, or for a VBRI header outside MPEG-1 stereo: shadPS4 looks for the tag after the CRC and after the side information, while LAME and FFmpeg place it without the CRC and VBRI always at byte 36.
|
||||
- [ATRAC9 gapless segments](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - a run job stops after the gapless total of output samples counted from the start of the segment, leaving the rest of its input unconsumed, and the next run starts the next segment with the decoder as it is and without a second skip. FMOD in PPSA29141 sets a total shorter than a sound's frames and reads the frames after it on later runs; with the output held at the total its stream reader waited for them forever and the last buffer repeated. The console's handling of the frames after the total is not documented; a reset (`SetGaplessDecode` with the reset flag, `ClearContext`, initialization) starts a new segment. An ATRAC9 run job without `RUN_MULTIPLE_FRAMES` decodes its whole input as one with it: FMOD in PPSA29141 puts two run jobs in one batch with the second's input right after the whole of the first's. Whether the console stops after one frame there is not verified; MP3 and Opus run jobs still stop after one frame
|
||||
- [Tessellation rings](../../core/shader/recompiler/Optimization/src/TessellationLowering.cpp) of local, hull and domain programs become Vulkan tessellation interfaces and are never written to guest memory; a title that reads the off-chip or factor ring back sees no data.
|
||||
- [Inner tessellation factors](../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) of fractional-odd triangle patches that are NaN tessellate as 1, as in the D3D11 reference tessellator; the console is not measured.
|
||||
- [VGT_TF_PARAM](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - DISTRIBUTION_MODE is taken as scheduling only; the host tessellator decides the order of a patch's triangles.
|
||||
- [Hull programs](../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) start from the hull launch registers (user data, ring bases, v0, v1); on the console they start with what the local program leaves, and that it leaves them unchanged is not checked.
|
||||
- [Local programs](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) that the hull header leaves at address 0 are prepared at their first draw, for its tessellation configuration; a later draw with another configuration throws.
|
||||
- The [output modifier](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) (`mul:2`, `mul:4`, `div:2`) follows the shader's float mode as measured on RDNA2 (gfx1035): the hardware applies it only with IEEE mode off and output denormals of the result's precision flushed (bit 1 of the `FLOAT_MODE` denormal field clear), and ignores it otherwise. On f32 results it is applied as the hardware does with f32 denormals flushed; a shader without a known float mode is translated with IEEE mode off and f32 denormals flushed, the mode titles use. On f16 and f64 results it is ignored, and it throws in the modes where the hardware applies it.
|
||||
- The [clamp modifier](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) follows `DX10_CLAMP`, measured on RDNA2 (gfx1035) for f32, f16, packed f16 and f64 results at IEEE=0 and IEEE=1: with `DX10_CLAMP`=1 (and in shaders without a known float mode) a NaN result clamps to +0, including the quieted NaN that `v_pk_min_f16` and `v_pk_max_f16` return for a signaling NaN operand with IEEE=1 (measured on RDNA2 gfx1036 at both `IEEE_MODE` and both `DX10_CLAMP` settings); with `DX10_CLAMP`=0 a NaN result is written unchanged and every other value clamps to [0, 1] as before. f64 results follow the same rule on hardware, but a clamped f64 result with `DX10_CLAMP`=0 throws until it has an execution test. With `DX10_CLAMP`=0 the NaN's bits are those the translation already produces for the instruction without clamp, so where those are left to the driver (f32 and scalar f16 arithmetic) the payload is the driver's, not the hardware's input propagation. `v_dot2_f32_f16` and the integer clamps are unchanged.
|
||||
- [FP16_OVFL](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) follows the shader's float mode as measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with f16 denormals kept and flushed: with `FP16_OVFL`=1 an f16 result that rounds to ±infinity is written as ±0x7bff unless a source operand is infinite, so `v_rcp_f16`, `v_rsq_f16` and `v_log_f16` of ±0 and `v_exp_f16` of 16 or more are clamped while an infinite input passes through, and `v_div_fixup_f16` keeps the infinity it writes for a zero denominator or an infinite numerator and clamps any other infinite quotient, a NaN one included. `v_cvt_pkrtz_f16_f32` rounds toward zero and never overflows. The sources are checked as the f32 values the translation computes with; the rule is applied to `v_fma_mix*_f16` with f32 sources and to `v_interp_p2_f16` without a measurement of infinite f32 sources or parameters. Round modes other than nearest even are not modelled, so the overflow threshold is the nearest-even one (65520).
|
||||
|
|
|
|||
Loading…
Reference in New Issue