Implement vertex swizzle

This commit is contained in:
nmzik
2026-07-18 03:36:09 +02:00
parent 18479daabd
commit f4b52cea2b
10 changed files with 282 additions and 195 deletions
+20 -73
View File
@@ -22,7 +22,7 @@
namespace Libs::Graphics {
//IDK: maybe we can remove it?
// IDK: maybe we can remove it?
constexpr uint32_t kTemporaryPs5BufferFormat121 = 121u;
static bool NarrowInputFormat(VkFormat* format, uint32_t* size, uint32_t used_components) {
@@ -561,13 +561,15 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline* pipeline, VkRenderP
input_attr[index].location = index;
input_attr[index].offset = b.attr_offsets[ai];
uint32_t attr_size = 4;
auto registers_num = vs_input_info->resources_dst[index].registers_num;
auto used_components = (vs_input_info->resource_fetch_components[index] > 0
? vs_input_info->resource_fetch_components[index]
: registers_num);
uint32_t attr_size = 4;
auto registers_num = vs_input_info->resources_dst[index].registers_num;
auto used_components = (vs_input_info->resource_fetch_components[index] > 0
? vs_input_info->resource_fetch_components[index]
: registers_num);
const auto raw_components = VertexRawComponentCount(
vs_input_info->resources[index].DstSelXYZW(), static_cast<uint32_t>(registers_num));
GetInputFormat(vs_input_info->resources[index], &input_attr[index].format, &attr_size,
static_cast<uint32_t>(used_components));
raw_components);
if (graphics_debug_dump_enabled()) {
static std::atomic_uint log_count = 0;
@@ -602,73 +604,18 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline* pipeline, VkRenderP
EXIT_NOT_IMPLEMENTED(vs_input_info->resources[index].AddTid());
EXIT_NOT_IMPLEMENTED(vs_input_info->resources[index].SwizzleEnabled());
auto log_unsupported_vertex_swizzle = [index, attr_size, registers_num](
uint32_t swizzle, uint32_t expected) {
static bool logged = false;
if (!logged) {
LOGF(
"VertexInput: temporary: accepting unsupported dst swizzle at attr %" PRIu32
" (attr_size=%" PRIu32 ", regs=%" PRIu32 ", swizzle=0x%03" PRIx32
", expected=0x%03" PRIx32 ")\n",
static_cast<uint32_t>(index), attr_size, registers_num, swizzle, expected);
logged = true;
if (registers_num < 1 || registers_num > 4) {
EXIT("invalid vertex destination register count: %u\n", registers_num);
}
for (uint32_t component = 0; component < static_cast<uint32_t>(registers_num);
component++) {
const auto selector =
GetDstSel(vs_input_info->resources[index].DstSelXYZW(), component);
if (DecodeVertexDstSelector(selector).kind == VertexDstSelectKind::Reserved) {
EXIT("unsupported reserved vertex dst selector: attr=%u component=%u "
"selector=%u\n",
static_cast<uint32_t>(index), component, selector);
}
};
switch (registers_num) {
case 1: {
auto swizzle = vs_input_info->resources[index].DstSelX();
if (swizzle != DstSel(4)) {
log_unsupported_vertex_swizzle(swizzle, DstSel(4));
}
break;
}
case 2: {
auto swizzle = vs_input_info->resources[index].DstSelXY();
auto expected = (attr_size == 1 ? DstSel(4, 0) : DstSel(4, 5));
if (swizzle != expected) {
log_unsupported_vertex_swizzle(swizzle, expected);
}
break;
}
case 3: {
auto swizzle = vs_input_info->resources[index].DstSelXYZ();
auto expected =
(attr_size == 1 ? DstSel(4, 0, 0)
: (attr_size == 2 ? DstSel(4, 5, 0) : DstSel(4, 5, 6)));
if (swizzle != expected) {
log_unsupported_vertex_swizzle(swizzle, expected);
}
break;
}
case 4: {
auto swizzle = vs_input_info->resources[index].DstSelXYZW();
auto expected = DstSel(4, 5, 6, 7);
bool supported = false;
switch (attr_size) {
case 1:
expected = DstSel(4, 0, 0, 1);
supported = (swizzle == expected);
break;
case 2:
expected = DstSel(4, 5, 0, 1);
supported = (swizzle == expected);
break;
case 3:
expected = DstSel(4, 5, 6, 1);
supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 0));
break;
default:
supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 1) ||
swizzle == DstSel(4, 5, 6, 0));
break;
}
if (!supported) {
log_unsupported_vertex_swizzle(swizzle, expected);
}
break;
}
default: EXIT("invalid registers_num");
}
}
}
@@ -639,6 +639,8 @@ struct Instruction {
bool slc = false;
bool idxen = false;
bool offen = false;
// V_CMPX writes the comparison result to its normal scalar destination and EXEC.
bool compare_exec = false;
int32_t branch_offset = 0;
uint32_t branch_target = 0;
struct {
@@ -1103,9 +1103,8 @@ bool DecodeVopcSdwa(uint32_t pc, std::span<const uint32_t> code, uint32_t word_i
!DecodeScalarSource(vsrc1 + (fields.s1 == 0u ? 256u : 0u), pc, &inst->src1, error)) {
return false;
}
if (IsVopcCompareExec(inst->opcode)) {
inst->dst.kind = OperandKind::ExecLo;
} else if (fields.sd == 0u) {
inst->compare_exec = IsVopcCompareExec(inst->opcode);
if (fields.sd == 0u) {
inst->dst.kind = OperandKind::VccLo;
} else if (!DecodeScalarDestination(fields.sdst, pc, &inst->dst, error)) {
return false;
@@ -1142,7 +1141,8 @@ bool DecodeVopcDpp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_in
!DecodeVectorGpr(vsrc1, &inst->src1, error)) {
return false;
}
inst->dst.kind = IsVopcCompareExec(inst->opcode) ? OperandKind::ExecLo : OperandKind::VccLo;
inst->dst.kind = OperandKind::VccLo;
inst->compare_exec = IsVopcCompareExec(inst->opcode);
inst->src0.negate = ((modifier >> 20u) & 0x1u) != 0u;
inst->src0.absolute = ((modifier >> 21u) & 0x1u) != 0u;
inst->src0.dpp = true;
@@ -1532,7 +1532,8 @@ bool DecodeVopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst->family = Family::VOPC;
inst->opcode_id = opcode;
inst->opcode = Lookup(VOPC_OPS, static_cast<uint32_t>(std::size(VOPC_OPS)), opcode);
inst->dst.kind = IsVopcCompareExec(inst->opcode) ? OperandKind::ExecLo : OperandKind::VccLo;
inst->dst.kind = OperandKind::VccLo;
inst->compare_exec = IsVopcCompareExec(inst->opcode);
SetRawWords(inst, code, word_index, 1);
switch (src0) {
@@ -1618,9 +1619,8 @@ bool DecodeVop3(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
return true;
}
bool dst_ok = true;
if (compare_exec) {
inst->dst.kind = OperandKind::ExecLo;
} else if (scalar_dst) {
inst->compare_exec = compare_exec;
if (scalar_dst) {
// VOP3A uses VDST for VOPC and the scalar-destination lane-read opcodes.
dst_ok = DecodeScalarDestination(vdst, pc, &inst->dst, error);
} else {
@@ -1165,6 +1165,22 @@ bool LowerImplemented(const Decoder::Instruction& decoded, BasicBlock* block, st
}
block->instructions.push_back(inst);
if (decoded.compare_exec) {
// V_CMPX has two architectural destinations. Keep the comparison's normal VCC/SDST
// result, then mirror the complete lane mask to EXEC. A mask is always a 64-bit scalar pair, including wave32 shaders.
for (uint32_t i = 0; i < 2u; i++) {
Instruction move;
move.pc = decoded.pc;
move.op = Opcode::MoveU32;
move.src_count = 1;
move.src[0] = inst.dst;
move.src[0].reg.index += i;
move.dst.kind = OperandKind::Register;
move.dst.reg.file = RegisterFile::Exec;
move.dst.reg.index = i;
block->instructions.push_back(move);
}
}
return AppendScalarResultSccNonZero(decoded, block, error);
}
@@ -94,8 +94,12 @@ uint32_t VertexParameterComponentCount(const EmitterState& state, const InputBin
input.location < ShaderVertexInputInfo::RES_MAX &&
input.location < static_cast<uint32_t>(state.vertex_input_info->resources_num) &&
state.vertex_input_info->resources_dst[input.location].registers_num > 0) {
count = static_cast<uint32_t>(
const auto& descriptor = state.vertex_input_info->resources[input.location];
const auto dst_count = static_cast<uint32_t>(
state.vertex_input_info->resources_dst[input.location].registers_num);
// Keep the SPIR-V interface and native VkFormat width identical. A destination
// selector may request a raw component beyond the written VGPR count (W -> X).
count = VertexRawComponentCount(descriptor.DstSelXYZW(), dst_count);
}
return std::clamp(count, 1u, 4u);
}
@@ -450,7 +450,25 @@ uint32_t EmitVertexParameterComponentU32(EmitterState* state, const InputBinding
uint32_t component) {
const auto count = VertexParameterComponentCount(*state, input);
const auto kind = VertexParameterScalarKind(*state, input.location);
if (component >= count) {
uint32_t source_component = component;
if (input.location < ShaderVertexInputInfo::RES_MAX &&
input.location < static_cast<uint32_t>(state->vertex_input_info->resources_num)) {
const auto& descriptor = state->vertex_input_info->resources[input.location];
const auto selection =
DecodeVertexDstSelector(GetDstSel(descriptor.DstSelXYZW(), component & 3u));
if (selection.kind == VertexDstSelectKind::Zero) {
return ConstantU32(state, 0u);
}
if (selection.kind == VertexDstSelectKind::One) {
return ConstantU32(state,
kind == VertexInputScalarKind::Float ? 0x3f800000u : 1u);
}
if (selection.kind == VertexDstSelectKind::Reserved) {
EXIT("reserved vertex destination selector\n");
}
source_component = selection.source_component;
}
if (source_component >= count) {
return VertexInputDefaultComponentU32(state, kind, component);
}
@@ -462,7 +480,7 @@ uint32_t EmitVertexParameterComponentU32(EmitterState* state, const InputBinding
const auto pointer_type = VertexParameterScalarPointerType(*state, kind);
const auto pointer = state->builder.AllocateId();
state->builder.AddFunction({OpAccessChain, pointer_type, pointer, input.variable_id,
ConstantU32(state, component)});
ConstantU32(state, source_component)});
state->builder.AddFunction({OpLoad, scalar_type, raw, pointer});
}
+55 -20
View File
@@ -64,6 +64,41 @@ inline uint8_t GetDstSel(uint32_t swizzle, uint32_t channel) {
return (swizzle >> (channel * 3u)) & 0x7u;
}
enum class VertexDstSelectKind : uint8_t { Zero, One, Component, Reserved };
struct VertexDstSelection {
VertexDstSelectKind kind = VertexDstSelectKind::Reserved;
uint8_t source_component = 0;
};
constexpr VertexDstSelection DecodeVertexDstSelector(uint8_t selector) {
if (selector == 0u) {
return {VertexDstSelectKind::Zero, 0};
}
if (selector == 1u) {
return {VertexDstSelectKind::One, 0};
}
if (selector >= 4u && selector <= 7u) {
return {VertexDstSelectKind::Component, static_cast<uint8_t>(selector - 4u)};
}
return {VertexDstSelectKind::Reserved, 0};
}
constexpr uint32_t VertexRawComponentCount(uint32_t swizzle, uint32_t destination_components) {
const auto dst_count = destination_components < 1u
? 1u
: (destination_components > 4u ? 4u : destination_components);
auto count = dst_count;
for (uint32_t component = 0; component < dst_count; component++) {
const auto selection = DecodeVertexDstSelector(GetDstSel(swizzle, component));
if (selection.kind == VertexDstSelectKind::Component) {
const auto source_count = static_cast<uint32_t>(selection.source_component) + 1u;
count = count > source_count ? count : source_count;
}
}
return count;
}
struct ShaderVertexInputInfo {
static constexpr int RES_MAX = 32;
@@ -96,26 +131,26 @@ struct ShaderComputeInputInfo {
};
struct ShaderPixelInputInfo {
uint32_t interpolator_settings[32] = {0};
uint32_t input_num = 0;
uint32_t ps_system_input_base = 0;
uint8_t target_output_mode[8] = {};
std::array<Prospero::ColorComponentMapping, 8> target_export_mapping = {};
uint32_t mrt_output_mask = 0;
uint32_t descriptor_set = 0;
bool ps_pos_x = false;
bool ps_pos_y = false;
bool ps_pos_xy = false;
bool ps_pos_z = false;
bool ps_pos_w = false;
bool ps_front_face = false;
bool ps_no_perspective = false;
bool ps_pixel_kill_enable = false;
bool ps_depth_export_enable = false;
bool ps_sample_mask_export_enable = false;
bool ps_early_z = false;
bool ps_execute_on_noop = false;
ShaderStageRuntime stage;
uint32_t interpolator_settings[32] = {0};
uint32_t input_num = 0;
uint32_t ps_system_input_base = 0;
uint8_t target_output_mode[8] = {};
std::array<Prospero::ColorComponentMapping, 8> target_export_mapping = {};
uint32_t mrt_output_mask = 0;
uint32_t descriptor_set = 0;
bool ps_pos_x = false;
bool ps_pos_y = false;
bool ps_pos_xy = false;
bool ps_pos_z = false;
bool ps_pos_w = false;
bool ps_front_face = false;
bool ps_no_perspective = false;
bool ps_pixel_kill_enable = false;
bool ps_depth_export_enable = false;
bool ps_sample_mask_export_enable = false;
bool ps_early_z = false;
bool ps_execute_on_noop = false;
ShaderStageRuntime stage;
bool HasPositionInput() const { return ps_pos_x || ps_pos_y || ps_pos_z || ps_pos_w; }
};