mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-27 16:02:37 +00:00
Initial commit
This commit is contained in:
commit
147188b143
427 files changed
+205923
No files matched your search
@@ -0,0 +1,293 @@
|
||||
#include "graphics/shader/recompiler/BindingLayout.h"
|
||||
|
||||
#include "graphics/shader/recompiler/ScalarProvenance.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t MaxPushConstantBytes = 128;
|
||||
|
||||
constexpr std::array ImageBindingKinds = {
|
||||
DescriptorBindingKind::Sampled2D, DescriptorBindingKind::Sampled2DArray,
|
||||
DescriptorBindingKind::Sampled3D, DescriptorBindingKind::SampledUint2D,
|
||||
DescriptorBindingKind::SampledUint2DArray, DescriptorBindingKind::SampledUint3D,
|
||||
DescriptorBindingKind::Storage2D, DescriptorBindingKind::Storage2DArray,
|
||||
DescriptorBindingKind::Storage3D, DescriptorBindingKind::StorageUint2D,
|
||||
DescriptorBindingKind::StorageUint2DArray, DescriptorBindingKind::StorageUint3D,
|
||||
};
|
||||
|
||||
bool ImageBinding(const ImageResource& image, DescriptorBindingKind& result) {
|
||||
using Dimension = Decoder::ImageDimension;
|
||||
using Kind = DescriptorBindingKind;
|
||||
|
||||
switch (image.kind) {
|
||||
case ResourceKind::Image:
|
||||
switch (image.dimension) {
|
||||
case Dimension::Dim2D: result = Kind::Sampled2D; return true;
|
||||
case Dimension::Dim2DArray: result = Kind::Sampled2DArray; return true;
|
||||
case Dimension::Dim3D: result = Kind::Sampled3D; return true;
|
||||
default: return false;
|
||||
}
|
||||
case ResourceKind::ImageUint:
|
||||
switch (image.dimension) {
|
||||
case Dimension::Dim2D: result = Kind::SampledUint2D; return true;
|
||||
case Dimension::Dim2DArray: result = Kind::SampledUint2DArray; return true;
|
||||
case Dimension::Dim3D: result = Kind::SampledUint3D; return true;
|
||||
default: return false;
|
||||
}
|
||||
case ResourceKind::StorageImage:
|
||||
switch (image.dimension) {
|
||||
case Dimension::Dim2D: result = Kind::Storage2D; return true;
|
||||
case Dimension::Dim2DArray: result = Kind::Storage2DArray; return true;
|
||||
case Dimension::Dim3D: result = Kind::Storage3D; return true;
|
||||
default: return false;
|
||||
}
|
||||
case ResourceKind::StorageImageUint:
|
||||
switch (image.dimension) {
|
||||
case Dimension::Dim2D: result = Kind::StorageUint2D; return true;
|
||||
case Dimension::Dim2DArray: result = Kind::StorageUint2DArray; return true;
|
||||
case Dimension::Dim3D: result = Kind::StorageUint3D; return true;
|
||||
default: return false;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CollectValue(const ScalarProvenance& provenance, uint32_t id, std::vector<uint8_t>* visited,
|
||||
std::set<uint32_t>* registers) {
|
||||
if (id <= ScalarProvenance::Unknown) {
|
||||
return true;
|
||||
}
|
||||
if (id >= provenance.values.size()) {
|
||||
return false;
|
||||
}
|
||||
if ((*visited)[id] != 0) {
|
||||
return true;
|
||||
}
|
||||
(*visited)[id] = 1;
|
||||
const auto& value = provenance.values[id];
|
||||
if (value.op == ScalarValueOp::UserData) {
|
||||
registers->insert(value.imm);
|
||||
return true;
|
||||
}
|
||||
if (value.op == ScalarValueOp::Phi) {
|
||||
for (const auto arg: value.phi_args) {
|
||||
if (!CollectValue(provenance, arg, visited, registers)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
for (uint32_t i = 0; i < ScalarValueArgCount(value.op); i++) {
|
||||
if (!CollectValue(provenance, value.args[i], visited, registers)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CollectSource(const Program& program, uint32_t source, bool allow_unknown,
|
||||
std::vector<uint8_t>* visited, std::set<uint32_t>* registers) {
|
||||
if (allow_unknown && source == ScalarProvenance::Unknown) {
|
||||
return true;
|
||||
}
|
||||
if (source < 2u || source - 2u >= program.provenance.descriptors.size()) {
|
||||
return false;
|
||||
}
|
||||
const auto& descriptor = program.provenance.descriptors[source - 2u];
|
||||
for (uint32_t i = 0; i < descriptor.dword_count; i++) {
|
||||
if (!CollectValue(program.provenance, descriptor.dwords[i], visited, registers)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CollectUserData(const Program& program, std::vector<uint32_t>& result) {
|
||||
std::set<uint32_t> registers;
|
||||
std::vector<uint8_t> visited(program.provenance.values.size());
|
||||
for (const auto& buffer: program.info.buffers) {
|
||||
if (!CollectSource(program, buffer.source, false, &visited, ®isters)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto& image: program.info.images) {
|
||||
if (!CollectSource(program, image.source, false, &visited, ®isters)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto& sampler: program.info.samplers) {
|
||||
if (!CollectSource(program, sampler.source, false, &visited, ®isters)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto& address: program.info.addresses) {
|
||||
if (!CollectSource(program, address.source, true, &visited, ®isters)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto& read: program.srt.reads) {
|
||||
if (!CollectValue(program.provenance, read.value, &visited, ®isters)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto read: program.srt.dynamic_reads) {
|
||||
if (!CollectValue(program.provenance, read, &visited, ®isters)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto source: program.srt.dynamic_sources) {
|
||||
if (!CollectSource(program, source, true, &visited, ®isters)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (inst.op == Opcode::SLoadDword && inst.memory.kind == ResourceKind::ScalarBuffer &&
|
||||
!CollectValue(program.provenance, inst.scalar_value, &visited, ®isters)) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < inst.src_count; i++) {
|
||||
if (!CollectValue(program.provenance, inst.scalar_sources[i], &visited,
|
||||
®isters)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.assign(registers.begin(), registers.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
void AddBinding(BindingLayout* layout, DescriptorBindingKind kind,
|
||||
std::vector<uint32_t> resources = {}) {
|
||||
layout->descriptors.push_back(
|
||||
{kind, static_cast<uint32_t>(layout->descriptors.size()), std::move(resources)});
|
||||
}
|
||||
|
||||
bool UsesGds(const Program& program) {
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (inst.memory.kind == ResourceKind::Gds) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool AllocateBindings(Program* program, const BindingLayoutOptions& options, std::string* error) {
|
||||
if (program == nullptr || !program->shader_info_complete || program->binding_layout_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = program == nullptr ? "invalid binding-layout program"
|
||||
: !program->shader_info_complete ? "shader info is not ready"
|
||||
: "binding layout already allocated";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (options.push_constant_offset > MaxPushConstantBytes) {
|
||||
if (error != nullptr) {
|
||||
*error = "push-constant offset exceeds the Vulkan minimum guarantee";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (options.push_constant_offset % 4u != 0) {
|
||||
if (error != nullptr) {
|
||||
*error = "push-constant offset is not dword aligned";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
BindingLayout next;
|
||||
next.descriptor_set = options.descriptor_set;
|
||||
next.push_constant_offset = options.push_constant_offset;
|
||||
if (!CollectUserData(*program, next.user_data_registers)) {
|
||||
if (error != nullptr) {
|
||||
*error = "shader plan contains an invalid scalar provenance reference";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!program->info.buffers.empty()) {
|
||||
std::vector<uint32_t> resources(program->info.buffers.size());
|
||||
for (uint32_t i = 0; i < resources.size(); i++) {
|
||||
resources[i] = i;
|
||||
}
|
||||
AddBinding(&next, DescriptorBindingKind::Buffers, std::move(resources));
|
||||
}
|
||||
|
||||
std::array<std::vector<uint32_t>, ImageBindingKinds.size()> image_groups;
|
||||
for (uint32_t i = 0; i < program->info.images.size(); i++) {
|
||||
DescriptorBindingKind kind;
|
||||
if (!ImageBinding(program->info.images[i], kind)) {
|
||||
if (error != nullptr) {
|
||||
*error = "shader info contains an invalid image binding class";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const auto group = std::find(ImageBindingKinds.begin(), ImageBindingKinds.end(), kind);
|
||||
if (group == ImageBindingKinds.end()) {
|
||||
if (error != nullptr) {
|
||||
*error = "shader info contains an unmapped image binding class";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
image_groups[static_cast<size_t>(group - ImageBindingKinds.begin())].push_back(i);
|
||||
}
|
||||
for (uint32_t i = 0; i < image_groups.size(); i++) {
|
||||
if (!image_groups[i].empty()) {
|
||||
AddBinding(&next, ImageBindingKinds[i], std::move(image_groups[i]));
|
||||
}
|
||||
}
|
||||
|
||||
if (!program->info.samplers.empty()) {
|
||||
std::vector<uint32_t> resources(program->info.samplers.size());
|
||||
for (uint32_t i = 0; i < resources.size(); i++) {
|
||||
resources[i] = i;
|
||||
}
|
||||
AddBinding(&next, DescriptorBindingKind::Samplers, std::move(resources));
|
||||
}
|
||||
if (UsesGds(*program)) {
|
||||
AddBinding(&next, DescriptorBindingKind::Gds);
|
||||
}
|
||||
if (!program->info.addresses.empty()) {
|
||||
std::vector<uint32_t> resources(program->info.addresses.size());
|
||||
for (uint32_t i = 0; i < resources.size(); i++) {
|
||||
resources[i] = i;
|
||||
}
|
||||
AddBinding(&next, DescriptorBindingKind::AddressMemory, std::move(resources));
|
||||
}
|
||||
if (!program->srt.reads.empty()) {
|
||||
AddBinding(&next, DescriptorBindingKind::FlattenedSrt);
|
||||
}
|
||||
|
||||
const auto available_push_dwords = (MaxPushConstantBytes - options.push_constant_offset) / 4u;
|
||||
const auto push_limit = std::min(options.max_push_dwords, available_push_dwords);
|
||||
if (next.user_data_registers.size() <= push_limit) {
|
||||
next.push_constant_size = static_cast<uint32_t>(next.user_data_registers.size() * 4u);
|
||||
} else {
|
||||
AddBinding(&next, DescriptorBindingKind::UserData);
|
||||
}
|
||||
|
||||
program->bindings = std::move(next);
|
||||
program->binding_layout_complete = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
const DescriptorBinding* FindBinding(const BindingLayout& layout, DescriptorBindingKind kind) {
|
||||
for (const auto& binding: layout.descriptors) {
|
||||
if (binding.kind == kind) {
|
||||
return &binding;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_BINDINGLAYOUT_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_BINDINGLAYOUT_H_
|
||||
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
struct BindingLayoutOptions {
|
||||
uint32_t descriptor_set = 0;
|
||||
uint32_t push_constant_offset = 0;
|
||||
uint32_t max_push_dwords = 32;
|
||||
};
|
||||
|
||||
bool AllocateBindings(Program* program, const BindingLayoutOptions& options, std::string* error);
|
||||
|
||||
const DescriptorBinding* FindBinding(const BindingLayout& layout, DescriptorBindingKind kind);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_BINDINGLAYOUT_H_ */
|
||||
@@ -0,0 +1,256 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_BUFFERFORMAT_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_BUFFERFORMAT_H_
|
||||
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Format {
|
||||
|
||||
enum class ComponentType {
|
||||
Unknown,
|
||||
Uint,
|
||||
Sint,
|
||||
Unorm,
|
||||
Snorm,
|
||||
Uscaled,
|
||||
Sscaled,
|
||||
Float,
|
||||
};
|
||||
|
||||
struct BufferFormatInfo {
|
||||
Prospero::BufferFormat format = Prospero::BufferFormat::kInvalid;
|
||||
ComponentType type = ComponentType::Unknown;
|
||||
uint32_t component_count = 0;
|
||||
uint32_t byte_size = 0;
|
||||
uint32_t component_bits[4] = {};
|
||||
uint32_t component_bit_offset[4] = {};
|
||||
bool packed_bitfield = false;
|
||||
};
|
||||
|
||||
constexpr Prospero::BufferFormat DecodeTBufferFormat(uint32_t data_format, uint32_t number_format) {
|
||||
return static_cast<Prospero::BufferFormat>(((number_format & 0x7u) << 4u) | (data_format & 0xfu));
|
||||
}
|
||||
|
||||
constexpr ComponentType GetFormatComponentType(Prospero::BufferFormat format) {
|
||||
switch (format) {
|
||||
case Prospero::BufferFormat::k8UInt:
|
||||
case Prospero::BufferFormat::k16UInt:
|
||||
case Prospero::BufferFormat::k8_8UInt:
|
||||
case Prospero::BufferFormat::k32UInt:
|
||||
case Prospero::BufferFormat::k16_16UInt:
|
||||
case Prospero::BufferFormat::k11_11_10UInt:
|
||||
case Prospero::BufferFormat::k10_11_11UInt:
|
||||
case Prospero::BufferFormat::k2_10_10_10UInt:
|
||||
case Prospero::BufferFormat::k10_10_10_2UInt:
|
||||
case Prospero::BufferFormat::k8_8_8_8UInt:
|
||||
case Prospero::BufferFormat::k32_32UInt:
|
||||
case Prospero::BufferFormat::k16_16_16_16UInt:
|
||||
case Prospero::BufferFormat::k32_32_32UInt:
|
||||
case Prospero::BufferFormat::k32_32_32_32UInt: return ComponentType::Uint;
|
||||
case Prospero::BufferFormat::k8SInt:
|
||||
case Prospero::BufferFormat::k16SInt:
|
||||
case Prospero::BufferFormat::k8_8SInt:
|
||||
case Prospero::BufferFormat::k32SInt:
|
||||
case Prospero::BufferFormat::k16_16SInt:
|
||||
case Prospero::BufferFormat::k11_11_10SInt:
|
||||
case Prospero::BufferFormat::k10_11_11SInt:
|
||||
case Prospero::BufferFormat::k2_10_10_10SInt:
|
||||
case Prospero::BufferFormat::k10_10_10_2SInt:
|
||||
case Prospero::BufferFormat::k8_8_8_8SInt:
|
||||
case Prospero::BufferFormat::k32_32SInt:
|
||||
case Prospero::BufferFormat::k16_16_16_16SInt:
|
||||
case Prospero::BufferFormat::k32_32_32SInt:
|
||||
case Prospero::BufferFormat::k32_32_32_32SInt: return ComponentType::Sint;
|
||||
case Prospero::BufferFormat::k8UNorm:
|
||||
case Prospero::BufferFormat::k16UNorm:
|
||||
case Prospero::BufferFormat::k8_8UNorm:
|
||||
case Prospero::BufferFormat::k16_16UNorm:
|
||||
case Prospero::BufferFormat::k11_11_10UNorm:
|
||||
case Prospero::BufferFormat::k10_11_11UNorm:
|
||||
case Prospero::BufferFormat::k2_10_10_10UNorm:
|
||||
case Prospero::BufferFormat::k10_10_10_2UNorm:
|
||||
case Prospero::BufferFormat::k8_8_8_8UNorm:
|
||||
case Prospero::BufferFormat::k16_16_16_16UNorm: return ComponentType::Unorm;
|
||||
case Prospero::BufferFormat::k8SNorm:
|
||||
case Prospero::BufferFormat::k16SNorm:
|
||||
case Prospero::BufferFormat::k8_8SNorm:
|
||||
case Prospero::BufferFormat::k16_16SNorm:
|
||||
case Prospero::BufferFormat::k11_11_10SNorm:
|
||||
case Prospero::BufferFormat::k10_11_11SNorm:
|
||||
case Prospero::BufferFormat::k2_10_10_10SNorm:
|
||||
case Prospero::BufferFormat::k10_10_10_2SNorm:
|
||||
case Prospero::BufferFormat::k8_8_8_8SNorm:
|
||||
case Prospero::BufferFormat::k16_16_16_16SNorm: return ComponentType::Snorm;
|
||||
case Prospero::BufferFormat::k8UScaled:
|
||||
case Prospero::BufferFormat::k16UScaled:
|
||||
case Prospero::BufferFormat::k8_8UScaled:
|
||||
case Prospero::BufferFormat::k16_16UScaled:
|
||||
case Prospero::BufferFormat::k11_11_10UScaled:
|
||||
case Prospero::BufferFormat::k10_11_11UScaled:
|
||||
case Prospero::BufferFormat::k2_10_10_10UScaled:
|
||||
case Prospero::BufferFormat::k10_10_10_2UScaled:
|
||||
case Prospero::BufferFormat::k8_8_8_8UScaled:
|
||||
case Prospero::BufferFormat::k16_16_16_16UScaled: return ComponentType::Uscaled;
|
||||
case Prospero::BufferFormat::k8SScaled:
|
||||
case Prospero::BufferFormat::k16SScaled:
|
||||
case Prospero::BufferFormat::k8_8SScaled:
|
||||
case Prospero::BufferFormat::k16_16SScaled:
|
||||
case Prospero::BufferFormat::k11_11_10SScaled:
|
||||
case Prospero::BufferFormat::k10_11_11SScaled:
|
||||
case Prospero::BufferFormat::k2_10_10_10SScaled:
|
||||
case Prospero::BufferFormat::k10_10_10_2SScaled:
|
||||
case Prospero::BufferFormat::k8_8_8_8SScaled:
|
||||
case Prospero::BufferFormat::k16_16_16_16SScaled: return ComponentType::Sscaled;
|
||||
case Prospero::BufferFormat::k16Float:
|
||||
case Prospero::BufferFormat::k32Float:
|
||||
case Prospero::BufferFormat::k16_16Float:
|
||||
case Prospero::BufferFormat::k11_11_10Float:
|
||||
case Prospero::BufferFormat::k10_11_11Float:
|
||||
case Prospero::BufferFormat::k32_32Float:
|
||||
case Prospero::BufferFormat::k16_16_16_16Float:
|
||||
case Prospero::BufferFormat::k32_32_32Float:
|
||||
case Prospero::BufferFormat::k32_32_32_32Float: return ComponentType::Float;
|
||||
default: return ComponentType::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr BufferFormatInfo GetFormatInfo(Prospero::BufferFormat format) {
|
||||
const auto type = GetFormatComponentType(format);
|
||||
if (type == ComponentType::Unknown) {
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case Prospero::BufferFormat::k8UNorm:
|
||||
case Prospero::BufferFormat::k8SNorm:
|
||||
case Prospero::BufferFormat::k8UScaled:
|
||||
case Prospero::BufferFormat::k8SScaled:
|
||||
case Prospero::BufferFormat::k8UInt:
|
||||
case Prospero::BufferFormat::k8SInt:
|
||||
return {format, type, 1, 1, {8, 0, 0, 0}, {0, 0, 0, 0}, false};
|
||||
case Prospero::BufferFormat::k16UNorm:
|
||||
case Prospero::BufferFormat::k16SNorm:
|
||||
case Prospero::BufferFormat::k16UScaled:
|
||||
case Prospero::BufferFormat::k16SScaled:
|
||||
case Prospero::BufferFormat::k16UInt:
|
||||
case Prospero::BufferFormat::k16SInt:
|
||||
case Prospero::BufferFormat::k16Float:
|
||||
return {format, type, 1, 2, {16, 0, 0, 0}, {0, 0, 0, 0}, false};
|
||||
case Prospero::BufferFormat::k8_8UNorm:
|
||||
case Prospero::BufferFormat::k8_8SNorm:
|
||||
case Prospero::BufferFormat::k8_8UScaled:
|
||||
case Prospero::BufferFormat::k8_8SScaled:
|
||||
case Prospero::BufferFormat::k8_8UInt:
|
||||
case Prospero::BufferFormat::k8_8SInt:
|
||||
return {format, type, 2, 2, {8, 8, 0, 0}, {0, 8, 0, 0}, false};
|
||||
case Prospero::BufferFormat::k32UInt:
|
||||
case Prospero::BufferFormat::k32SInt:
|
||||
case Prospero::BufferFormat::k32Float:
|
||||
return {format, type, 1, 4, {32, 0, 0, 0}, {0, 0, 0, 0}, false};
|
||||
case Prospero::BufferFormat::k16_16UNorm:
|
||||
case Prospero::BufferFormat::k16_16SNorm:
|
||||
case Prospero::BufferFormat::k16_16UScaled:
|
||||
case Prospero::BufferFormat::k16_16SScaled:
|
||||
case Prospero::BufferFormat::k16_16UInt:
|
||||
case Prospero::BufferFormat::k16_16SInt:
|
||||
case Prospero::BufferFormat::k16_16Float:
|
||||
return {format, type, 2, 4, {16, 16, 0, 0}, {0, 16, 0, 0}, false};
|
||||
case Prospero::BufferFormat::k11_11_10UNorm:
|
||||
case Prospero::BufferFormat::k11_11_10SNorm:
|
||||
case Prospero::BufferFormat::k11_11_10UScaled:
|
||||
case Prospero::BufferFormat::k11_11_10SScaled:
|
||||
case Prospero::BufferFormat::k11_11_10UInt:
|
||||
case Prospero::BufferFormat::k11_11_10SInt:
|
||||
case Prospero::BufferFormat::k11_11_10Float:
|
||||
return {format, type, 3, 4, {11, 11, 10, 0}, {0, 11, 22, 0}, true};
|
||||
case Prospero::BufferFormat::k10_11_11UNorm:
|
||||
case Prospero::BufferFormat::k10_11_11SNorm:
|
||||
case Prospero::BufferFormat::k10_11_11UScaled:
|
||||
case Prospero::BufferFormat::k10_11_11SScaled:
|
||||
case Prospero::BufferFormat::k10_11_11UInt:
|
||||
case Prospero::BufferFormat::k10_11_11SInt:
|
||||
case Prospero::BufferFormat::k10_11_11Float:
|
||||
return {format, type, 3, 4, {10, 11, 11, 0}, {0, 10, 21, 0}, true};
|
||||
case Prospero::BufferFormat::k2_10_10_10UNorm:
|
||||
case Prospero::BufferFormat::k2_10_10_10SNorm:
|
||||
case Prospero::BufferFormat::k2_10_10_10UScaled:
|
||||
case Prospero::BufferFormat::k2_10_10_10SScaled:
|
||||
case Prospero::BufferFormat::k2_10_10_10UInt:
|
||||
case Prospero::BufferFormat::k2_10_10_10SInt:
|
||||
return {format, type, 4, 4, {2, 10, 10, 10}, {0, 2, 12, 22}, true};
|
||||
case Prospero::BufferFormat::k10_10_10_2UNorm:
|
||||
case Prospero::BufferFormat::k10_10_10_2SNorm:
|
||||
case Prospero::BufferFormat::k10_10_10_2UScaled:
|
||||
case Prospero::BufferFormat::k10_10_10_2SScaled:
|
||||
case Prospero::BufferFormat::k10_10_10_2UInt:
|
||||
case Prospero::BufferFormat::k10_10_10_2SInt:
|
||||
return {format, type, 4, 4, {10, 10, 10, 2}, {0, 10, 20, 30}, true};
|
||||
case Prospero::BufferFormat::k8_8_8_8UNorm:
|
||||
case Prospero::BufferFormat::k8_8_8_8SNorm:
|
||||
case Prospero::BufferFormat::k8_8_8_8UScaled:
|
||||
case Prospero::BufferFormat::k8_8_8_8SScaled:
|
||||
case Prospero::BufferFormat::k8_8_8_8UInt:
|
||||
case Prospero::BufferFormat::k8_8_8_8SInt:
|
||||
return {format, type, 4, 4, {8, 8, 8, 8}, {0, 8, 16, 24}, false};
|
||||
case Prospero::BufferFormat::k32_32UInt:
|
||||
case Prospero::BufferFormat::k32_32SInt:
|
||||
case Prospero::BufferFormat::k32_32Float:
|
||||
return {format, type, 2, 8, {32, 32, 0, 0}, {0, 32, 0, 0}, false};
|
||||
case Prospero::BufferFormat::k16_16_16_16UNorm:
|
||||
case Prospero::BufferFormat::k16_16_16_16SNorm:
|
||||
case Prospero::BufferFormat::k16_16_16_16UScaled:
|
||||
case Prospero::BufferFormat::k16_16_16_16SScaled:
|
||||
case Prospero::BufferFormat::k16_16_16_16UInt:
|
||||
case Prospero::BufferFormat::k16_16_16_16SInt:
|
||||
case Prospero::BufferFormat::k16_16_16_16Float:
|
||||
return {format, type, 4, 8, {16, 16, 16, 16}, {0, 16, 32, 48}, false};
|
||||
case Prospero::BufferFormat::k32_32_32UInt:
|
||||
case Prospero::BufferFormat::k32_32_32SInt:
|
||||
case Prospero::BufferFormat::k32_32_32Float:
|
||||
return {format, type, 3, 12, {32, 32, 32, 0}, {0, 32, 64, 0}, false};
|
||||
case Prospero::BufferFormat::k32_32_32_32UInt:
|
||||
case Prospero::BufferFormat::k32_32_32_32SInt:
|
||||
case Prospero::BufferFormat::k32_32_32_32Float:
|
||||
return {format, type, 4, 16, {32, 32, 32, 32}, {0, 32, 64, 96}, false};
|
||||
default: return {};
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool IsKnownFormat(Prospero::BufferFormat format) {
|
||||
return GetFormatInfo(format).type != ComponentType::Unknown;
|
||||
}
|
||||
|
||||
constexpr uint32_t GetFormatComponentCount(Prospero::BufferFormat format) {
|
||||
return GetFormatInfo(format).component_count;
|
||||
}
|
||||
|
||||
constexpr uint32_t GetFormatByteSize(Prospero::BufferFormat format) {
|
||||
return GetFormatInfo(format).byte_size;
|
||||
}
|
||||
|
||||
constexpr uint32_t GetFormatComponentBits(Prospero::BufferFormat format, uint32_t component) {
|
||||
const auto info = GetFormatInfo(format);
|
||||
return component < info.component_count ? info.component_bits[component] : 0u;
|
||||
}
|
||||
|
||||
constexpr uint32_t GetFormatComponentBitOffset(Prospero::BufferFormat format, uint32_t component) {
|
||||
const auto info = GetFormatInfo(format);
|
||||
return component < info.component_count ? info.component_bit_offset[component] : 0u;
|
||||
}
|
||||
|
||||
constexpr uint32_t GetFormatComponentByteOffset(Prospero::BufferFormat format, uint32_t component) {
|
||||
const auto info = GetFormatInfo(format);
|
||||
if (component >= info.component_count) {
|
||||
return 0;
|
||||
}
|
||||
return info.packed_bitfield ? 0u : info.component_bit_offset[component] / 8u;
|
||||
}
|
||||
|
||||
constexpr bool CanUseTypedBufferLoad(Prospero::BufferFormat format) {
|
||||
const auto info = GetFormatInfo(format);
|
||||
return info.type != ComponentType::Unknown && !info.packed_bitfield &&
|
||||
info.component_bits[0] == 32u;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Format
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_BUFFERFORMAT_H_ */
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "graphics/shader/recompiler/ExecMask.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Exec {
|
||||
|
||||
Mask FullMask(uint32_t wave_size) {
|
||||
return {0xffffffffu, wave_size == 64u ? 0xffffffffu : 0u};
|
||||
}
|
||||
|
||||
Mask ApplyWaveSize(Mask mask, uint32_t wave_size) {
|
||||
const auto full = FullMask(wave_size);
|
||||
mask.low &= full.low;
|
||||
mask.high &= full.high;
|
||||
return mask;
|
||||
}
|
||||
|
||||
Mask ReadExec(const State& state) {
|
||||
return ApplyWaveSize(state.exec, state.wave_size);
|
||||
}
|
||||
|
||||
void WriteExec(State& state, Mask mask) {
|
||||
state.exec = ApplyWaveSize(mask, state.wave_size);
|
||||
}
|
||||
|
||||
void AndExec(State& state, Mask mask) {
|
||||
WriteExec(state, {state.exec.low & mask.low, state.exec.high & mask.high});
|
||||
}
|
||||
|
||||
void OrExec(State& state, Mask mask) {
|
||||
WriteExec(state, {state.exec.low | mask.low, state.exec.high | mask.high});
|
||||
}
|
||||
|
||||
void XorExec(State& state, Mask mask) {
|
||||
WriteExec(state, {state.exec.low ^ mask.low, state.exec.high ^ mask.high});
|
||||
}
|
||||
|
||||
void InvertExec(State& state) {
|
||||
const auto full = FullMask(state.wave_size);
|
||||
WriteExec(state, {~state.exec.low & full.low, ~state.exec.high & full.high});
|
||||
}
|
||||
|
||||
bool IsExecZero(const State& state) {
|
||||
const auto exec = ReadExec(state);
|
||||
return exec.low == 0u && exec.high == 0u;
|
||||
}
|
||||
|
||||
bool IsVccZero(const State& state) {
|
||||
const auto vcc = ApplyWaveSize(state.vcc, state.wave_size);
|
||||
return vcc.low == 0u && vcc.high == 0u;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Exec
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_EXECMASK_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_EXECMASK_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Exec {
|
||||
|
||||
struct Mask {
|
||||
uint32_t low = 0;
|
||||
uint32_t high = 0;
|
||||
};
|
||||
|
||||
struct State {
|
||||
uint32_t wave_size = 64;
|
||||
Mask exec;
|
||||
Mask vcc;
|
||||
bool scc = false;
|
||||
};
|
||||
|
||||
Mask FullMask(uint32_t wave_size);
|
||||
Mask ApplyWaveSize(Mask mask, uint32_t wave_size);
|
||||
|
||||
Mask ReadExec(const State& state);
|
||||
void WriteExec(State& state, Mask mask);
|
||||
void AndExec(State& state, Mask mask);
|
||||
void OrExec(State& state, Mask mask);
|
||||
void XorExec(State& state, Mask mask);
|
||||
void InvertExec(State& state);
|
||||
bool IsExecZero(const State& state);
|
||||
bool IsVccZero(const State& state);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Exec
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_EXECMASK_H_ */
|
||||
@@ -0,0 +1,53 @@
|
||||
#include "graphics/shader/recompiler/ExportOps.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
|
||||
bool DecodeExp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error) {
|
||||
if (word_index + 1u >= code.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("truncated EXP instruction at pc 0x{:08x}", pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t word0 = code[word_index];
|
||||
const uint32_t word1 = code[word_index + 1u];
|
||||
const uint32_t target = (word0 >> 4u) & 0x3fu;
|
||||
const uint32_t en = word0 & 0xfu;
|
||||
|
||||
inst->pc = pc;
|
||||
inst->word = word0;
|
||||
inst->word_count = 2;
|
||||
inst->family = Family::EXP;
|
||||
inst->opcode_id = target;
|
||||
inst->opcode = Opcode::Exp;
|
||||
inst->exp.target = target;
|
||||
inst->exp.en = en;
|
||||
inst->exp.compr = ((word0 >> 10u) & 1u) != 0;
|
||||
inst->exp.done = ((word0 >> 11u) & 1u) != 0;
|
||||
inst->exp.vm = ((word0 >> 12u) & 1u) != 0;
|
||||
SetRawWords(inst, code, word_index, 2);
|
||||
|
||||
if (!DecodeVectorGpr(word1 & 0xffu, &inst->src0, error) ||
|
||||
!DecodeVectorGpr((word1 >> 8u) & 0xffu, &inst->src1, error) ||
|
||||
!DecodeVectorGpr((word1 >> 16u) & 0xffu, &inst->src2, error) ||
|
||||
!DecodeVectorGpr((word1 >> 24u) & 0xffu, &inst->src3, error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target == 0x14u && inst->exp.done && en == 0x1u) {
|
||||
inst->src_count = 1u;
|
||||
} else {
|
||||
inst->src_count = inst->exp.compr ? 2u : 4u;
|
||||
}
|
||||
if (en == 0u) {
|
||||
inst->src_count = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_EXPORTOPS_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_EXPORTOPS_H_
|
||||
|
||||
#include "graphics/shader/recompiler/ShaderDecoder.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
|
||||
bool DecodeExp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_EXPORTOPS_H_ */
|
||||
@@ -0,0 +1,331 @@
|
||||
#include "graphics/shader/recompiler/ImageOps.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
#include <iterator>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
namespace {
|
||||
|
||||
struct MimgSampleInfo {
|
||||
uint32_t opcode = 0;
|
||||
const char* name = nullptr;
|
||||
uint32_t flags = 0;
|
||||
uint32_t address_components = 3;
|
||||
};
|
||||
|
||||
struct MimgGatherInfo {
|
||||
uint32_t opcode = 0;
|
||||
const char* name = nullptr;
|
||||
Opcode decoded = Opcode::Unsupported;
|
||||
uint32_t flags = 0;
|
||||
uint32_t address_components = 3;
|
||||
};
|
||||
|
||||
struct MimgAtomicInfo {
|
||||
uint32_t opcode = 0;
|
||||
const char* name = nullptr;
|
||||
Opcode decoded = Opcode::Unsupported;
|
||||
};
|
||||
|
||||
constexpr ImageDimension DecodeImageDimension(uint32_t dim) {
|
||||
switch (dim) {
|
||||
case 1u: return ImageDimension::Dim2D;
|
||||
case 2u: return ImageDimension::Dim3D;
|
||||
case 3u: return ImageDimension::Dim2DArray;
|
||||
case 5u:
|
||||
case 7u: return ImageDimension::Dim2DArray;
|
||||
case 6u: return ImageDimension::Dim2D;
|
||||
default: return ImageDimension::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr uint32_t ImageCoordComponents(ImageDimension dimension) {
|
||||
switch (dimension) {
|
||||
case ImageDimension::Dim3D:
|
||||
case ImageDimension::Dim2DArray: return 3u;
|
||||
default: return 2u;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr uint32_t ImageSampleAddressComponents(uint32_t flags, ImageDimension dimension) {
|
||||
const auto coord_components = ImageCoordComponents(dimension);
|
||||
uint32_t components = coord_components;
|
||||
if ((flags & ImageSampleFlagOffset) != 0) {
|
||||
components++;
|
||||
}
|
||||
if ((flags & ImageSampleFlagCompare) != 0) {
|
||||
components++;
|
||||
}
|
||||
if ((flags & ImageSampleFlagBias) != 0) {
|
||||
components++;
|
||||
}
|
||||
if ((flags & ImageSampleFlagLod) != 0) {
|
||||
components++;
|
||||
}
|
||||
if ((flags & ImageSampleFlagDerivative) != 0) {
|
||||
components += 4u;
|
||||
}
|
||||
return components;
|
||||
}
|
||||
|
||||
constexpr MimgSampleInfo SampleInfo(uint32_t opcode, const char* name, uint32_t flags) {
|
||||
return {opcode, name, flags, ImageSampleAddressComponents(flags, ImageDimension::Dim2D)};
|
||||
}
|
||||
|
||||
constexpr MimgSampleInfo MIMG_SAMPLE_OPS[] = {
|
||||
SampleInfo(0x20u, "image_sample", 0),
|
||||
SampleInfo(0x21u, "image_sample_cl", ImageSampleFlagLodClamp),
|
||||
SampleInfo(0x22u, "image_sample_d", ImageSampleFlagDerivative),
|
||||
SampleInfo(0x23u, "image_sample_d_cl", ImageSampleFlagDerivative | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0x24u, "image_sample_l", ImageSampleFlagLod),
|
||||
SampleInfo(0x25u, "image_sample_b", ImageSampleFlagBias),
|
||||
SampleInfo(0x26u, "image_sample_b_cl", ImageSampleFlagBias | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0x27u, "image_sample_lz", ImageSampleFlagLevelZero),
|
||||
SampleInfo(0x28u, "image_sample_c", ImageSampleFlagCompare),
|
||||
SampleInfo(0x29u, "image_sample_c_cl", ImageSampleFlagCompare | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0x2au, "image_sample_c_d", ImageSampleFlagCompare | ImageSampleFlagDerivative),
|
||||
SampleInfo(0x2bu, "image_sample_c_d_cl",
|
||||
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0x2cu, "image_sample_c_l", ImageSampleFlagCompare | ImageSampleFlagLod),
|
||||
SampleInfo(0x2du, "image_sample_c_b", ImageSampleFlagCompare | ImageSampleFlagBias),
|
||||
SampleInfo(0x2eu, "image_sample_c_b_cl",
|
||||
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0x2fu, "image_sample_c_lz", ImageSampleFlagCompare | ImageSampleFlagLevelZero),
|
||||
SampleInfo(0x30u, "image_sample_o", ImageSampleFlagOffset),
|
||||
SampleInfo(0x31u, "image_sample_cl_o", ImageSampleFlagLodClamp | ImageSampleFlagOffset),
|
||||
SampleInfo(0x32u, "image_sample_d_o", ImageSampleFlagDerivative | ImageSampleFlagOffset),
|
||||
SampleInfo(0x33u, "image_sample_d_cl_o",
|
||||
ImageSampleFlagDerivative | ImageSampleFlagLodClamp | ImageSampleFlagOffset),
|
||||
SampleInfo(0x34u, "image_sample_l_o", ImageSampleFlagLod | ImageSampleFlagOffset),
|
||||
SampleInfo(0x35u, "image_sample_b_o", ImageSampleFlagBias | ImageSampleFlagOffset),
|
||||
SampleInfo(0x36u, "image_sample_b_cl_o",
|
||||
ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagOffset),
|
||||
SampleInfo(0x37u, "image_sample_lz_o", ImageSampleFlagLevelZero | ImageSampleFlagOffset),
|
||||
SampleInfo(0x38u, "image_sample_c_o", ImageSampleFlagCompare | ImageSampleFlagOffset),
|
||||
SampleInfo(0x39u, "image_sample_c_cl_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagOffset),
|
||||
SampleInfo(0x3au, "image_sample_c_d_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagOffset),
|
||||
SampleInfo(0x3bu, "image_sample_c_d_cl_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagLodClamp |
|
||||
ImageSampleFlagOffset),
|
||||
SampleInfo(0x3cu, "image_sample_c_l_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagLod | ImageSampleFlagOffset),
|
||||
SampleInfo(0x3du, "image_sample_c_b_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagOffset),
|
||||
SampleInfo(0x3eu, "image_sample_c_b_cl_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp |
|
||||
ImageSampleFlagOffset),
|
||||
SampleInfo(0x3fu, "image_sample_c_lz_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagLevelZero | ImageSampleFlagOffset),
|
||||
SampleInfo(0x68u, "image_sample_cd", ImageSampleFlagDerivative | ImageSampleFlagCd),
|
||||
SampleInfo(0x69u, "image_sample_cd_cl",
|
||||
ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0x6au, "image_sample_c_cd",
|
||||
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd),
|
||||
SampleInfo(0x6bu, "image_sample_c_cd_cl",
|
||||
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd |
|
||||
ImageSampleFlagLodClamp),
|
||||
SampleInfo(0x6cu, "image_sample_cd_o",
|
||||
ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagOffset),
|
||||
SampleInfo(0x6du, "image_sample_cd_cl_o",
|
||||
ImageSampleFlagDerivative | ImageSampleFlagCd | ImageSampleFlagLodClamp |
|
||||
ImageSampleFlagOffset),
|
||||
SampleInfo(0x6eu, "image_sample_c_cd_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd |
|
||||
ImageSampleFlagOffset),
|
||||
SampleInfo(0x6fu, "image_sample_c_cd_cl_o",
|
||||
ImageSampleFlagCompare | ImageSampleFlagDerivative | ImageSampleFlagCd |
|
||||
ImageSampleFlagLodClamp | ImageSampleFlagOffset),
|
||||
SampleInfo(0xa0u, "image_sample_a", 0),
|
||||
SampleInfo(0xa1u, "image_sample_cl_a", ImageSampleFlagLodClamp),
|
||||
SampleInfo(0xa5u, "image_sample_b_a", ImageSampleFlagBias),
|
||||
SampleInfo(0xa6u, "image_sample_b_cl_a", ImageSampleFlagBias | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0xa8u, "image_sample_c_a", ImageSampleFlagCompare),
|
||||
SampleInfo(0xa9u, "image_sample_c_cl_a", ImageSampleFlagCompare | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0xadu, "image_sample_c_b_a", ImageSampleFlagCompare | ImageSampleFlagBias),
|
||||
SampleInfo(0xaeu, "image_sample_c_b_cl_a",
|
||||
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp),
|
||||
SampleInfo(0xb0u, "image_sample_o_a", ImageSampleFlagOffset),
|
||||
SampleInfo(0xb1u, "image_sample_cl_o_a", ImageSampleFlagLodClamp | ImageSampleFlagOffset),
|
||||
SampleInfo(0xb5u, "image_sample_b_o_a", ImageSampleFlagBias | ImageSampleFlagOffset),
|
||||
SampleInfo(0xb6u, "image_sample_b_cl_o_a",
|
||||
ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagOffset),
|
||||
SampleInfo(0xb8u, "image_sample_c_o_a", ImageSampleFlagCompare | ImageSampleFlagOffset),
|
||||
SampleInfo(0xb9u, "image_sample_c_cl_o_a",
|
||||
ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagOffset),
|
||||
SampleInfo(0xbdu, "image_sample_c_b_o_a",
|
||||
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagOffset),
|
||||
SampleInfo(0xbeu, "image_sample_c_b_cl_o_a",
|
||||
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp |
|
||||
ImageSampleFlagOffset),
|
||||
};
|
||||
|
||||
constexpr MimgGatherInfo MIMG_GATHER_OPS[] = {
|
||||
{0x47u, "image_gather4_lz", Opcode::ImageGather4Lz, ImageSampleFlagLevelZero, 2u},
|
||||
{0x48u, "image_gather4_c", Opcode::ImageGather4C, ImageSampleFlagCompare, 3u},
|
||||
{0x4fu, "image_gather4_c_lz", Opcode::ImageGather4CLz,
|
||||
ImageSampleFlagCompare | ImageSampleFlagLevelZero, 3u},
|
||||
{0x57u, "image_gather4_lz_o", Opcode::ImageGather4LzO,
|
||||
ImageSampleFlagLevelZero | ImageSampleFlagOffset, 3u},
|
||||
{0x58u, "image_gather4_c_o", Opcode::ImageGather4CO,
|
||||
ImageSampleFlagCompare | ImageSampleFlagOffset, 4u},
|
||||
{0x5fu, "image_gather4_c_lz_o", Opcode::ImageGather4CLzO,
|
||||
ImageSampleFlagCompare | ImageSampleFlagLevelZero | ImageSampleFlagOffset, 4u},
|
||||
{0x61u, "image_gather4h", Opcode::ImageGather4H, ImageSampleFlagGatherHorizontal, 2u},
|
||||
};
|
||||
|
||||
constexpr MimgAtomicInfo MIMG_ATOMIC_OPS[] = {
|
||||
{0x11u, "image_atomic_add", Opcode::ImageAtomicAdd},
|
||||
{0x15u, "image_atomic_umin", Opcode::ImageAtomicUMin},
|
||||
{0x17u, "image_atomic_umax", Opcode::ImageAtomicUMax},
|
||||
{0x18u, "image_atomic_and", Opcode::ImageAtomicAnd},
|
||||
{0x19u, "image_atomic_or", Opcode::ImageAtomicOr},
|
||||
{0x1au, "image_atomic_xor", Opcode::ImageAtomicXor},
|
||||
};
|
||||
|
||||
const MimgSampleInfo* LookupSample(uint32_t opcode) {
|
||||
for (const auto& info: MIMG_SAMPLE_OPS) {
|
||||
if (info.opcode == opcode) {
|
||||
return &info;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const MimgGatherInfo* LookupGather(uint32_t opcode) {
|
||||
for (const auto& info: MIMG_GATHER_OPS) {
|
||||
if (info.opcode == opcode) {
|
||||
return &info;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const MimgAtomicInfo* LookupAtomic(uint32_t opcode) {
|
||||
for (const auto& info: MIMG_ATOMIC_OPS) {
|
||||
if (info.opcode == opcode) {
|
||||
return &info;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t CountDmaskComponents(uint32_t dmask) {
|
||||
uint32_t count = 0;
|
||||
for (uint32_t i = 0; i < 4u; i++) {
|
||||
count += (dmask >> i) & 1u;
|
||||
}
|
||||
return count != 0 ? count : 1u;
|
||||
}
|
||||
|
||||
bool IsSingleDmaskBit(uint32_t dmask) {
|
||||
return dmask != 0u && (dmask & (dmask - 1u)) == 0u;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error) {
|
||||
if (word_index + 1u >= code.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("truncated MIMG instruction at pc 0x{:08x}", pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t word0 = code[word_index];
|
||||
const uint32_t word1 = code[word_index + 1u];
|
||||
const uint32_t opcode = ((word0 >> 18u) & 0x7fu) | ((word0 & 1u) << 7u);
|
||||
const uint32_t nsa_dwords = (word0 >> 1u) & 0x3u;
|
||||
const auto dimension = DecodeImageDimension((word0 >> 3u) & 0x7u);
|
||||
const uint32_t word_count = 2u + nsa_dwords;
|
||||
if (word_index + word_count > code.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("truncated MIMG NSA instruction at pc 0x{:08x}", pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t vdata = (word1 >> 8u) & 0xffu;
|
||||
const uint32_t vaddr = word1 & 0xffu;
|
||||
const uint32_t srsrc = (word1 >> 16u) & 0x1fu;
|
||||
const uint32_t ssamp = (word1 >> 21u) & 0x1fu;
|
||||
const bool a16 = ((word1 >> 30u) & 0x1u) != 0u;
|
||||
const auto* sample = LookupSample(opcode);
|
||||
const auto* gather = LookupGather(opcode);
|
||||
const auto* atomic = LookupAtomic(opcode);
|
||||
|
||||
inst->pc = pc;
|
||||
inst->word = word0;
|
||||
inst->word_count = word_count;
|
||||
inst->family = Family::MIMG;
|
||||
inst->opcode_id = opcode;
|
||||
inst->opcode = (sample != nullptr ? Opcode::ImageSample
|
||||
: gather != nullptr ? gather->decoded
|
||||
: atomic != nullptr ? atomic->decoded
|
||||
: opcode == 0x00u ? Opcode::ImageLoad
|
||||
: opcode == 0x01u ? Opcode::ImageLoadMip
|
||||
: opcode == 0x08u ? Opcode::ImageStore
|
||||
: opcode == 0x09u ? Opcode::ImageStoreMip
|
||||
: opcode == 0x0eu ? Opcode::ImageGetResinfo
|
||||
: opcode == 0x60u ? Opcode::ImageGetLod
|
||||
: Opcode::Unsupported);
|
||||
inst->dmask = (word0 >> 8u) & 0xfu;
|
||||
inst->data_dwords = gather != nullptr ? 4u : CountDmaskComponents(inst->dmask);
|
||||
inst->glc = ((word0 >> 13u) & 1u) != 0;
|
||||
inst->slc = ((word0 >> 25u) & 1u) != 0;
|
||||
inst->image_sample_flags = (sample != nullptr ? sample->flags
|
||||
: gather != nullptr ? gather->flags
|
||||
: 0u) |
|
||||
(a16 ? ImageSampleFlagA16 : 0u);
|
||||
inst->image_dimension = dimension;
|
||||
inst->image_nsa_dwords = nsa_dwords;
|
||||
for (uint32_t i = 0; i < nsa_dwords * 4u && i < MaxImageNsaAddressComponents; i++) {
|
||||
inst->image_nsa_addr[i] = (code[word_index + 2u + i / 4u] >> ((i % 4u) * 8u)) & 0xffu;
|
||||
}
|
||||
inst->image_address_components =
|
||||
sample != nullptr ? ImageSampleAddressComponents(sample->flags, dimension)
|
||||
: gather != nullptr ? ImageSampleAddressComponents(gather->flags, dimension)
|
||||
: atomic != nullptr ? 3u
|
||||
: opcode == 0x60u ? ImageCoordComponents(dimension)
|
||||
: opcode == 0x01u ? ImageCoordComponents(dimension) + 1u
|
||||
: opcode == 0x00u ? ImageCoordComponents(dimension)
|
||||
: (opcode == 0x09u ? ImageCoordComponents(dimension) + 1u
|
||||
: opcode == 0x08u ? ImageCoordComponents(dimension)
|
||||
: 0u);
|
||||
SetRawWords(inst, code, word_index, word_count);
|
||||
|
||||
if (inst->opcode == Opcode::Unsupported) {
|
||||
SetUnsupported(inst, Family::MIMG, opcode, "MIMG opcode is not implemented");
|
||||
}
|
||||
if (gather != nullptr && !IsSingleDmaskBit(inst->dmask)) {
|
||||
SetUnsupported(inst, Family::MIMG, opcode,
|
||||
"MIMG image gather requires exactly one dmask bit");
|
||||
}
|
||||
|
||||
DecodeVectorGpr(vdata, &inst->dst, nullptr);
|
||||
DecodeVectorGpr(vaddr, &inst->src0, nullptr);
|
||||
DecodeScalarSource(srsrc * 4u, pc, &inst->src1, nullptr);
|
||||
DecodeScalarSource(ssamp * 4u, pc, &inst->src2, nullptr);
|
||||
inst->src_count = 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* MimgSampleOpcodeName(uint32_t opcode) {
|
||||
const auto* sample = LookupSample(opcode);
|
||||
return sample != nullptr ? sample->name : nullptr;
|
||||
}
|
||||
|
||||
uint32_t MimgSampleOpcodeFlags(uint32_t opcode) {
|
||||
const auto* sample = LookupSample(opcode);
|
||||
return sample != nullptr ? sample->flags : 0u;
|
||||
}
|
||||
|
||||
uint32_t MimgSampleAddressComponents(uint32_t opcode) {
|
||||
const auto* sample = LookupSample(opcode);
|
||||
return sample != nullptr ? sample->address_components : 0u;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_IMAGEOPS_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_IMAGEOPS_H_
|
||||
|
||||
#include "graphics/shader/recompiler/ShaderDecoder.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
|
||||
bool DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error);
|
||||
|
||||
const char* MimgSampleOpcodeName(uint32_t opcode);
|
||||
uint32_t MimgSampleOpcodeFlags(uint32_t opcode);
|
||||
uint32_t MimgSampleAddressComponents(uint32_t opcode);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_IMAGEOPS_H_ */
|
||||
@@ -0,0 +1,480 @@
|
||||
#include "graphics/shader/recompiler/MemoryOps.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <iterator>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
namespace {
|
||||
|
||||
struct MemoryOpcodeInfo {
|
||||
uint32_t opcode = 0;
|
||||
Opcode decoded = Opcode::Unsupported;
|
||||
uint32_t data_dwords = 1;
|
||||
uint32_t data_bits = 32;
|
||||
bool data_signed = false;
|
||||
bool typed = false;
|
||||
bool formatted = false;
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo SMEM_OPS[] = {
|
||||
{0x00u, Opcode::SLoadDword, 1, 32}, {0x01u, Opcode::SLoadDwordx2, 2, 32},
|
||||
{0x02u, Opcode::SLoadDwordx4, 4, 32}, {0x03u, Opcode::SLoadDwordx8, 8, 32},
|
||||
{0x04u, Opcode::SLoadDwordx16, 16, 32}, {0x08u, Opcode::SBufferLoadDword, 1, 32},
|
||||
{0x09u, Opcode::SBufferLoadDwordx2, 2, 32}, {0x0au, Opcode::SBufferLoadDwordx4, 4, 32},
|
||||
{0x0bu, Opcode::SBufferLoadDwordx8, 8, 32}, {0x0cu, Opcode::SBufferLoadDwordx16, 16, 32},
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo MUBUF_OPS[] = {
|
||||
{0x00u, Opcode::BufferLoadFormatX, 1, 32, false, false, true},
|
||||
{0x01u, Opcode::BufferLoadFormatXy, 2, 32, false, false, true},
|
||||
{0x02u, Opcode::BufferLoadFormatXyz, 3, 32, false, false, true},
|
||||
{0x03u, Opcode::BufferLoadFormatXyzw, 4, 32, false, false, true},
|
||||
{0x04u, Opcode::BufferStoreFormatX, 1, 32, false, false, true},
|
||||
{0x05u, Opcode::BufferStoreFormatXy, 2, 32, false, false, true},
|
||||
{0x06u, Opcode::BufferStoreFormatXyz, 3, 32, false, false, true},
|
||||
{0x07u, Opcode::BufferStoreFormatXyzw, 4, 32, false, false, true},
|
||||
{0x08u, Opcode::BufferLoadUbyte, 1, 8},
|
||||
{0x09u, Opcode::BufferLoadSbyte, 1, 8, true},
|
||||
{0x0au, Opcode::BufferLoadUshort, 1, 16},
|
||||
{0x0bu, Opcode::BufferLoadSshort, 1, 16, true},
|
||||
{0x0cu, Opcode::BufferLoadDword, 1, 32},
|
||||
{0x0du, Opcode::BufferLoadDwordx2, 2, 32},
|
||||
{0x0eu, Opcode::BufferLoadDwordx4, 4, 32},
|
||||
{0x0fu, Opcode::BufferLoadDwordx3, 3, 32},
|
||||
{0x18u, Opcode::BufferStoreByte, 1, 8},
|
||||
{0x1au, Opcode::BufferStoreShort, 1, 16},
|
||||
{0x1cu, Opcode::BufferStoreDword, 1, 32},
|
||||
{0x1du, Opcode::BufferStoreDwordx2, 2, 32},
|
||||
{0x1eu, Opcode::BufferStoreDwordx4, 4, 32},
|
||||
{0x1fu, Opcode::BufferStoreDwordx3, 3, 32},
|
||||
{0x30u, Opcode::BufferAtomicSwap, 1, 32},
|
||||
{0x32u, Opcode::BufferAtomicAdd, 1, 32},
|
||||
{0x33u, Opcode::BufferAtomicSub, 1, 32},
|
||||
{0x35u, Opcode::BufferAtomicSMin, 1, 32},
|
||||
{0x36u, Opcode::BufferAtomicUMin, 1, 32},
|
||||
{0x37u, Opcode::BufferAtomicSMax, 1, 32},
|
||||
{0x38u, Opcode::BufferAtomicUMax, 1, 32},
|
||||
{0x39u, Opcode::BufferAtomicAnd, 1, 32},
|
||||
{0x3au, Opcode::BufferAtomicOr, 1, 32},
|
||||
{0x3bu, Opcode::BufferAtomicXor, 1, 32},
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo MTBUF_OPS[] = {
|
||||
{0x00u, Opcode::TBufferLoadFormatX, 1, 32, false, true, true},
|
||||
{0x01u, Opcode::TBufferLoadFormatXy, 2, 32, false, true, true},
|
||||
{0x02u, Opcode::TBufferLoadFormatXyz, 3, 32, false, true, true},
|
||||
{0x03u, Opcode::TBufferLoadFormatXyzw, 4, 32, false, true, true},
|
||||
{0x04u, Opcode::TBufferStoreFormatX, 1, 32, false, true, true},
|
||||
{0x05u, Opcode::TBufferStoreFormatXy, 2, 32, false, true, true},
|
||||
{0x06u, Opcode::TBufferStoreFormatXyz, 3, 32, false, true, true},
|
||||
{0x07u, Opcode::TBufferStoreFormatXyzw, 4, 32, false, true, true},
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo FLAT_OPS[] = {
|
||||
{0x08u, Opcode::FlatLoadUbyte, 1, 8}, {0x09u, Opcode::FlatLoadSbyte, 1, 8, true},
|
||||
{0x0au, Opcode::FlatLoadUshort, 1, 16}, {0x0bu, Opcode::FlatLoadSshort, 1, 16, true},
|
||||
{0x0cu, Opcode::FlatLoadDword, 1, 32}, {0x0du, Opcode::FlatLoadDwordx2, 2, 32},
|
||||
{0x0eu, Opcode::FlatLoadDwordx4, 4, 32}, {0x0fu, Opcode::FlatLoadDwordx3, 3, 32},
|
||||
{0x18u, Opcode::FlatStoreByte, 1, 8}, {0x1au, Opcode::FlatStoreShort, 1, 16},
|
||||
{0x1cu, Opcode::FlatStoreDword, 1, 32}, {0x1du, Opcode::FlatStoreDwordx2, 2, 32},
|
||||
{0x1eu, Opcode::FlatStoreDwordx4, 4, 32}, {0x1fu, Opcode::FlatStoreDwordx3, 3, 32},
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo DS_OPS[] = {
|
||||
{0x00u, Opcode::DsAddU32, 1, 32}, {0x01u, Opcode::DsSubU32, 1, 32},
|
||||
{0x05u, Opcode::DsMinI32, 1, 32}, {0x06u, Opcode::DsMaxI32, 1, 32},
|
||||
{0x07u, Opcode::DsMinU32, 1, 32}, {0x08u, Opcode::DsMaxU32, 1, 32},
|
||||
{0x09u, Opcode::DsAndB32, 1, 32}, {0x0au, Opcode::DsOrB32, 1, 32},
|
||||
{0x0bu, Opcode::DsXorB32, 1, 32}, {0x0du, Opcode::DsWriteB32, 1, 32},
|
||||
{0x0eu, Opcode::DsWrite2B32, 2, 32}, {0x0fu, Opcode::DsWrite2St64B32, 2, 32},
|
||||
{0x12u, Opcode::DsMinF32, 1, 32}, {0x13u, Opcode::DsMaxF32, 1, 32},
|
||||
{0x1eu, Opcode::DsWriteByte, 1, 8}, {0x1fu, Opcode::DsWriteShort, 1, 16},
|
||||
{0x20u, Opcode::DsAddRtnU32, 1, 32}, {0x21u, Opcode::DsSubRtnU32, 1, 32},
|
||||
{0x25u, Opcode::DsMinRtnI32, 1, 32}, {0x26u, Opcode::DsMaxRtnI32, 1, 32},
|
||||
{0x27u, Opcode::DsMinRtnU32, 1, 32}, {0x28u, Opcode::DsMaxRtnU32, 1, 32},
|
||||
{0x29u, Opcode::DsAndRtnB32, 1, 32}, {0x2au, Opcode::DsOrRtnB32, 1, 32},
|
||||
{0x2bu, Opcode::DsXorRtnB32, 1, 32}, {0x2du, Opcode::DsWrxchgRtnB32, 1, 32},
|
||||
{0x35u, Opcode::DsSwizzleB32, 1, 32}, {0x36u, Opcode::DsReadB32, 1, 32},
|
||||
{0x37u, Opcode::DsRead2B32, 2, 32}, {0x38u, Opcode::DsRead2B32, 2, 32},
|
||||
{0x39u, Opcode::DsReadSbyte, 1, 8, true}, {0x3au, Opcode::DsReadUbyte, 1, 8},
|
||||
{0x3bu, Opcode::DsReadSshort, 1, 16, true}, {0x3cu, Opcode::DsReadUshort, 1, 16},
|
||||
{0x3du, Opcode::DsConsume, 1, 32}, {0x3eu, Opcode::DsAppend, 1, 32},
|
||||
{0x4du, Opcode::DsWriteB64, 2, 32}, {0x4eu, Opcode::DsWrite2B64, 4, 32},
|
||||
{0x4fu, Opcode::DsWrite2St64B64, 4, 32}, {0x76u, Opcode::DsReadB64, 2, 32},
|
||||
{0x77u, Opcode::DsRead2B64, 4, 32}, {0x78u, Opcode::DsRead2St64B64, 4, 32},
|
||||
{0xb0u, Opcode::DsWriteAddtidB32, 1, 32}, {0xb1u, Opcode::DsReadAddtidB32, 1, 32},
|
||||
{0xdeu, Opcode::DsWriteB96, 3, 32}, {0xdfu, Opcode::DsWriteB128, 4, 32},
|
||||
{0xfeu, Opcode::DsReadB96, 3, 32}, {0xffu, Opcode::DsReadB128, 4, 32},
|
||||
};
|
||||
|
||||
const MemoryOpcodeInfo* LookupMemoryOpcode(const MemoryOpcodeInfo* ops, uint32_t count,
|
||||
uint32_t opcode) {
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (ops[i].opcode == opcode) {
|
||||
return &ops[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t SignExtendU32(uint32_t value, uint32_t bits) {
|
||||
if (bits == 0u || bits >= 32u) {
|
||||
return value;
|
||||
}
|
||||
const uint32_t sign = 1u << (bits - 1u);
|
||||
return (value ^ sign) - sign;
|
||||
}
|
||||
|
||||
void MarkMemoryUnsupported(Instruction* inst, Family family, uint32_t opcode, const char* reason) {
|
||||
inst->family = family;
|
||||
inst->opcode_id = opcode;
|
||||
inst->opcode = Opcode::Unsupported;
|
||||
SetUnsupported(inst, family, opcode, reason);
|
||||
}
|
||||
|
||||
void ApplyMemoryInfo(Instruction* inst, const MemoryOpcodeInfo* info) {
|
||||
if (info == nullptr) {
|
||||
inst->opcode = Opcode::Unsupported;
|
||||
return;
|
||||
}
|
||||
inst->opcode = info->decoded;
|
||||
inst->data_dwords = info->data_dwords;
|
||||
inst->data_bits = info->data_bits;
|
||||
inst->data_signed = info->data_signed;
|
||||
inst->typed = info->typed;
|
||||
inst->formatted = info->formatted;
|
||||
}
|
||||
|
||||
bool IsDsWriteOpcode(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::DsWriteByte:
|
||||
case Opcode::DsWriteShort:
|
||||
case Opcode::DsWrite2B32:
|
||||
case Opcode::DsWrite2St64B32:
|
||||
case Opcode::DsWrite2B64:
|
||||
case Opcode::DsWrite2St64B64:
|
||||
case Opcode::DsWriteB32:
|
||||
case Opcode::DsWriteB64:
|
||||
case Opcode::DsWriteB96:
|
||||
case Opcode::DsWriteB128: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsDsAtomicOpcode(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::DsAddU32:
|
||||
case Opcode::DsAddRtnU32:
|
||||
case Opcode::DsSubU32:
|
||||
case Opcode::DsSubRtnU32:
|
||||
case Opcode::DsMinI32:
|
||||
case Opcode::DsMinRtnI32:
|
||||
case Opcode::DsMaxI32:
|
||||
case Opcode::DsMaxRtnI32:
|
||||
case Opcode::DsMinU32:
|
||||
case Opcode::DsMinRtnU32:
|
||||
case Opcode::DsMaxU32:
|
||||
case Opcode::DsMaxRtnU32:
|
||||
case Opcode::DsAndB32:
|
||||
case Opcode::DsAndRtnB32:
|
||||
case Opcode::DsOrB32:
|
||||
case Opcode::DsOrRtnB32:
|
||||
case Opcode::DsXorB32:
|
||||
case Opcode::DsXorRtnB32:
|
||||
case Opcode::DsWrxchgRtnB32: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t DsSourceCount(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::DsWrite2B32:
|
||||
case Opcode::DsWrite2St64B32:
|
||||
case Opcode::DsWrite2B64:
|
||||
case Opcode::DsWrite2St64B64:
|
||||
case Opcode::DsMinF32:
|
||||
case Opcode::DsMaxF32: return 3u;
|
||||
case Opcode::DsReadAddtidB32:
|
||||
case Opcode::DsConsume:
|
||||
case Opcode::DsAppend: return 0u;
|
||||
default: return IsDsWriteOpcode(opcode) || IsDsAtomicOpcode(opcode) ? 2u : 1u;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsFlatStoreOpcode(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::FlatStoreByte:
|
||||
case Opcode::FlatStoreShort:
|
||||
case Opcode::FlatStoreDword:
|
||||
case Opcode::FlatStoreDwordx2:
|
||||
case Opcode::FlatStoreDwordx3:
|
||||
case Opcode::FlatStoreDwordx4: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool DecodeSmem(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error) {
|
||||
if (word_index + 1u >= code.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("truncated SMEM instruction at pc 0x{:08x}", pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t word0 = code[word_index];
|
||||
const uint32_t word1 = code[word_index + 1u];
|
||||
const uint32_t opcode = (word0 >> 18u) & 0xffu;
|
||||
const uint32_t sdst = (word0 >> 6u) & 0x7fu;
|
||||
const uint32_t sbase = word0 & 0x3fu;
|
||||
const uint32_t soffset = (word1 >> 25u) & 0x7fu;
|
||||
|
||||
inst->pc = pc;
|
||||
inst->word = word0;
|
||||
inst->word_count = 2;
|
||||
inst->offset = SignExtendU32(word1 & 0x1fffffu, 21u);
|
||||
inst->glc = ((word0 >> 16u) & 1u) != 0;
|
||||
inst->family = Family::SMEM;
|
||||
inst->opcode_id = opcode;
|
||||
const auto* info =
|
||||
LookupMemoryOpcode(SMEM_OPS, static_cast<uint32_t>(std::size(SMEM_OPS)), opcode);
|
||||
ApplyMemoryInfo(inst, info);
|
||||
SetRawWords(inst, code, word_index, 2);
|
||||
if (inst->opcode == Opcode::Unsupported) {
|
||||
MarkMemoryUnsupported(inst, Family::SMEM, opcode, "SMEM opcode is not implemented");
|
||||
}
|
||||
|
||||
DecodeScalarDestination(sdst, pc, &inst->dst, nullptr);
|
||||
// SMEM encodes SBASE in SGPR pairs. Scalar-buffer loads still use the same
|
||||
// pair index; their descriptor operand consumes four SGPRs from that base.
|
||||
DecodeScalarSource(sbase * 2u, pc, &inst->src0, nullptr);
|
||||
DecodeScalarSource(soffset, pc, &inst->src1, nullptr);
|
||||
inst->src_count = 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction* inst, std::string* error) {
|
||||
if (word_index + 1u >= code.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("truncated MUBUF instruction at pc 0x{:08x}", pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t word0 = code[word_index];
|
||||
const uint32_t word1 = code[word_index + 1u];
|
||||
const uint32_t opcode = ((word0 >> 18u) & 0x7fu) | (((word0 >> 25u) & 1u) << 7u);
|
||||
const uint32_t vdata = (word1 >> 8u) & 0xffu;
|
||||
const uint32_t vaddr = word1 & 0xffu;
|
||||
const uint32_t srsrc = (word1 >> 16u) & 0x1fu;
|
||||
const uint32_t soffset = (word1 >> 24u) & 0xffu;
|
||||
|
||||
inst->pc = pc;
|
||||
inst->word = word0;
|
||||
inst->word_count = 2;
|
||||
inst->offset = word0 & 0xfffu;
|
||||
inst->idxen = ((word0 >> 13u) & 1u) != 0;
|
||||
inst->offen = ((word0 >> 12u) & 1u) != 0;
|
||||
inst->glc = ((word0 >> 14u) & 1u) != 0;
|
||||
inst->slc = ((word1 >> 22u) & 1u) != 0;
|
||||
inst->family = Family::MUBUF;
|
||||
inst->opcode_id = opcode;
|
||||
const auto* info =
|
||||
LookupMemoryOpcode(MUBUF_OPS, static_cast<uint32_t>(std::size(MUBUF_OPS)), opcode);
|
||||
ApplyMemoryInfo(inst, info);
|
||||
SetRawWords(inst, code, word_index, 2);
|
||||
if (inst->opcode == Opcode::Unsupported) {
|
||||
MarkMemoryUnsupported(inst, Family::MUBUF, opcode, "MUBUF opcode is not implemented");
|
||||
}
|
||||
|
||||
DecodeVectorGpr(vdata, &inst->dst, nullptr);
|
||||
DecodeVectorGpr(vaddr, &inst->src0, nullptr);
|
||||
DecodeScalarSource(srsrc * 4u, pc, &inst->src1, nullptr);
|
||||
DecodeScalarSource(soffset, pc, &inst->src2, nullptr);
|
||||
inst->src_count = 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodeMtbuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction* inst, std::string* error) {
|
||||
if (word_index + 1u >= code.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("truncated MTBUF instruction at pc 0x{:08x}", pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t word0 = code[word_index];
|
||||
const uint32_t word1 = code[word_index + 1u];
|
||||
const uint32_t opcode = ((word0 >> 16u) & 0x7u) | (((word1 >> 21u) & 1u) << 3u);
|
||||
const uint32_t dfmt = (word0 >> 19u) & 0xfu;
|
||||
const uint32_t nfmt = (word0 >> 23u) & 0x7u;
|
||||
const uint32_t vdata = (word1 >> 8u) & 0xffu;
|
||||
const uint32_t vaddr = word1 & 0xffu;
|
||||
const uint32_t srsrc = (word1 >> 16u) & 0x1fu;
|
||||
const uint32_t soffset = (word1 >> 24u) & 0xffu;
|
||||
|
||||
inst->pc = pc;
|
||||
inst->word = word0;
|
||||
inst->word_count = 2;
|
||||
inst->offset = word0 & 0xfffu;
|
||||
inst->idxen = ((word0 >> 13u) & 1u) != 0;
|
||||
inst->offen = ((word0 >> 12u) & 1u) != 0;
|
||||
inst->glc = ((word0 >> 14u) & 1u) != 0;
|
||||
inst->slc = ((word1 >> 22u) & 1u) != 0;
|
||||
inst->family = Family::MTBUF;
|
||||
inst->opcode_id = opcode;
|
||||
inst->data_format = dfmt;
|
||||
inst->number_format = nfmt;
|
||||
const auto* info =
|
||||
LookupMemoryOpcode(MTBUF_OPS, static_cast<uint32_t>(std::size(MTBUF_OPS)), opcode);
|
||||
ApplyMemoryInfo(inst, info);
|
||||
SetRawWords(inst, code, word_index, 2);
|
||||
if (inst->opcode == Opcode::Unsupported) {
|
||||
MarkMemoryUnsupported(inst, Family::MTBUF, opcode, "MTBUF opcode is not implemented");
|
||||
}
|
||||
|
||||
DecodeVectorGpr(vdata, &inst->dst, nullptr);
|
||||
DecodeVectorGpr(vaddr, &inst->src0, nullptr);
|
||||
DecodeScalarSource(srsrc * 4u, pc, &inst->src1, nullptr);
|
||||
DecodeScalarSource(soffset, pc, &inst->src2, nullptr);
|
||||
inst->src_count = 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodeFlat(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error) {
|
||||
if (word_index + 1u >= code.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("truncated FLAT instruction at pc 0x{:08x}", pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t word0 = code[word_index];
|
||||
const uint32_t word1 = code[word_index + 1u];
|
||||
const uint32_t offset = word0 & 0xfffu;
|
||||
const uint32_t dlc = (word0 >> 12u) & 1u;
|
||||
const uint32_t lds = (word0 >> 13u) & 1u;
|
||||
const uint32_t seg = (word0 >> 14u) & 0x3u;
|
||||
const uint32_t opcode = (word0 >> 18u) & 0x7fu;
|
||||
const uint32_t vdst = (word1 >> 24u) & 0xffu;
|
||||
const uint32_t saddr = (word1 >> 16u) & 0x7fu;
|
||||
const uint32_t data = (word1 >> 8u) & 0xffu;
|
||||
const uint32_t addr = word1 & 0xffu;
|
||||
|
||||
inst->pc = pc;
|
||||
inst->word = word0;
|
||||
inst->word_count = 2;
|
||||
inst->offset = seg == 0u ? (offset & 0x7ffu) : SignExtendU32(offset, 12u);
|
||||
inst->glc = ((word0 >> 16u) & 1u) != 0;
|
||||
inst->slc = ((word0 >> 17u) & 1u) != 0;
|
||||
inst->family = Family::FLAT;
|
||||
inst->opcode_id = opcode;
|
||||
inst->memory_segment = seg;
|
||||
const auto* info =
|
||||
LookupMemoryOpcode(FLAT_OPS, static_cast<uint32_t>(std::size(FLAT_OPS)), opcode);
|
||||
ApplyMemoryInfo(inst, info);
|
||||
SetRawWords(inst, code, word_index, 2);
|
||||
|
||||
if (dlc != 0 || lds != 0 || inst->glc || inst->slc || seg == 3u) {
|
||||
SetUnsupported(inst, Family::FLAT, opcode, "FLAT modifiers or segment are not implemented");
|
||||
return true;
|
||||
}
|
||||
if (inst->opcode == Opcode::Unsupported) {
|
||||
MarkMemoryUnsupported(inst, Family::FLAT, opcode, "FLAT opcode is not implemented");
|
||||
return true;
|
||||
}
|
||||
|
||||
DecodeVectorGpr(IsFlatStoreOpcode(inst->opcode) ? data : vdst, &inst->dst, nullptr);
|
||||
DecodeVectorGpr(addr, &inst->src0, nullptr);
|
||||
inst->src_count = 1;
|
||||
if (seg == 0u || saddr == 0x7du || saddr == 0x7fu) {
|
||||
DecodeVectorGpr(addr + 1u, &inst->src1, nullptr);
|
||||
inst->src_count = 2;
|
||||
} else {
|
||||
DecodeScalarSource(saddr, pc, &inst->src1, nullptr);
|
||||
inst->src_count = 2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error) {
|
||||
if (word_index + 1u >= code.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("truncated DS instruction at pc 0x{:08x}", pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t word0 = code[word_index];
|
||||
const uint32_t word1 = code[word_index + 1u];
|
||||
const uint32_t opcode = (word0 >> 18u) & 0xffu;
|
||||
const uint32_t offset0 = word0 & 0xffu;
|
||||
const uint32_t offset1 = (word0 >> 8u) & 0xffu;
|
||||
const uint32_t vdst = (word1 >> 24u) & 0xffu;
|
||||
const uint32_t data1 = (word1 >> 16u) & 0xffu;
|
||||
const uint32_t data0 = (word1 >> 8u) & 0xffu;
|
||||
const uint32_t addr = word1 & 0xffu;
|
||||
|
||||
inst->pc = pc;
|
||||
inst->word = word0;
|
||||
inst->word_count = 2;
|
||||
inst->offset = offset0 | (offset1 << 8u);
|
||||
inst->gds = ((word0 >> 17u) & 1u) != 0u;
|
||||
inst->family = Family::DS;
|
||||
inst->opcode_id = opcode;
|
||||
const auto* info = LookupMemoryOpcode(DS_OPS, static_cast<uint32_t>(std::size(DS_OPS)), opcode);
|
||||
ApplyMemoryInfo(inst, info);
|
||||
SetRawWords(inst, code, word_index, 2);
|
||||
if (inst->opcode == Opcode::Unsupported) {
|
||||
MarkMemoryUnsupported(inst, Family::DS, opcode, "DS opcode is not implemented");
|
||||
}
|
||||
if (inst->opcode == Opcode::DsSwizzleB32 && inst->offset >= 0xe000u) {
|
||||
SetUnsupported(inst, Family::DS, opcode, "DS swizzle FFT mode is not implemented");
|
||||
}
|
||||
if (inst->gds &&
|
||||
(inst->opcode == Opcode::DsSwizzleB32 || inst->opcode == Opcode::DsWriteAddtidB32 ||
|
||||
inst->opcode == Opcode::DsReadAddtidB32)) {
|
||||
SetUnsupported(inst, Family::DS, opcode, "DS swizzle/addtid is available only for LDS");
|
||||
}
|
||||
if (inst->gds && (inst->opcode == Opcode::DsAppend || inst->opcode == Opcode::DsConsume) &&
|
||||
inst->offset != 0u) {
|
||||
SetUnsupported(inst, Family::DS, opcode,
|
||||
"GDS append/consume requires a zero instruction offset");
|
||||
}
|
||||
if (inst->opcode == Opcode::DsWriteAddtidB32 && data1 != 0u) {
|
||||
SetUnsupported(inst, Family::DS, opcode,
|
||||
"DS write addtid data1 operand is not implemented");
|
||||
}
|
||||
if (inst->opcode == Opcode::DsReadAddtidB32 && (data0 != 0u || data1 != 0u)) {
|
||||
SetUnsupported(inst, Family::DS, opcode,
|
||||
"DS read addtid data operands are not implemented");
|
||||
}
|
||||
if (inst->opcode == Opcode::DsWrite2B32 || opcode == 0x37u) {
|
||||
inst->offset = offset0 * 4u;
|
||||
inst->secondary_offset = offset1 * 4u;
|
||||
} else if (inst->opcode == Opcode::DsWrite2St64B32 || opcode == 0x38u) {
|
||||
inst->offset = offset0 * 256u;
|
||||
inst->secondary_offset = offset1 * 256u;
|
||||
} else if (inst->opcode == Opcode::DsWrite2B64 || inst->opcode == Opcode::DsRead2B64) {
|
||||
inst->offset = offset0 * 8u;
|
||||
inst->secondary_offset = offset1 * 8u;
|
||||
} else if (inst->opcode == Opcode::DsWrite2St64B64 || inst->opcode == Opcode::DsRead2St64B64) {
|
||||
inst->offset = offset0 * 512u;
|
||||
inst->secondary_offset = offset1 * 512u;
|
||||
}
|
||||
|
||||
DecodeVectorGpr(vdst, &inst->dst, nullptr);
|
||||
DecodeVectorGpr(addr, &inst->src0, nullptr);
|
||||
DecodeVectorGpr(data0, &inst->src1, nullptr);
|
||||
DecodeVectorGpr(data1, &inst->src2, nullptr);
|
||||
inst->src_count = DsSourceCount(inst->opcode);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_MEMORYOPS_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_MEMORYOPS_H_
|
||||
|
||||
#include "graphics/shader/recompiler/ShaderDecoder.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
|
||||
bool DecodeSmem(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error);
|
||||
bool DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction* inst, std::string* error);
|
||||
bool DecodeMtbuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction* inst, std::string* error);
|
||||
bool DecodeFlat(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error);
|
||||
bool DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_MEMORYOPS_H_ */
|
||||
@@ -0,0 +1,354 @@
|
||||
#include "graphics/shader/recompiler/ResourceMaterialization.h"
|
||||
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
namespace {
|
||||
|
||||
constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
|
||||
|
||||
Decoder::ImageDimension DescriptorDimension(const DescriptorValue& descriptor) {
|
||||
switch (static_cast<Prospero::ImageType>((descriptor.dwords[3] >> 28u) & 0xfu)) {
|
||||
case Prospero::ImageType::kColor3D: return Decoder::ImageDimension::Dim3D;
|
||||
case Prospero::ImageType::kCube:
|
||||
case Prospero::ImageType::kColor1DArray:
|
||||
case Prospero::ImageType::kColor2DArray:
|
||||
case Prospero::ImageType::kColor2DMsaaArray: return Decoder::ImageDimension::Dim2DArray;
|
||||
case Prospero::ImageType::kColor1D:
|
||||
case Prospero::ImageType::kColor2D:
|
||||
case Prospero::ImageType::kColor2DMsaa: return Decoder::ImageDimension::Dim2D;
|
||||
default: return Decoder::ImageDimension::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
bool NullImageDescriptor(const DescriptorValue& descriptor) {
|
||||
return descriptor.dwords[0] == 0 && (descriptor.dwords[1] & 0xffu) == 0;
|
||||
}
|
||||
|
||||
uint32_t DescriptorImageSwizzle(const DescriptorValue& descriptor) {
|
||||
return descriptor.dwords[3] & 0xfffu;
|
||||
}
|
||||
|
||||
uint32_t DescriptorPackedStride(const DescriptorValue& descriptor) {
|
||||
const auto stride = (descriptor.dwords[1] >> 16u) & 0x3fffu;
|
||||
const auto swizzle = (descriptor.dwords[1] >> 31u) & 1u;
|
||||
const auto index_stride = (descriptor.dwords[3] >> 21u) & 3u;
|
||||
const auto add_tid = (descriptor.dwords[3] >> 23u) & 1u;
|
||||
return stride | (swizzle << 14u) | (index_stride << 16u) | (add_tid << 20u);
|
||||
}
|
||||
|
||||
uint32_t DescriptorBufferFormat(const DescriptorValue& descriptor) {
|
||||
return (descriptor.dwords[3] >> 12u) & 0x7fu;
|
||||
}
|
||||
|
||||
uint64_t AddressSpecialization(const AddressResource& resource,
|
||||
const ResourceSnapshot::Address& snapshot) {
|
||||
return resource.kind == ResourceKind::Flat || resource.source == ScalarProvenance::Unknown
|
||||
? snapshot.binding_base
|
||||
: snapshot.guest_base - snapshot.binding_base;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& snapshot,
|
||||
std::string* error) {
|
||||
if (!program.resource_tracking_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = "shader resources were not tracked";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (snapshot.buffers.size() != program.info.buffers.size() ||
|
||||
snapshot.images.size() != program.info.images.size() ||
|
||||
snapshot.samplers.size() != program.info.samplers.size() ||
|
||||
snapshot.addresses.size() != program.info.addresses.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = "resource snapshot does not match dense shader topology";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (snapshot.flattened_srt.size() != program.srt.reads.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = "flattened SRT snapshot does not match the shader plan";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (program.binding_layout_complete) {
|
||||
for (const auto reg: program.bindings.user_data_registers) {
|
||||
if (reg < program.user_data_base ||
|
||||
reg - program.user_data_base >= snapshot.user_data.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("runtime snapshot is missing user SGPR {}", reg);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto CheckWidth = [&](const auto& values, uint32_t width, const char* kind) {
|
||||
for (uint32_t i = 0; i < values.size(); i++) {
|
||||
if (values[i].dword_count != width) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("{} descriptor {} has {} dwords", kind, i,
|
||||
values[i].dword_count);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
for (uint32_t i = 0; i < snapshot.addresses.size(); i++) {
|
||||
if (snapshot.addresses[i].binding_base > snapshot.addresses[i].guest_base) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("address resource {} binds above its guest base", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return CheckWidth(snapshot.buffers, 4, "buffer") && CheckWidth(snapshot.images, 8, "image") &&
|
||||
CheckWidth(snapshot.samplers, 4, "sampler");
|
||||
}
|
||||
|
||||
bool ValidateResourceSpecialization(const Program& program, const ResourceSnapshot& snapshot,
|
||||
std::string* error) {
|
||||
if (!ValidateResourceSnapshot(program, snapshot, error)) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
const auto& buffer = program.info.buffers[i];
|
||||
const auto& descriptor = snapshot.buffers[i];
|
||||
if (buffer.packed_stride != DescriptorPackedStride(descriptor) ||
|
||||
buffer.descriptor_format != DescriptorBufferFormat(descriptor)) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("buffer descriptor {} no longer matches specialization", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
const auto& image = program.info.images[i];
|
||||
const auto& descriptor = snapshot.images[i];
|
||||
if (NullImageDescriptor(descriptor)) {
|
||||
continue;
|
||||
}
|
||||
const auto dimension = DescriptorDimension(descriptor);
|
||||
if (dimension == Decoder::ImageDimension::Unknown || dimension != image.dimension) {
|
||||
if (error != nullptr) {
|
||||
*error =
|
||||
fmt::format("image descriptor {} no longer matches specialized dimension", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (image.kind == ResourceKind::Image || image.kind == ResourceKind::ImageUint ||
|
||||
image.kind == ResourceKind::StorageImage ||
|
||||
image.kind == ResourceKind::StorageImageUint) {
|
||||
const bool storage = image.kind == ResourceKind::StorageImage ||
|
||||
image.kind == ResourceKind::StorageImageUint;
|
||||
if (storage && image.storage_swizzle != DescriptorImageSwizzle(descriptor)) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("storage image descriptor {} changed swizzle", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const auto uint_descriptor =
|
||||
Prospero::IsUintTextureFormat((descriptor.dwords[1] >> 20u) & 0x1ffu);
|
||||
const auto uint_program = image.kind == ResourceKind::ImageUint ||
|
||||
image.kind == ResourceKind::StorageImageUint;
|
||||
if (uint_descriptor != uint_program && !(image.atomic && uint_program)) {
|
||||
if (error != nullptr) {
|
||||
*error =
|
||||
fmt::format("image descriptor {} no longer matches specialized format", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (image.depth_compare && uint_program) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("integer image descriptor {} uses depth comparison", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
|
||||
if (program.info.addresses[i].specialized_base !=
|
||||
AddressSpecialization(program.info.addresses[i], snapshot.addresses[i])) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("address resource {} no longer matches specialization", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
ResourceSnapshot* snapshot, std::string* error) {
|
||||
if (snapshot == nullptr || !program.resource_tracking_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = snapshot == nullptr ? "invalid resource snapshot"
|
||||
: "shader resources were not tracked";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<DescriptorSourceRequest> requests;
|
||||
requests.reserve(program.info.buffers.size() + program.info.images.size() +
|
||||
program.info.samplers.size() + program.info.addresses.size());
|
||||
for (const auto& buffer: program.info.buffers) {
|
||||
requests.push_back({buffer.source, buffer.first_use_pc});
|
||||
}
|
||||
for (const auto& image: program.info.images) {
|
||||
requests.push_back({image.source, image.first_use_pc});
|
||||
}
|
||||
for (const auto& sampler: program.info.samplers) {
|
||||
requests.push_back({sampler.source, sampler.first_use_pc});
|
||||
}
|
||||
for (const auto& address: program.info.addresses) {
|
||||
if (address.source != ScalarProvenance::Unknown) {
|
||||
requests.push_back({address.source, address.first_use_pc});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<DescriptorValue> values;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
if (!EvaluateRuntimeSources(program, requests, runtime, &values, &flattened_srt, error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResourceSnapshot next;
|
||||
auto cursor = values.begin();
|
||||
next.buffers.assign(cursor, cursor + program.info.buffers.size());
|
||||
cursor += program.info.buffers.size();
|
||||
next.images.assign(cursor, cursor + program.info.images.size());
|
||||
cursor += program.info.images.size();
|
||||
next.samplers.assign(cursor, cursor + program.info.samplers.size());
|
||||
cursor += program.info.samplers.size();
|
||||
for (const auto& address: program.info.addresses) {
|
||||
if (address.source != ScalarProvenance::Unknown) {
|
||||
const auto value = *cursor++;
|
||||
auto base = (static_cast<uint64_t>(value.dwords[0]) |
|
||||
static_cast<uint64_t>(value.dwords[1]) << 32u) &
|
||||
AddressMask;
|
||||
if (address.kind == ResourceKind::ScalarBuffer) {
|
||||
base &= ~uint64_t {3};
|
||||
}
|
||||
const auto before = static_cast<uint64_t>(-static_cast<int64_t>(address.min_offset));
|
||||
const auto binding_base = address.kind == ResourceKind::Flat
|
||||
? base & ~(FlatAddressWindowSize - 1u)
|
||||
: base >= before ? base - before
|
||||
: 0;
|
||||
next.addresses.push_back({base, binding_base});
|
||||
} else {
|
||||
if (!runtime.flat_memory_base.has_value()) {
|
||||
if (error != nullptr) {
|
||||
*error =
|
||||
fmt::format("unbased {} address at pc 0x{:08x} requires runtime "
|
||||
"guest-address translation",
|
||||
address.kind == ResourceKind::Flat ? "FLAT" : "global/scratch",
|
||||
address.first_use_pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
next.addresses.push_back({*runtime.flat_memory_base, *runtime.flat_memory_base});
|
||||
}
|
||||
}
|
||||
next.flattened_srt = std::move(flattened_srt);
|
||||
next.user_data.assign(runtime.user_data.begin(), runtime.user_data.end());
|
||||
if (!ValidateResourceSnapshot(program, next, error)) {
|
||||
return false;
|
||||
}
|
||||
*snapshot = std::move(next);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SpecializeResources(Program* program, const ResourceSnapshot& snapshot, std::string* error) {
|
||||
if (program == nullptr || !program->resource_tracking_complete ||
|
||||
program->shader_info_complete || program->binding_layout_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = program == nullptr ? "invalid resource specialization program"
|
||||
: !program->resource_tracking_complete ? "shader resources were not tracked"
|
||||
: "resource specialization is too late";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!ValidateResourceSnapshot(*program, snapshot, error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto next = program->info;
|
||||
for (uint32_t i = 0; i < next.buffers.size(); i++) {
|
||||
next.buffers[i].packed_stride = DescriptorPackedStride(snapshot.buffers[i]);
|
||||
next.buffers[i].descriptor_format = DescriptorBufferFormat(snapshot.buffers[i]);
|
||||
}
|
||||
for (uint32_t i = 0; i < next.addresses.size(); i++) {
|
||||
next.addresses[i].specialized_base =
|
||||
AddressSpecialization(next.addresses[i], snapshot.addresses[i]);
|
||||
}
|
||||
for (uint32_t i = 0; i < next.images.size(); i++) {
|
||||
const auto& descriptor = snapshot.images[i];
|
||||
auto& image = next.images[i];
|
||||
if (NullImageDescriptor(descriptor)) {
|
||||
continue;
|
||||
}
|
||||
const auto descriptor_dimension = DescriptorDimension(descriptor);
|
||||
if (descriptor_dimension == Decoder::ImageDimension::Unknown) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format(
|
||||
"image descriptor {} has unsupported type {}: {:08x},{:08x},{:08x},{:08x},"
|
||||
"{:08x},{:08x},{:08x},{:08x}",
|
||||
i, (descriptor.dwords[3] >> 28u) & 0xfu, descriptor.dwords[0],
|
||||
descriptor.dwords[1], descriptor.dwords[2], descriptor.dwords[3],
|
||||
descriptor.dwords[4], descriptor.dwords[5], descriptor.dwords[6],
|
||||
descriptor.dwords[7]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
image.dimension = descriptor_dimension;
|
||||
if (image.kind == ResourceKind::StorageImage ||
|
||||
image.kind == ResourceKind::StorageImageUint) {
|
||||
image.storage_swizzle = DescriptorImageSwizzle(descriptor);
|
||||
}
|
||||
if (Prospero::IsUintTextureFormat((descriptor.dwords[1] >> 20u) & 0x1ffu)) {
|
||||
switch (image.kind) {
|
||||
case ResourceKind::Image: image.kind = ResourceKind::ImageUint; break;
|
||||
case ResourceKind::StorageImage: image.kind = ResourceKind::StorageImageUint; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
struct ImagePatch {
|
||||
Instruction* inst;
|
||||
ResourceKind kind;
|
||||
Decoder::ImageDimension dimension;
|
||||
};
|
||||
std::vector<ImagePatch> patches;
|
||||
for (auto& block: program->blocks) {
|
||||
for (auto& inst: block.instructions) {
|
||||
if (inst.memory.kind != ResourceKind::Image &&
|
||||
inst.memory.kind != ResourceKind::ImageUint &&
|
||||
inst.memory.kind != ResourceKind::StorageImage &&
|
||||
inst.memory.kind != ResourceKind::StorageImageUint) {
|
||||
continue;
|
||||
}
|
||||
if (inst.memory.resource >= next.images.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("image instruction at pc 0x{:08x} has invalid resource {}",
|
||||
inst.pc, inst.memory.resource);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const auto& image = next.images[inst.memory.resource];
|
||||
patches.push_back({&inst, image.kind, image.dimension});
|
||||
}
|
||||
}
|
||||
program->info = std::move(next);
|
||||
for (const auto& patch: patches) {
|
||||
patch.inst->memory.kind = patch.kind;
|
||||
patch.inst->memory.image_dimension = patch.dimension;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_RESOURCEMATERIALIZATION_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_RESOURCEMATERIALIZATION_H_
|
||||
|
||||
#include "graphics/shader/recompiler/SrtWalker.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
constexpr uint64_t FlatAddressWindowSize = 0x10000;
|
||||
|
||||
struct ResourceSnapshot {
|
||||
struct Address {
|
||||
uint64_t guest_base = 0;
|
||||
uint64_t binding_base = 0;
|
||||
|
||||
bool operator==(const Address& other) const = default;
|
||||
};
|
||||
|
||||
std::vector<DescriptorValue> buffers;
|
||||
std::vector<DescriptorValue> images;
|
||||
std::vector<DescriptorValue> samplers;
|
||||
std::vector<Address> addresses;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
std::vector<uint32_t> user_data;
|
||||
};
|
||||
|
||||
bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& snapshot,
|
||||
std::string* error);
|
||||
bool ValidateResourceSpecialization(const Program& program, const ResourceSnapshot& snapshot,
|
||||
std::string* error);
|
||||
|
||||
// Resolves the immutable dense resource topology against one runtime user-data/SRT snapshot.
|
||||
// On failure the destination is unchanged.
|
||||
bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
ResourceSnapshot* snapshot, std::string* error);
|
||||
|
||||
// Applies runtime descriptor shape/format facts to a copied dense topology before layout and
|
||||
// emission. On failure the program is unchanged.
|
||||
bool SpecializeResources(Program* program, const ResourceSnapshot& snapshot, std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_RESOURCEMATERIALIZATION_H_ */
|
||||
@@ -0,0 +1,449 @@
|
||||
#include "graphics/shader/recompiler/ResourceTracking.h"
|
||||
|
||||
#include "graphics/shader/recompiler/ScalarProvenance.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
namespace {
|
||||
|
||||
const char* StageName(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Vertex: return "vertex";
|
||||
case ShaderType::Pixel: return "pixel";
|
||||
case ShaderType::Fetch: return "fetch";
|
||||
case ShaderType::Compute: return "compute";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
bool IsAtomic(Opcode op) {
|
||||
switch (op) {
|
||||
case Opcode::AtomicSwapU32:
|
||||
case Opcode::AtomicAddU32:
|
||||
case Opcode::AtomicSubU32:
|
||||
case Opcode::AtomicSMinI32:
|
||||
case Opcode::AtomicUMinU32:
|
||||
case Opcode::AtomicSMaxI32:
|
||||
case Opcode::AtomicUMaxU32:
|
||||
case Opcode::AtomicAndU32:
|
||||
case Opcode::AtomicOrU32:
|
||||
case Opcode::AtomicXorU32: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsWrite(Opcode op) {
|
||||
switch (op) {
|
||||
case Opcode::BufferStoreByte:
|
||||
case Opcode::BufferStoreShort:
|
||||
case Opcode::BufferStoreDword:
|
||||
case Opcode::ImageStore: return true;
|
||||
default: return IsAtomic(op);
|
||||
}
|
||||
}
|
||||
|
||||
bool NeedsSampler(Opcode op) {
|
||||
return op == Opcode::ImageSample || op == Opcode::ImageGather4 || op == Opcode::ImageGetLod;
|
||||
}
|
||||
|
||||
bool IsDepthCompare(const Instruction& inst) {
|
||||
return (inst.memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0;
|
||||
}
|
||||
|
||||
ImageMipMode MipMode(const Instruction& inst) {
|
||||
const bool storage = inst.memory.kind == ResourceKind::StorageImage ||
|
||||
inst.memory.kind == ResourceKind::StorageImageUint;
|
||||
return storage && inst.memory.image_has_mip ? ImageMipMode::DynamicStorage : ImageMipMode::None;
|
||||
}
|
||||
|
||||
bool IsBuffer(const Instruction& inst) {
|
||||
return inst.memory.kind == ResourceKind::Buffer ||
|
||||
(inst.memory.kind == ResourceKind::ScalarBuffer && inst.op == Opcode::SBufferLoadDword);
|
||||
}
|
||||
|
||||
bool IsImage(const Instruction& inst) {
|
||||
return inst.memory.kind == ResourceKind::Image || inst.memory.kind == ResourceKind::ImageUint ||
|
||||
inst.memory.kind == ResourceKind::StorageImage ||
|
||||
inst.memory.kind == ResourceKind::StorageImageUint;
|
||||
}
|
||||
|
||||
bool IsAddress(const Instruction& inst) {
|
||||
return inst.op == Opcode::SLoadDword || inst.memory.kind == ResourceKind::Flat ||
|
||||
inst.memory.kind == ResourceKind::Global || inst.memory.kind == ResourceKind::Scratch;
|
||||
}
|
||||
|
||||
uint32_t ByteExtent(const Instruction& inst) {
|
||||
const auto bytes = std::max((inst.memory.data_bits + 7u) / 8u, 1u);
|
||||
const auto count = std::max(inst.memory.data_dwords, 1u);
|
||||
const auto end =
|
||||
static_cast<uint64_t>(inst.memory.offset) + static_cast<uint64_t>(bytes) * count;
|
||||
return end > UINT32_MAX ? UINT32_MAX : static_cast<uint32_t>(end);
|
||||
}
|
||||
|
||||
bool ContainsUnknown(const ScalarProvenance& provenance, uint32_t id, std::vector<uint8_t>* visited,
|
||||
std::vector<uint32_t>* path) {
|
||||
path->push_back(id);
|
||||
if (id <= ScalarProvenance::Unknown || id >= provenance.values.size()) {
|
||||
return true;
|
||||
}
|
||||
if ((*visited)[id] != 0) {
|
||||
path->pop_back();
|
||||
return false;
|
||||
}
|
||||
(*visited)[id] = 1;
|
||||
const auto& value = provenance.values[id];
|
||||
if (value.op == ScalarValueOp::Phi) {
|
||||
for (const auto arg: value.phi_args) {
|
||||
if (ContainsUnknown(provenance, arg, visited, path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
path->pop_back();
|
||||
return false;
|
||||
}
|
||||
const auto args = ScalarValueArgCount(value.op);
|
||||
for (uint32_t i = 0; i < args; i++) {
|
||||
if (ContainsUnknown(provenance, value.args[i], visited, path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
path->pop_back();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsLoopInvariantValue(const ScalarProvenance& provenance, uint32_t id,
|
||||
std::vector<uint8_t>* visiting) {
|
||||
if (id <= ScalarProvenance::Unknown || id >= provenance.values.size()) {
|
||||
return false;
|
||||
}
|
||||
if ((*visiting)[id] != 0) {
|
||||
return true;
|
||||
}
|
||||
(*visiting)[id] = 1;
|
||||
const auto& value = provenance.values[id];
|
||||
if (value.op == ScalarValueOp::Phi) {
|
||||
uint32_t invariant = ScalarProvenance::Undefined;
|
||||
for (const auto arg: value.phi_args) {
|
||||
if (arg == id) {
|
||||
continue;
|
||||
}
|
||||
if (invariant == ScalarProvenance::Undefined) {
|
||||
invariant = arg;
|
||||
} else if (arg != invariant) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return invariant != ScalarProvenance::Undefined &&
|
||||
IsLoopInvariantValue(provenance, invariant, visiting);
|
||||
}
|
||||
for (uint32_t i = 0; i < ScalarValueArgCount(value.op); i++) {
|
||||
if (!IsLoopInvariantValue(provenance, value.args[i], visiting)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsLoopInvariantDescriptor(const ScalarProvenance& provenance,
|
||||
const DescriptorValue& descriptor) {
|
||||
std::vector<uint8_t> visiting(provenance.values.size());
|
||||
for (uint32_t i = 0; i < descriptor.dword_count; i++) {
|
||||
if (!IsLoopInvariantValue(provenance, descriptor.dwords[i], &visiting)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class Tracker {
|
||||
public:
|
||||
explicit Tracker(Program& program): m_program(program) {}
|
||||
|
||||
bool Run(std::string* error) {
|
||||
if (m_program.resource_tracking_complete) {
|
||||
return Fail(0, error, "resources already tracked");
|
||||
}
|
||||
if (!m_program.srt_plan_complete) {
|
||||
return Fail(0, error, "SRT plan is not ready");
|
||||
}
|
||||
if (!m_program.srt_patching_complete) {
|
||||
return Fail(0, error, "SRT reads were not patched");
|
||||
}
|
||||
for (auto& block: m_program.blocks) {
|
||||
for (auto& inst: block.instructions) {
|
||||
if (!Collect(&inst, error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_program.info = std::move(m_info);
|
||||
for (const auto& patch: m_patches) {
|
||||
patch.inst->memory.resource = patch.resource;
|
||||
patch.inst->memory.sampler = patch.sampler;
|
||||
patch.inst->memory.resource_source = ScalarProvenance::Undefined;
|
||||
patch.inst->memory.sampler_source = ScalarProvenance::Undefined;
|
||||
}
|
||||
m_program.resource_tracking_complete = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Patch {
|
||||
Instruction* inst = nullptr;
|
||||
uint32_t resource = 0;
|
||||
uint32_t sampler = 0;
|
||||
};
|
||||
|
||||
bool Fail(uint32_t pc, std::string* error, const std::string& reason) const {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("shader resource tracking: hash=0x{:016x} stage={} pc=0x{:08x} {}",
|
||||
m_program.shader_hash, StageName(m_program.stage), pc, reason);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ValidateSource(uint32_t source, uint32_t dwords, uint32_t pc, std::string* error) const {
|
||||
const auto* descriptor = GetDescriptorSource(m_program, source);
|
||||
if (descriptor == nullptr || descriptor->dword_count != dwords) {
|
||||
return Fail(pc, error,
|
||||
fmt::format("descriptor source {} is missing or has wrong width", source));
|
||||
}
|
||||
std::vector<uint8_t> visited(m_program.provenance.values.size());
|
||||
for (uint32_t i = 0; i < descriptor->dword_count; i++) {
|
||||
std::vector<uint32_t> path;
|
||||
if (ContainsUnknown(m_program.provenance, descriptor->dwords[i], &visited, &path)) {
|
||||
const auto value = descriptor->dwords[i];
|
||||
std::string chain;
|
||||
for (const auto id: path) {
|
||||
const auto op = id < m_program.provenance.values.size()
|
||||
? static_cast<uint32_t>(m_program.provenance.values[id].op)
|
||||
: UINT32_MAX;
|
||||
chain += fmt::format("{}{}:{}({})", chain.empty() ? "" : " -> ", id, op,
|
||||
ScalarValueToString(m_program.provenance, id));
|
||||
}
|
||||
return Fail(
|
||||
pc, error,
|
||||
fmt::format(
|
||||
"descriptor source {} dword {} contains an unknown value {} ({}) path {}",
|
||||
source, i, value, ScalarValueToString(m_program.provenance, value), chain));
|
||||
}
|
||||
}
|
||||
const auto dynamic =
|
||||
std::find(m_program.srt.dynamic_sources.begin(), m_program.srt.dynamic_sources.end(),
|
||||
source) != m_program.srt.dynamic_sources.end();
|
||||
if (dynamic && !IsLoopInvariantDescriptor(m_program.provenance, *descriptor)) {
|
||||
std::string detail;
|
||||
for (uint32_t i = 0; i < descriptor->dword_count; i++) {
|
||||
const auto id = descriptor->dwords[i];
|
||||
detail +=
|
||||
fmt::format(" d{}={}({})", i, id,
|
||||
id < m_program.provenance.values.size()
|
||||
? static_cast<uint32_t>(m_program.provenance.values[id].op)
|
||||
: UINT32_MAX);
|
||||
if (id < m_program.provenance.values.size()) {
|
||||
for (const auto arg: m_program.provenance.values[id].phi_args) {
|
||||
detail += fmt::format(
|
||||
"/{}({})", arg,
|
||||
arg < m_program.provenance.values.size()
|
||||
? static_cast<uint32_t>(m_program.provenance.values[arg].op)
|
||||
: UINT32_MAX);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Fail(pc, error,
|
||||
fmt::format("descriptor source {} requires unsupported GPU selection{}",
|
||||
source, detail));
|
||||
}
|
||||
if (!DescriptorSourceResolved(m_program, source) && !dynamic) {
|
||||
return Fail(pc, error, fmt::format("descriptor source {} is unresolved", source));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t AddBuffer(const Instruction& inst) {
|
||||
for (uint32_t i = 0; i < m_info.buffers.size(); i++) {
|
||||
if (m_info.buffers[i].source == inst.memory.resource_source) {
|
||||
Merge(&m_info.buffers[i], inst);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
if (m_info.buffers.size() >= ShaderInfo::MaxBuffers) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
BufferResource resource;
|
||||
resource.source = inst.memory.resource_source;
|
||||
resource.first_use_pc = inst.pc;
|
||||
Merge(&resource, inst);
|
||||
m_info.buffers.push_back(resource);
|
||||
return static_cast<uint32_t>(m_info.buffers.size() - 1);
|
||||
}
|
||||
|
||||
void Merge(BufferResource* resource, const Instruction& inst) const {
|
||||
resource->first_use_pc = std::min(resource->first_use_pc, inst.pc);
|
||||
resource->max_byte_extent = std::max(resource->max_byte_extent, ByteExtent(inst));
|
||||
resource->read = resource->read || !IsWrite(inst.op) || IsAtomic(inst.op);
|
||||
resource->written = resource->written || IsWrite(inst.op);
|
||||
resource->atomic = resource->atomic || IsAtomic(inst.op);
|
||||
resource->formatted = resource->formatted || inst.memory.formatted;
|
||||
resource->scalar = resource->scalar || inst.memory.kind == ResourceKind::ScalarBuffer;
|
||||
}
|
||||
|
||||
uint32_t AddImage(const Instruction& inst) {
|
||||
for (uint32_t i = 0; i < m_info.images.size(); i++) {
|
||||
auto& image = m_info.images[i];
|
||||
if (image.source == inst.memory.resource_source && image.kind == inst.memory.kind &&
|
||||
image.dimension == inst.memory.image_dimension && image.mip_mode == MipMode(inst) &&
|
||||
image.depth_compare == IsDepthCompare(inst)) {
|
||||
Merge(&image, inst);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
if (m_info.images.size() >= ShaderInfo::MaxImages) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
ImageResource image;
|
||||
image.source = inst.memory.resource_source;
|
||||
image.first_use_pc = inst.pc;
|
||||
image.kind = inst.memory.kind;
|
||||
image.dimension = inst.memory.image_dimension;
|
||||
image.mip_mode = MipMode(inst);
|
||||
image.depth_compare = IsDepthCompare(inst);
|
||||
Merge(&image, inst);
|
||||
m_info.images.push_back(image);
|
||||
return static_cast<uint32_t>(m_info.images.size() - 1);
|
||||
}
|
||||
|
||||
uint32_t AddAddress(const Instruction& inst) {
|
||||
auto immediate = static_cast<int32_t>(inst.memory.offset);
|
||||
if (inst.memory.kind == ResourceKind::ScalarBuffer) {
|
||||
immediate = static_cast<int32_t>(static_cast<uint32_t>(immediate) & ~3u);
|
||||
}
|
||||
const auto min_offset =
|
||||
inst.memory.resource_source == ScalarProvenance::Unknown ? 0 : std::min(immediate, 0);
|
||||
for (uint32_t i = 0; i < m_info.addresses.size(); i++) {
|
||||
auto& address = m_info.addresses[i];
|
||||
if (address.source == inst.memory.resource_source && address.kind == inst.memory.kind) {
|
||||
address.first_use_pc = std::min(address.first_use_pc, inst.pc);
|
||||
address.min_offset = std::min(address.min_offset, min_offset);
|
||||
address.read = address.read || !IsWrite(inst.op) || IsAtomic(inst.op);
|
||||
address.written = address.written || IsWrite(inst.op);
|
||||
address.atomic = address.atomic || IsAtomic(inst.op);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
if (m_info.addresses.size() >= ShaderInfo::MaxAddresses) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
AddressResource address {inst.memory.resource_source, inst.pc, inst.memory.kind,
|
||||
min_offset};
|
||||
address.read = !IsWrite(inst.op) || IsAtomic(inst.op);
|
||||
address.written = IsWrite(inst.op);
|
||||
address.atomic = IsAtomic(inst.op);
|
||||
m_info.addresses.push_back(address);
|
||||
return static_cast<uint32_t>(m_info.addresses.size() - 1);
|
||||
}
|
||||
|
||||
void Merge(ImageResource* resource, const Instruction& inst) const {
|
||||
resource->first_use_pc = std::min(resource->first_use_pc, inst.pc);
|
||||
resource->read = resource->read || !IsWrite(inst.op) || IsAtomic(inst.op);
|
||||
resource->written = resource->written || IsWrite(inst.op);
|
||||
resource->atomic = resource->atomic || IsAtomic(inst.op);
|
||||
}
|
||||
|
||||
uint32_t AddSampler(const Instruction& inst) {
|
||||
for (uint32_t i = 0; i < m_info.samplers.size(); i++) {
|
||||
if (m_info.samplers[i].source == inst.memory.sampler_source) {
|
||||
m_info.samplers[i].first_use_pc =
|
||||
std::min(m_info.samplers[i].first_use_pc, inst.pc);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
if (m_info.samplers.size() >= ShaderInfo::MaxSamplers) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
m_info.samplers.push_back({inst.memory.sampler_source, inst.pc});
|
||||
return static_cast<uint32_t>(m_info.samplers.size() - 1);
|
||||
}
|
||||
|
||||
bool AddSampledPair(uint32_t image, uint32_t sampler, uint32_t pc, std::string* error) {
|
||||
for (auto& pair: m_info.sampled_pairs) {
|
||||
if (pair.image == image && pair.sampler == sampler) {
|
||||
pair.first_use_pc = std::min(pair.first_use_pc, pc);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (m_info.sampled_pairs.size() >= ShaderInfo::MaxSampledPairs) {
|
||||
return Fail(pc, error, "sampled image/sampler pair limit exceeded");
|
||||
}
|
||||
m_info.sampled_pairs.push_back({image, sampler, pc});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Collect(Instruction* inst, std::string* error) {
|
||||
if (IsAddress(*inst)) {
|
||||
const bool unbased = inst->memory.resource_source == ScalarProvenance::Unknown;
|
||||
if ((!unbased && !ValidateSource(inst->memory.resource_source, 2, inst->pc, error)) ||
|
||||
(unbased && inst->memory.kind != ResourceKind::Flat &&
|
||||
inst->memory.kind != ResourceKind::Global &&
|
||||
inst->memory.kind != ResourceKind::Scratch)) {
|
||||
return unbased ? Fail(inst->pc, error, "scalar memory base is unresolved") : false;
|
||||
}
|
||||
const auto resource = AddAddress(*inst);
|
||||
if (resource == UINT32_MAX) {
|
||||
return Fail(inst->pc, error, "address resource limit exceeded");
|
||||
}
|
||||
m_patches.push_back({inst, resource, 0});
|
||||
return true;
|
||||
}
|
||||
if (!IsBuffer(*inst) && !IsImage(*inst)) {
|
||||
return true;
|
||||
}
|
||||
if (!ValidateSource(inst->memory.resource_source, IsBuffer(*inst) ? 4u : 8u, inst->pc,
|
||||
error)) {
|
||||
return false;
|
||||
}
|
||||
Patch patch;
|
||||
patch.inst = inst;
|
||||
patch.resource = IsBuffer(*inst) ? AddBuffer(*inst) : AddImage(*inst);
|
||||
if (patch.resource == UINT32_MAX) {
|
||||
return Fail(inst->pc, error,
|
||||
IsBuffer(*inst) ? "buffer resource limit exceeded"
|
||||
: "image resource limit exceeded");
|
||||
}
|
||||
if (NeedsSampler(inst->op)) {
|
||||
if (!ValidateSource(inst->memory.sampler_source, 4, inst->pc, error)) {
|
||||
return false;
|
||||
}
|
||||
patch.sampler = AddSampler(*inst);
|
||||
if (patch.sampler == UINT32_MAX) {
|
||||
return Fail(inst->pc, error, "sampler resource limit exceeded");
|
||||
}
|
||||
if (!AddSampledPair(patch.resource, patch.sampler, inst->pc, error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
m_patches.push_back(patch);
|
||||
return true;
|
||||
}
|
||||
|
||||
Program& m_program;
|
||||
ShaderInfo m_info;
|
||||
std::vector<Patch> m_patches;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool TrackResources(Program* program, std::string* error) {
|
||||
if (program == nullptr) {
|
||||
if (error != nullptr) {
|
||||
*error = "shader resource tracking: hash=0x0000000000000000 stage=unknown "
|
||||
"pc=0x00000000 invalid program";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return Tracker(*program).Run(error);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_RESOURCETRACKING_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_RESOURCETRACKING_H_
|
||||
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
// Collects immutable resource topology, then replaces descriptor operands with dense indices.
|
||||
// On failure the program is left unpatched.
|
||||
bool TrackResources(Program* program, std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_RESOURCETRACKING_H_ */
|
||||
@@ -0,0 +1,283 @@
|
||||
#include "graphics/shader/recompiler/ScalarAluOps.h"
|
||||
|
||||
#include <iterator>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
namespace {
|
||||
|
||||
struct OpcodeMap {
|
||||
uint32_t opcode = 0;
|
||||
Opcode ir = Opcode::Unknown;
|
||||
};
|
||||
|
||||
constexpr OpcodeMap SOP2_OPS[] = {
|
||||
{0x00u, Opcode::SAddU32}, {0x01u, Opcode::SSubU32}, {0x02u, Opcode::SAddI32},
|
||||
{0x03u, Opcode::SSubI32}, {0x04u, Opcode::SAddcU32}, {0x05u, Opcode::SSubbU32},
|
||||
{0x06u, Opcode::SMinI32}, {0x07u, Opcode::SMinU32}, {0x08u, Opcode::SMaxI32},
|
||||
{0x09u, Opcode::SMaxU32}, {0x0au, Opcode::SCselectB32}, {0x0bu, Opcode::SCselectB64},
|
||||
{0x0eu, Opcode::SAndB32}, {0x0fu, Opcode::SAndB64}, {0x10u, Opcode::SOrB32},
|
||||
{0x11u, Opcode::SOrB64}, {0x12u, Opcode::SXorB32}, {0x13u, Opcode::SXorB64},
|
||||
{0x14u, Opcode::SAndn2B32}, {0x15u, Opcode::SAndn2B64}, {0x16u, Opcode::SOrn2B32},
|
||||
{0x17u, Opcode::SOrn2B64}, {0x18u, Opcode::SNandB32}, {0x19u, Opcode::SNandB64},
|
||||
{0x1au, Opcode::SNorB32}, {0x1bu, Opcode::SNorB64}, {0x1cu, Opcode::SXnorB32},
|
||||
{0x1du, Opcode::SXnorB64}, {0x1eu, Opcode::SLshlB32}, {0x1fu, Opcode::SLshlB64},
|
||||
{0x20u, Opcode::SLshrB32}, {0x21u, Opcode::SLshrB64}, {0x22u, Opcode::SAshrI32},
|
||||
{0x24u, Opcode::SBfmB32}, {0x25u, Opcode::SBfmB64}, {0x26u, Opcode::SMulI32},
|
||||
{0x27u, Opcode::SBfeU32}, {0x29u, Opcode::SBfeU64}, {0x2eu, Opcode::SLshl1AddU32},
|
||||
{0x2fu, Opcode::SLshl2AddU32}, {0x30u, Opcode::SLshl3AddU32}, {0x31u, Opcode::SLshl4AddU32},
|
||||
{0x32u, Opcode::SPackLlB32B16}, {0x33u, Opcode::SPackLhB32B16}, {0x34u, Opcode::SPackHhB32B16},
|
||||
{0x35u, Opcode::SMulHiU32},
|
||||
};
|
||||
|
||||
constexpr OpcodeMap SOP1_OPS[] = {
|
||||
{0x03u, Opcode::SMovB32},
|
||||
{0x04u, Opcode::SMovB64},
|
||||
{0x07u, Opcode::SNotB32},
|
||||
{0x08u, Opcode::SNotB64},
|
||||
{0x0au, Opcode::SWqmB64},
|
||||
{0x0bu, Opcode::SBrevB32},
|
||||
{0x0fu, Opcode::SBcnt1I32B32},
|
||||
{0x10u, Opcode::SBcnt1I32B64},
|
||||
{0x13u, Opcode::SFf1I32B32},
|
||||
{0x16u, Opcode::SFlbitI32B64},
|
||||
{0x1bu, Opcode::SBitset0B32},
|
||||
{0x1du, Opcode::SBitset1B32},
|
||||
{0x1fu, Opcode::SGetpcB64},
|
||||
{0x20u, Opcode::SSetpcB64},
|
||||
{0x24u, Opcode::SAndSaveexecB64},
|
||||
{0x28u, Opcode::SOrn2SaveexecB64},
|
||||
{0x34u, Opcode::SAbsI32},
|
||||
{0x37u, Opcode::SAndn1SaveexecB64},
|
||||
{0x3bu, Opcode::SBitreplicateB64B32},
|
||||
{0x3cu, Opcode::SAndSaveexecB32},
|
||||
{0x44u, Opcode::SAndn1SaveexecB32},
|
||||
};
|
||||
|
||||
constexpr OpcodeMap SOPC_OPS[] = {
|
||||
{0x00u, Opcode::SCmpEqI32}, {0x01u, Opcode::SCmpLgI32}, {0x02u, Opcode::SCmpGtI32},
|
||||
{0x03u, Opcode::SCmpGeI32}, {0x04u, Opcode::SCmpLtI32}, {0x05u, Opcode::SCmpLeI32},
|
||||
{0x06u, Opcode::SCmpEqU32}, {0x07u, Opcode::SCmpLgU32}, {0x08u, Opcode::SCmpGtU32},
|
||||
{0x09u, Opcode::SCmpGeU32}, {0x0au, Opcode::SCmpLtU32}, {0x0bu, Opcode::SCmpLeU32},
|
||||
{0x0cu, Opcode::SBitcmp0B32}, {0x0du, Opcode::SBitcmp1B32}, {0x13u, Opcode::SCmpLgU64},
|
||||
};
|
||||
|
||||
constexpr OpcodeMap SOPK_OPS[] = {
|
||||
{0x00u, Opcode::SMovkI32}, {0x03u, Opcode::SCmpEqI32}, {0x04u, Opcode::SCmpLgI32},
|
||||
{0x05u, Opcode::SCmpGtI32}, {0x06u, Opcode::SCmpGeI32}, {0x07u, Opcode::SCmpLtI32},
|
||||
{0x08u, Opcode::SCmpLeI32}, {0x09u, Opcode::SCmpEqU32}, {0x0au, Opcode::SCmpLgU32},
|
||||
{0x0bu, Opcode::SCmpGtU32}, {0x0cu, Opcode::SCmpGeU32}, {0x0du, Opcode::SCmpLtU32},
|
||||
{0x0eu, Opcode::SCmpLeU32}, {0x0fu, Opcode::SAddI32}, {0x10u, Opcode::SMulkI32},
|
||||
{0x13u, Opcode::SSetregB32}, {0x17u, Opcode::SWaitcnt}, {0x18u, Opcode::SWaitcnt},
|
||||
{0x19u, Opcode::SWaitcnt}, {0x1au, Opcode::SWaitcnt},
|
||||
};
|
||||
|
||||
constexpr OpcodeMap SOPP_OPS[] = {
|
||||
{0x00u, Opcode::SNop}, {0x01u, Opcode::SEndpgm}, {0x02u, Opcode::SBranch},
|
||||
{0x04u, Opcode::SCbranchScc0}, {0x05u, Opcode::SCbranchScc1}, {0x06u, Opcode::SCbranchVccz},
|
||||
{0x07u, Opcode::SCbranchVccnz}, {0x08u, Opcode::SCbranchExecz}, {0x09u, Opcode::SCbranchExecnz},
|
||||
{0x0au, Opcode::SBarrier}, {0x0cu, Opcode::SWaitcnt}, {0x0eu, Opcode::SSleep},
|
||||
{0x10u, Opcode::SSendmsg}, {0x16u, Opcode::STtraceData}, {0x20u, Opcode::SInstPrefetch},
|
||||
};
|
||||
|
||||
Opcode Lookup(const OpcodeMap* ops, uint32_t count, uint32_t opcode) {
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (ops[i].opcode == opcode) {
|
||||
return ops[i].ir;
|
||||
}
|
||||
}
|
||||
return Opcode::Unsupported;
|
||||
}
|
||||
|
||||
bool DecodeBinarySources(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction* inst, uint32_t ssrc0, uint32_t ssrc1, std::string* error) {
|
||||
if (!DecodeScalarSource(ssrc0, pc, &inst->src0, error) ||
|
||||
!DecodeScalarSource(ssrc1, pc, &inst->src1, error)) {
|
||||
return false;
|
||||
}
|
||||
inst->src_count = 2;
|
||||
return ReadLiteralOperands(code, word_index, inst, error);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool DecodeSop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error) {
|
||||
const uint32_t word = code[word_index];
|
||||
const uint32_t opcode = (word >> 8u) & 0xffu;
|
||||
const uint32_t ssrc0 = word & 0xffu;
|
||||
const uint32_t sdst = (word >> 16u) & 0x7fu;
|
||||
|
||||
inst->pc = pc;
|
||||
inst->word = word;
|
||||
inst->family = Family::SOP1;
|
||||
inst->opcode_id = opcode;
|
||||
inst->opcode = Lookup(SOP1_OPS, static_cast<uint32_t>(std::size(SOP1_OPS)), opcode);
|
||||
SetRawWords(inst, code, word_index, 1);
|
||||
|
||||
if (inst->opcode == Opcode::Unsupported) {
|
||||
SetUnsupported(inst, Family::SOP1, opcode, "SOP1 opcode is not implemented");
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (inst->opcode) {
|
||||
case Opcode::SGetpcB64:
|
||||
inst->src_count = 0;
|
||||
return DecodeScalarDestination(sdst, pc, &inst->dst, error);
|
||||
case Opcode::SSetpcB64:
|
||||
inst->src_count = 1;
|
||||
inst->dst.kind = OperandKind::Null;
|
||||
if (!DecodeScalarSource(ssrc0, pc, &inst->src0, error)) {
|
||||
return false;
|
||||
}
|
||||
return ReadLiteralOperands(code, word_index, inst, error);
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (!DecodeScalarSource(ssrc0, pc, &inst->src0, error) ||
|
||||
!DecodeScalarDestination(sdst, pc, &inst->dst, error)) {
|
||||
return false;
|
||||
}
|
||||
inst->src_count = 1;
|
||||
return ReadLiteralOperands(code, word_index, inst, error);
|
||||
}
|
||||
|
||||
bool DecodeSop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error) {
|
||||
const uint32_t word = code[word_index];
|
||||
const uint32_t opcode = (word >> 23u) & 0x7fu;
|
||||
const uint32_t ssrc1 = (word >> 8u) & 0xffu;
|
||||
const uint32_t ssrc0 = word & 0xffu;
|
||||
const uint32_t sdst = (word >> 16u) & 0x7fu;
|
||||
|
||||
inst->pc = pc;
|
||||
inst->word = word;
|
||||
inst->family = Family::SOP2;
|
||||
inst->opcode_id = opcode;
|
||||
inst->opcode = Lookup(SOP2_OPS, static_cast<uint32_t>(std::size(SOP2_OPS)), opcode);
|
||||
SetRawWords(inst, code, word_index, 1);
|
||||
|
||||
if (inst->opcode == Opcode::Unsupported) {
|
||||
SetUnsupported(inst, Family::SOP2, opcode, "SOP2 opcode is not implemented");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!DecodeScalarDestination(sdst, pc, &inst->dst, error)) {
|
||||
return false;
|
||||
}
|
||||
return DecodeBinarySources(pc, code, word_index, inst, ssrc0, ssrc1, error);
|
||||
}
|
||||
|
||||
bool DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error) {
|
||||
const uint32_t word = code[word_index];
|
||||
const uint32_t opcode = (word >> 23u) & 0x1fu;
|
||||
const uint32_t sdst = (word >> 16u) & 0x7fu;
|
||||
const auto imm = static_cast<int16_t>(word & 0xffffu);
|
||||
|
||||
(void)error;
|
||||
|
||||
inst->pc = pc;
|
||||
inst->word = word;
|
||||
inst->family = Family::SOPK;
|
||||
inst->opcode_id = opcode;
|
||||
inst->opcode = Lookup(SOPK_OPS, static_cast<uint32_t>(std::size(SOPK_OPS)), opcode);
|
||||
inst->src0.kind = OperandKind::IntegerInlineConstant;
|
||||
inst->src0.signed_val = imm;
|
||||
inst->src0.value = static_cast<uint32_t>(imm);
|
||||
inst->src_count = 1;
|
||||
SetRawWords(inst, code, word_index, 1);
|
||||
|
||||
if (inst->opcode == Opcode::Unsupported) {
|
||||
SetUnsupported(inst, Family::SOPK, opcode, "SOPK opcode is not implemented");
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (inst->opcode) {
|
||||
case Opcode::SMovkI32: return DecodeScalarDestination(sdst, pc, &inst->dst, error);
|
||||
case Opcode::SWaitcnt: {
|
||||
const uint32_t waitcnt = word & 0xffffu;
|
||||
inst->dst.kind = OperandKind::Null;
|
||||
inst->src0.signed_val = static_cast<int32_t>(waitcnt);
|
||||
inst->src0.value = waitcnt;
|
||||
inst->src_count = 1;
|
||||
return true;
|
||||
}
|
||||
case Opcode::SSetregB32:
|
||||
inst->dst.kind = OperandKind::Null;
|
||||
inst->src1.kind = OperandKind::LiteralConstant;
|
||||
inst->src1.value = word & 0xffffu;
|
||||
inst->src1.signed_val = static_cast<int32_t>(imm);
|
||||
inst->src_count = 2;
|
||||
return DecodeScalarSource(sdst, pc, &inst->src0, error);
|
||||
default: break;
|
||||
}
|
||||
|
||||
inst->src1 = inst->src0;
|
||||
if (!DecodeScalarSource(sdst, pc, &inst->src0, error)) {
|
||||
return false;
|
||||
}
|
||||
if (inst->opcode == Opcode::SAddI32 || inst->opcode == Opcode::SMulkI32) {
|
||||
inst->src_count = 2;
|
||||
return DecodeScalarDestination(sdst, pc, &inst->dst, error);
|
||||
}
|
||||
|
||||
inst->dst.kind = OperandKind::Scc;
|
||||
inst->src_count = 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodeSopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error) {
|
||||
const uint32_t word = code[word_index];
|
||||
const uint32_t ssrc1 = (word >> 8u) & 0xffu;
|
||||
const uint32_t ssrc0 = word & 0xffu;
|
||||
const uint32_t opcode = (word >> 16u) & 0x7fu;
|
||||
|
||||
inst->pc = pc;
|
||||
inst->word = word;
|
||||
inst->family = Family::SOPC;
|
||||
inst->opcode_id = opcode;
|
||||
inst->opcode = Lookup(SOPC_OPS, static_cast<uint32_t>(std::size(SOPC_OPS)), opcode);
|
||||
inst->dst.kind = OperandKind::Scc;
|
||||
SetRawWords(inst, code, word_index, 1);
|
||||
|
||||
if (inst->opcode == Opcode::Unsupported) {
|
||||
SetUnsupported(inst, Family::SOPC, opcode, "SOPC opcode is not implemented");
|
||||
return true;
|
||||
}
|
||||
|
||||
return DecodeBinarySources(pc, code, word_index, inst, ssrc0, ssrc1, error);
|
||||
}
|
||||
|
||||
bool DecodeSopp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error) {
|
||||
const uint32_t word = code[word_index];
|
||||
const uint32_t opcode = (word >> 16u) & 0x7fu;
|
||||
const uint32_t simm = word & 0xffffu;
|
||||
|
||||
(void)error;
|
||||
|
||||
inst->pc = pc;
|
||||
inst->word = word;
|
||||
inst->family = Family::SOPP;
|
||||
inst->opcode_id = opcode;
|
||||
inst->opcode = Lookup(SOPP_OPS, static_cast<uint32_t>(std::size(SOPP_OPS)), opcode);
|
||||
inst->src0.kind = OperandKind::LiteralConstant;
|
||||
inst->src0.value = simm;
|
||||
inst->src0.signed_val = static_cast<int16_t>(simm);
|
||||
inst->src_count = (inst->opcode == Opcode::SNop || inst->opcode == Opcode::SWaitcnt ||
|
||||
inst->opcode == Opcode::SSleep || inst->opcode == Opcode::SSendmsg ||
|
||||
inst->opcode == Opcode::STtraceData || inst->opcode == Opcode::SInstPrefetch)
|
||||
? 1
|
||||
: 0;
|
||||
inst->branch_offset = static_cast<int32_t>(static_cast<int16_t>(simm)) * 4;
|
||||
inst->branch_target = pc + 4u + static_cast<uint32_t>(inst->branch_offset);
|
||||
SetRawWords(inst, code, word_index, 1);
|
||||
|
||||
if (inst->opcode == Opcode::Unsupported) {
|
||||
SetUnsupported(inst, Family::SOPP, opcode, "SOPP control-flow opcode is not implemented");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SCALARALUOPS_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SCALARALUOPS_H_
|
||||
|
||||
#include "graphics/shader/recompiler/ShaderDecoder.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
|
||||
bool DecodeSop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error);
|
||||
bool DecodeSop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error);
|
||||
bool DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error);
|
||||
bool DecodeSopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error);
|
||||
bool DecodeSopp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SCALARALUOPS_H_ */
|
||||
@@ -0,0 +1,946 @@
|
||||
#include "graphics/shader/recompiler/ScalarProvenance.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <compare>
|
||||
#include <deque>
|
||||
#include <fmt/format.h>
|
||||
#include <map>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
uint32_t ScalarValueArgCount(ScalarValueOp op) {
|
||||
switch (op) {
|
||||
case ScalarValueOp::Not: return 1;
|
||||
case ScalarValueOp::Add:
|
||||
case ScalarValueOp::Sub:
|
||||
case ScalarValueOp::Mul:
|
||||
case ScalarValueOp::And:
|
||||
case ScalarValueOp::AndNot:
|
||||
case ScalarValueOp::Or:
|
||||
case ScalarValueOp::OrNot:
|
||||
case ScalarValueOp::Xor:
|
||||
case ScalarValueOp::ShiftLeft:
|
||||
case ScalarValueOp::ShiftRight:
|
||||
case ScalarValueOp::ShiftRightArithmetic:
|
||||
case ScalarValueOp::BitFieldMaskU32:
|
||||
case ScalarValueOp::BitFieldMaskU64Low:
|
||||
case ScalarValueOp::BitFieldMaskU64High: return 2;
|
||||
case ScalarValueOp::AddCarry:
|
||||
case ScalarValueOp::Carry:
|
||||
case ScalarValueOp::SubBorrow:
|
||||
case ScalarValueOp::Borrow:
|
||||
case ScalarValueOp::Add3:
|
||||
case ScalarValueOp::ShiftLeftAdd:
|
||||
case ScalarValueOp::ShiftLeftAddCarry:
|
||||
case ScalarValueOp::AddShiftLeft:
|
||||
case ScalarValueOp::XorAdd:
|
||||
case ScalarValueOp::ShiftLeftOr:
|
||||
case ScalarValueOp::ReadConst: return 3;
|
||||
case ScalarValueOp::ReadConstBuffer: return 5;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t ScalarRegisters = 128;
|
||||
constexpr uint32_t VectorRegisters = 256;
|
||||
|
||||
struct ScalarState {
|
||||
std::array<uint32_t, ScalarRegisters> regs = {};
|
||||
std::array<uint32_t, VectorRegisters> address_bases = {};
|
||||
uint32_t scc = ScalarProvenance::Undefined;
|
||||
uint32_t m0 = ScalarProvenance::Undefined;
|
||||
std::map<uint64_t, uint32_t> vector_lanes;
|
||||
|
||||
void Fill(uint32_t value) {
|
||||
regs.fill(value);
|
||||
address_bases.fill(value);
|
||||
scc = value;
|
||||
m0 = value;
|
||||
vector_lanes.clear();
|
||||
}
|
||||
|
||||
bool operator==(const ScalarState&) const = default;
|
||||
};
|
||||
|
||||
struct ValueKey {
|
||||
ScalarValueOp op = ScalarValueOp::Undefined;
|
||||
uint32_t imm = 0;
|
||||
std::array<uint32_t, 6> args = {};
|
||||
|
||||
auto operator<=>(const ValueKey&) const = default;
|
||||
};
|
||||
|
||||
struct DescriptorKey {
|
||||
std::array<uint32_t, 8> dwords = {};
|
||||
uint32_t dword_count = 0;
|
||||
|
||||
auto operator<=>(const DescriptorKey&) const = default;
|
||||
};
|
||||
|
||||
bool ScalarRegister(const Operand& operand, uint32_t& reg) {
|
||||
if (operand.kind != OperandKind::Register) {
|
||||
return false;
|
||||
}
|
||||
if (operand.reg.file == RegisterFile::Scalar && operand.reg.index < ScalarRegisters) {
|
||||
reg = operand.reg.index;
|
||||
return true;
|
||||
}
|
||||
if (operand.reg.file == RegisterFile::Vcc && operand.reg.index < 2) {
|
||||
reg = 106u + operand.reg.index;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VectorRegister(const Operand& operand, uint32_t& reg) {
|
||||
if (operand.kind != OperandKind::Register || operand.reg.file != RegisterFile::Vector ||
|
||||
operand.reg.index >= VectorRegisters) {
|
||||
return false;
|
||||
}
|
||||
reg = operand.reg.index;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FlatStore(Opcode op) {
|
||||
switch (op) {
|
||||
case Opcode::FlatStoreByte:
|
||||
case Opcode::FlatStoreShort:
|
||||
case Opcode::FlatStoreDword: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool PairDwordOpcode(Opcode op) {
|
||||
switch (op) {
|
||||
case Opcode::MoveU64:
|
||||
case Opcode::WqmB64:
|
||||
case Opcode::SaveexecB64:
|
||||
case Opcode::BitwiseAndU64:
|
||||
case Opcode::BitwiseAndNotU64:
|
||||
case Opcode::BitwiseOrU64:
|
||||
case Opcode::BitwiseOrNotU64:
|
||||
case Opcode::BitwiseXorU64:
|
||||
case Opcode::BitwiseNandU64:
|
||||
case Opcode::BitwiseNorU64:
|
||||
case Opcode::BitwiseXnorU64:
|
||||
case Opcode::BitwiseNotU64:
|
||||
case Opcode::BitFieldMaskU64:
|
||||
case Opcode::BitFieldExtractU64:
|
||||
case Opcode::BitReplicateB64B32:
|
||||
case Opcode::ShiftLeftLogicalU64:
|
||||
case Opcode::ShiftRightLogicalU64:
|
||||
case Opcode::SelectU64: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ScalarMemoryLoad(Opcode op) {
|
||||
return op == Opcode::SLoadDword || op == Opcode::SBufferLoadDword;
|
||||
}
|
||||
|
||||
bool ValueResolved(const ScalarProvenance& provenance, uint32_t id, std::vector<uint8_t>* visited) {
|
||||
if (id <= ScalarProvenance::Unknown || id >= provenance.values.size()) {
|
||||
return false;
|
||||
}
|
||||
if ((*visited)[id] == 1) {
|
||||
return false;
|
||||
}
|
||||
if ((*visited)[id] == 2) {
|
||||
return true;
|
||||
}
|
||||
if ((*visited)[id] == 3) {
|
||||
return false;
|
||||
}
|
||||
(*visited)[id] = 1;
|
||||
const auto& value = provenance.values[id];
|
||||
bool resolved = true;
|
||||
if (value.op == ScalarValueOp::Phi) {
|
||||
resolved = !value.phi_args.empty() &&
|
||||
std::all_of(value.phi_args.begin(), value.phi_args.end(), [&](uint32_t arg) {
|
||||
return ValueResolved(provenance, arg, visited);
|
||||
});
|
||||
} else {
|
||||
const auto args = ScalarValueArgCount(value.op);
|
||||
for (uint32_t i = 0; i < args && resolved; i++) {
|
||||
resolved = ValueResolved(provenance, value.args[i], visited);
|
||||
}
|
||||
}
|
||||
(*visited)[id] = resolved ? 2 : 3;
|
||||
return resolved;
|
||||
}
|
||||
|
||||
class Builder {
|
||||
public:
|
||||
explicit Builder(Program* program): m_program(program), m_graph(program->provenance) {}
|
||||
|
||||
bool Run(std::string* error) {
|
||||
m_graph = {};
|
||||
m_graph.values.resize(2);
|
||||
m_graph.values[ScalarProvenance::Undefined].op = ScalarValueOp::Undefined;
|
||||
m_graph.values[ScalarProvenance::Unknown].op = ScalarValueOp::Unknown;
|
||||
|
||||
const auto block_count = m_program->blocks.size();
|
||||
m_entry.resize(block_count);
|
||||
m_exit.resize(block_count);
|
||||
m_phi.resize(block_count);
|
||||
m_exit_ready.assign(block_count, false);
|
||||
m_queued.assign(block_count, false);
|
||||
for (auto& state: m_entry) {
|
||||
state.Fill(ScalarProvenance::Undefined);
|
||||
}
|
||||
for (auto& state: m_exit) {
|
||||
state.Fill(ScalarProvenance::Undefined);
|
||||
}
|
||||
for (auto& phis: m_phi) {
|
||||
phis.Fill(ScalarProvenance::Undefined);
|
||||
}
|
||||
|
||||
m_user_data.Fill(ScalarProvenance::Undefined);
|
||||
for (uint32_t reg = 0; reg < ScalarRegisters; reg++) {
|
||||
m_user_data.regs[reg] =
|
||||
reg >= m_program->user_data_base &&
|
||||
reg - m_program->user_data_base < m_program->user_data_count
|
||||
? InternValue({ScalarValueOp::UserData, 0, reg})
|
||||
: ScalarProvenance::Unknown;
|
||||
}
|
||||
m_user_data.scc = ScalarProvenance::Unknown;
|
||||
m_user_data.m0 = ScalarProvenance::Unknown;
|
||||
|
||||
if (block_count == 0) {
|
||||
return true;
|
||||
}
|
||||
for (const auto& block: m_program->blocks) {
|
||||
for (const auto predecessor: block.predecessors) {
|
||||
if (predecessor >= block_count) {
|
||||
return Fail(error, fmt::format("scalar provenance has invalid predecessor {}",
|
||||
predecessor));
|
||||
}
|
||||
}
|
||||
for (const auto successor: block.successors) {
|
||||
if (successor >= block_count) {
|
||||
return Fail(error, fmt::format("scalar provenance has invalid successor {}",
|
||||
successor));
|
||||
}
|
||||
}
|
||||
if (!ValidateScalarMemoryGroups(block, error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Queue(0);
|
||||
while (!m_work.empty()) {
|
||||
const auto block_index = m_work.front();
|
||||
m_work.pop_front();
|
||||
m_queued[block_index] = false;
|
||||
|
||||
auto next_entry = MergeEntry(block_index);
|
||||
const bool entry_changed = next_entry != m_entry[block_index];
|
||||
m_entry[block_index] = next_entry;
|
||||
|
||||
auto next_exit = Execute(m_program->blocks[block_index], next_entry, false);
|
||||
const bool was_ready = m_exit_ready[block_index];
|
||||
if (was_ready && !entry_changed && next_exit == m_exit[block_index]) {
|
||||
continue;
|
||||
}
|
||||
m_exit[block_index] = next_exit;
|
||||
m_exit_ready[block_index] = true;
|
||||
for (const auto successor: m_program->blocks[block_index].successors) {
|
||||
Queue(successor);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t block = 0; block < block_count; block++) {
|
||||
Execute(m_program->blocks[block], m_entry[block], true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool Fail(std::string* error, std::string text) const {
|
||||
if (error != nullptr) {
|
||||
*error = std::move(text);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t AddValue(ScalarValue value) {
|
||||
m_graph.values.push_back(std::move(value));
|
||||
return static_cast<uint32_t>(m_graph.values.size() - 1);
|
||||
}
|
||||
|
||||
bool ValidateScalarMemoryGroups(const BasicBlock& block, std::string* error) const {
|
||||
for (size_t index = 0; index < block.instructions.size();) {
|
||||
const auto& first = block.instructions[index];
|
||||
if (!ScalarMemoryLoad(first.op) || first.memory.component_count == 1) {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
const auto count = first.memory.component_count;
|
||||
uint32_t first_dst = 0;
|
||||
if (count == 0 || first.memory.component_index != 0 ||
|
||||
index + count > block.instructions.size() ||
|
||||
!ScalarRegister(first.dst, first_dst)) {
|
||||
return Fail(error,
|
||||
fmt::format("malformed scalar-memory group at pc=0x{:x}", first.pc));
|
||||
}
|
||||
for (uint32_t component = 0; component < count; component++) {
|
||||
const auto& inst = block.instructions[index + component];
|
||||
uint32_t dst = 0;
|
||||
if (inst.op != first.op || inst.pc != first.pc ||
|
||||
inst.memory.component_count != count ||
|
||||
inst.memory.component_index != component ||
|
||||
inst.memory.resource != first.memory.resource ||
|
||||
inst.memory.offset != first.memory.offset + component * 4u ||
|
||||
inst.src_count != first.src_count ||
|
||||
!std::equal(std::begin(inst.src), std::end(inst.src), std::begin(first.src)) ||
|
||||
!ScalarRegister(inst.dst, dst) || dst != first_dst + component) {
|
||||
return Fail(error,
|
||||
fmt::format("malformed scalar-memory component {} at pc=0x{:x}",
|
||||
component, first.pc));
|
||||
}
|
||||
}
|
||||
index += count;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t InternValue(ScalarValue value) {
|
||||
const ValueKey key {value.op, value.imm, value.args};
|
||||
if (const auto found = m_values.find(key); found != m_values.end()) {
|
||||
return found->second;
|
||||
}
|
||||
const auto id = AddValue(std::move(value));
|
||||
m_values.emplace(key, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t Constant(uint32_t value) { return InternValue({ScalarValueOp::Constant, 0, value}); }
|
||||
|
||||
static uint64_t VectorLaneKey(uint32_t reg, uint32_t lane) {
|
||||
return (static_cast<uint64_t>(reg) << 32u) | lane;
|
||||
}
|
||||
|
||||
uint32_t OperandValue(const Operand& operand, const ScalarState& state) {
|
||||
uint32_t reg = 0;
|
||||
if (ScalarRegister(operand, reg)) {
|
||||
return state.regs[reg];
|
||||
}
|
||||
if (operand.kind == OperandKind::Register && operand.reg.file == RegisterFile::Scc) {
|
||||
return state.scc;
|
||||
}
|
||||
if (operand.kind == OperandKind::Register && operand.reg.file == RegisterFile::M0) {
|
||||
return state.m0;
|
||||
}
|
||||
if (operand.kind == OperandKind::ImmediateU32 ||
|
||||
operand.kind == OperandKind::PcRelativeU32) {
|
||||
if (operand.kind == OperandKind::PcRelativeU32) {
|
||||
return InternValue({ScalarValueOp::PcRelativeLow, 0, operand.imm});
|
||||
}
|
||||
return Constant(operand.imm);
|
||||
}
|
||||
return ScalarProvenance::Unknown;
|
||||
}
|
||||
|
||||
uint32_t Define(const Instruction& inst, ScalarValueOp op, const ScalarState& state,
|
||||
uint32_t imm = 0) {
|
||||
ScalarValue node;
|
||||
node.op = op;
|
||||
node.pc = inst.pc;
|
||||
node.imm = imm;
|
||||
for (uint32_t i = 0; i < inst.src_count && i < node.args.size(); i++) {
|
||||
node.args[i] = OperandValue(inst.src[i], state);
|
||||
}
|
||||
return InternValue(std::move(node));
|
||||
}
|
||||
|
||||
uint32_t DefineBorrow(const Instruction& inst, const ScalarState& state) {
|
||||
ScalarValue node;
|
||||
node.op = ScalarValueOp::Borrow;
|
||||
node.pc = inst.pc;
|
||||
node.args[0] =
|
||||
inst.src_count > 0 ? OperandValue(inst.src[0], state) : ScalarProvenance::Unknown;
|
||||
node.args[1] =
|
||||
inst.src_count > 1 ? OperandValue(inst.src[1], state) : ScalarProvenance::Unknown;
|
||||
node.args[2] = inst.src_count > 2 ? OperandValue(inst.src[2], state) : Constant(0);
|
||||
return InternValue(std::move(node));
|
||||
}
|
||||
|
||||
bool ConstantOperand(const Operand& operand, const ScalarState& state, uint32_t* value) {
|
||||
if (value == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const auto id = OperandValue(operand, state);
|
||||
if (id >= m_graph.values.size() || m_graph.values[id].op != ScalarValueOp::Constant) {
|
||||
return false;
|
||||
}
|
||||
*value = m_graph.values[id].imm;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t ReadVectorLane(const Instruction& inst, const ScalarState& state) {
|
||||
if (inst.src_count < 2 || inst.src[0].kind != OperandKind::Register ||
|
||||
inst.src[0].reg.file != RegisterFile::Vector ||
|
||||
(m_program->wave_size != 32 && m_program->wave_size != 64)) {
|
||||
return ScalarProvenance::Unknown;
|
||||
}
|
||||
uint32_t lane = 0;
|
||||
if (!ConstantOperand(inst.src[1], state, &lane)) {
|
||||
return ScalarProvenance::Unknown;
|
||||
}
|
||||
const auto found = state.vector_lanes.find(
|
||||
VectorLaneKey(inst.src[0].reg.index, lane % m_program->wave_size));
|
||||
return found != state.vector_lanes.end() ? found->second : ScalarProvenance::Unknown;
|
||||
}
|
||||
|
||||
void ClearVectorLanes(uint32_t reg, ScalarState* state) {
|
||||
const auto first = state->vector_lanes.lower_bound(VectorLaneKey(reg, 0));
|
||||
const auto last = state->vector_lanes.lower_bound((static_cast<uint64_t>(reg) + 1u) << 32u);
|
||||
state->vector_lanes.erase(first, last);
|
||||
}
|
||||
|
||||
void WriteVectorDestination(const Instruction& inst, ScalarState* state) {
|
||||
if (inst.dst.kind != OperandKind::Register || inst.dst.reg.file != RegisterFile::Vector) {
|
||||
return;
|
||||
}
|
||||
const uint32_t reg = inst.dst.reg.index;
|
||||
uint32_t address_base = ScalarProvenance::Unknown;
|
||||
if (inst.op == Opcode::MoveU32 || inst.op == Opcode::IAddU32 ||
|
||||
inst.op == Opcode::IAddCarryU32 || inst.op == Opcode::ISubU32) {
|
||||
for (uint32_t i = 0; i < inst.src_count; i++) {
|
||||
uint32_t src_reg = 0;
|
||||
if (VectorRegister(inst.src[i], src_reg) &&
|
||||
state->address_bases[src_reg] > ScalarProvenance::Unknown) {
|
||||
address_base = state->address_bases[src_reg];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (address_base == ScalarProvenance::Unknown) {
|
||||
for (uint32_t i = 0; i < inst.src_count; i++) {
|
||||
uint32_t src_reg = 0;
|
||||
if (ScalarRegister(inst.src[i], src_reg) &&
|
||||
state->regs[src_reg] > ScalarProvenance::Unknown) {
|
||||
address_base = state->regs[src_reg];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
state->address_bases[reg] = ScalarProvenance::Unknown;
|
||||
if (inst.op == Opcode::MoveRelDestU32) {
|
||||
state->vector_lanes.clear();
|
||||
return;
|
||||
}
|
||||
if (inst.op != Opcode::WriteLaneU32) {
|
||||
uint32_t dwords = std::max(inst.memory.data_dwords, 1u);
|
||||
if (PairDwordOpcode(inst.op) || inst.op == Opcode::UMadU64U32) {
|
||||
dwords = std::max(dwords, 2u);
|
||||
}
|
||||
for (uint32_t i = 0; i < dwords && reg <= UINT32_MAX - i; i++) {
|
||||
ClearVectorLanes(reg + i, state);
|
||||
if (reg + i < state->address_bases.size()) {
|
||||
state->address_bases[reg + i] = ScalarProvenance::Unknown;
|
||||
}
|
||||
}
|
||||
state->address_bases[reg] = address_base;
|
||||
return;
|
||||
}
|
||||
if (inst.src_count < 2 || (m_program->wave_size != 32 && m_program->wave_size != 64)) {
|
||||
ClearVectorLanes(reg, state);
|
||||
return;
|
||||
}
|
||||
uint32_t lane = 0;
|
||||
if (!ConstantOperand(inst.src[1], *state, &lane)) {
|
||||
ClearVectorLanes(reg, state);
|
||||
return;
|
||||
}
|
||||
state->vector_lanes[VectorLaneKey(reg, lane % m_program->wave_size)] =
|
||||
OperandValue(inst.src[0], *state);
|
||||
}
|
||||
|
||||
uint32_t ReadConst(const Instruction& inst, const ScalarState& state, bool buffer) {
|
||||
ScalarValue node;
|
||||
node.op = buffer ? ScalarValueOp::ReadConstBuffer : ScalarValueOp::ReadConst;
|
||||
node.pc = inst.pc;
|
||||
const auto base = buffer ? inst.memory.resource * 4u : inst.memory.resource;
|
||||
if (base + 1u >= ScalarRegisters) {
|
||||
return ScalarProvenance::Unknown;
|
||||
}
|
||||
node.imm = inst.memory.offset;
|
||||
if (buffer) {
|
||||
if (base + 3u >= ScalarRegisters) {
|
||||
return ScalarProvenance::Unknown;
|
||||
}
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
node.args[i] = state.regs[base + i];
|
||||
}
|
||||
node.args[4] = inst.src_count != 0 ? OperandValue(inst.src[0], state) : Constant(0);
|
||||
} else {
|
||||
node.args[0] = state.regs[base];
|
||||
node.args[1] = state.regs[base + 1u];
|
||||
node.args[2] = inst.src_count != 0 ? OperandValue(inst.src[0], state) : Constant(0);
|
||||
}
|
||||
return InternValue(std::move(node));
|
||||
}
|
||||
|
||||
ScalarValueOp Operation(Opcode op) const {
|
||||
switch (op) {
|
||||
case Opcode::IAddU32:
|
||||
case Opcode::ScalarSignedAddOverflowI32: return ScalarValueOp::Add;
|
||||
case Opcode::IAddCarryU32:
|
||||
case Opcode::ScalarAddCarryU32: return ScalarValueOp::AddCarry;
|
||||
case Opcode::ISubU32:
|
||||
case Opcode::ScalarSubBorrowU32:
|
||||
case Opcode::ScalarSignedSubOverflowI32: return ScalarValueOp::Sub;
|
||||
case Opcode::ScalarSubBorrowCarryU32: return ScalarValueOp::SubBorrow;
|
||||
case Opcode::IMulU32: return ScalarValueOp::Mul;
|
||||
case Opcode::BitwiseAndU32: return ScalarValueOp::And;
|
||||
case Opcode::BitwiseAndNotU32: return ScalarValueOp::AndNot;
|
||||
case Opcode::BitwiseOrU32: return ScalarValueOp::Or;
|
||||
case Opcode::BitwiseOrNotU32: return ScalarValueOp::OrNot;
|
||||
case Opcode::BitwiseXorU32: return ScalarValueOp::Xor;
|
||||
case Opcode::BitwiseNotU32: return ScalarValueOp::Not;
|
||||
case Opcode::ShiftLeftLogicalU32: return ScalarValueOp::ShiftLeft;
|
||||
case Opcode::ShiftRightLogicalU32: return ScalarValueOp::ShiftRight;
|
||||
case Opcode::ShiftRightArithmeticI32: return ScalarValueOp::ShiftRightArithmetic;
|
||||
case Opcode::BitFieldMaskU32: return ScalarValueOp::BitFieldMaskU32;
|
||||
case Opcode::IAdd3U32: return ScalarValueOp::Add3;
|
||||
case Opcode::ScalarShiftLeftAddCarryU32:
|
||||
case Opcode::ShiftLeftAddU32: return ScalarValueOp::ShiftLeftAdd;
|
||||
case Opcode::AddShiftLeftU32: return ScalarValueOp::AddShiftLeft;
|
||||
case Opcode::XorAddU32: return ScalarValueOp::XorAdd;
|
||||
case Opcode::ShiftLeftOrU32: return ScalarValueOp::ShiftLeftOr;
|
||||
default: return ScalarValueOp::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateScc(const Instruction& inst, const ScalarState& before, ScalarState* state) {
|
||||
switch (inst.op) {
|
||||
case Opcode::ScalarAddCarryU32:
|
||||
state->scc = Define(inst, ScalarValueOp::Carry, before);
|
||||
break;
|
||||
case Opcode::ScalarSubBorrowU32:
|
||||
case Opcode::ScalarSubBorrowCarryU32: state->scc = DefineBorrow(inst, before); break;
|
||||
case Opcode::ScalarShiftLeftAddCarryU32:
|
||||
state->scc = Define(inst, ScalarValueOp::ShiftLeftAddCarry, before);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteDestination(const Instruction& inst, ScalarState* state) {
|
||||
if (inst.dst.kind == OperandKind::Register && inst.dst.reg.file == RegisterFile::Scc) {
|
||||
state->scc = inst.op == Opcode::MoveU32 && inst.src_count != 0
|
||||
? OperandValue(inst.src[0], *state)
|
||||
: ScalarProvenance::Unknown;
|
||||
return;
|
||||
}
|
||||
if (inst.dst.kind == OperandKind::Register && inst.dst.reg.file == RegisterFile::M0) {
|
||||
const auto before = *state;
|
||||
const auto op = Operation(inst.op);
|
||||
state->m0 = inst.op == Opcode::MoveU32 && inst.src_count != 0
|
||||
? OperandValue(inst.src[0], before)
|
||||
: op != ScalarValueOp::Unknown ? Define(inst, op, before)
|
||||
: ScalarProvenance::Unknown;
|
||||
UpdateScc(inst, before, state);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t dst = 0;
|
||||
if (!ScalarRegister(inst.dst, dst)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto before = *state;
|
||||
state->regs[dst] = ScalarProvenance::Unknown;
|
||||
if (PairDwordOpcode(inst.op) && dst + 1 < ScalarRegisters) {
|
||||
state->regs[dst + 1] = ScalarProvenance::Unknown;
|
||||
}
|
||||
|
||||
uint32_t value = ScalarProvenance::Unknown;
|
||||
switch (inst.op) {
|
||||
case Opcode::MoveU32:
|
||||
case Opcode::MoveF32Bits:
|
||||
value = inst.src_count == 0 ? ScalarProvenance::Unknown
|
||||
: inst.src[0].kind == OperandKind::PcRelativeU32
|
||||
? InternValue({ScalarValueOp::PcRelativeLow, inst.pc, inst.src[0].imm})
|
||||
: OperandValue(inst.src[0], before);
|
||||
if (inst.op == Opcode::MoveU32 && inst.src_count != 0 &&
|
||||
inst.src[0].kind == OperandKind::ImmediateU32 && inst.src[0].imm == 0 &&
|
||||
dst != 0) {
|
||||
const auto low = before.regs[dst - 1];
|
||||
if (low < m_graph.values.size() &&
|
||||
m_graph.values[low].op == ScalarValueOp::PcRelativeLow &&
|
||||
m_graph.values[low].pc == inst.pc) {
|
||||
value = InternValue(
|
||||
{ScalarValueOp::PcRelativeHigh, inst.pc, m_graph.values[low].imm});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Opcode::MoveU64: {
|
||||
uint32_t src = 0;
|
||||
if (inst.src_count != 0 && dst + 1 < ScalarRegisters) {
|
||||
if (ScalarRegister(inst.src[0], src) && src + 1 < ScalarRegisters) {
|
||||
value = before.regs[src];
|
||||
state->regs[dst + 1] = before.regs[src + 1];
|
||||
} else {
|
||||
value = OperandValue(inst.src[0], before);
|
||||
state->regs[dst + 1] = Constant(
|
||||
inst.src[0].kind == OperandKind::ImmediateU32 && inst.src[0].sext_64
|
||||
? UINT32_MAX
|
||||
: 0u);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Opcode::BitFieldMaskU64:
|
||||
value = Define(inst, ScalarValueOp::BitFieldMaskU64Low, before);
|
||||
if (dst + 1 < ScalarRegisters) {
|
||||
state->regs[dst + 1] = Define(inst, ScalarValueOp::BitFieldMaskU64High, before);
|
||||
}
|
||||
break;
|
||||
case Opcode::SLoadDword: value = ReadConst(inst, before, false); break;
|
||||
case Opcode::SBufferLoadDword: value = ReadConst(inst, before, true); break;
|
||||
case Opcode::ReadLaneU32: value = ReadVectorLane(inst, before); break;
|
||||
default: {
|
||||
const auto op = Operation(inst.op);
|
||||
if (op != ScalarValueOp::Unknown) {
|
||||
value = Define(inst, op, before);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
state->regs[dst] = value;
|
||||
UpdateScc(inst, before, state);
|
||||
|
||||
uint32_t dst2 = 0;
|
||||
if (ScalarRegister(inst.dst2, dst2)) {
|
||||
state->regs[dst2] =
|
||||
inst.src_count > 1 ? OperandValue(inst.src[1], before) : ScalarProvenance::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t AddDescriptor(DescriptorValue descriptor) {
|
||||
const DescriptorKey key {descriptor.dwords, descriptor.dword_count};
|
||||
if (const auto found = m_descriptors.find(key); found != m_descriptors.end()) {
|
||||
return found->second;
|
||||
}
|
||||
m_graph.descriptors.push_back(descriptor);
|
||||
const auto id = static_cast<uint32_t>(m_graph.descriptors.size() - 1) + 2u;
|
||||
m_descriptors.emplace(key, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t AddDescriptor(const ScalarState& state, uint32_t base, uint32_t dwords) {
|
||||
if (base >= ScalarRegisters || dwords == 0 || dwords > 8 ||
|
||||
base + dwords > ScalarRegisters) {
|
||||
return ScalarProvenance::Unknown;
|
||||
}
|
||||
DescriptorValue descriptor;
|
||||
descriptor.dword_count = dwords;
|
||||
for (uint32_t i = 0; i < dwords; i++) {
|
||||
descriptor.dwords[i] = state.regs[base + i];
|
||||
}
|
||||
return AddDescriptor(descriptor);
|
||||
}
|
||||
|
||||
uint32_t AddFlatAddressDescriptor(const Instruction& inst, const ScalarState& state) {
|
||||
const uint32_t first = FlatStore(inst.op) ? 1u : 0u;
|
||||
if (inst.src_count < first + 2u) {
|
||||
return ScalarProvenance::Unknown;
|
||||
}
|
||||
uint32_t low = 0;
|
||||
uint32_t high = 0;
|
||||
if (!VectorRegister(inst.src[first], low) || !VectorRegister(inst.src[first + 1u], high) ||
|
||||
state.address_bases[low] <= ScalarProvenance::Unknown ||
|
||||
state.address_bases[high] <= ScalarProvenance::Unknown) {
|
||||
return ScalarProvenance::Unknown;
|
||||
}
|
||||
DescriptorValue descriptor;
|
||||
descriptor.dword_count = 2;
|
||||
descriptor.dwords[0] = state.address_bases[low];
|
||||
descriptor.dwords[1] = state.address_bases[high];
|
||||
return AddDescriptor(descriptor);
|
||||
}
|
||||
|
||||
void AttachSources(Instruction* inst, const ScalarState& state) {
|
||||
inst->memory.resource_source = 0;
|
||||
inst->memory.sampler_source = 0;
|
||||
if (inst->op == Opcode::SLoadDword) {
|
||||
inst->memory.resource_source = AddDescriptor(state, inst->memory.resource, 2);
|
||||
return;
|
||||
}
|
||||
if (inst->memory.kind == ResourceKind::Flat || inst->memory.kind == ResourceKind::Global ||
|
||||
inst->memory.kind == ResourceKind::Scratch) {
|
||||
inst->memory.resource_source = ScalarProvenance::Unknown;
|
||||
if (inst->memory.kind == ResourceKind::Flat) {
|
||||
inst->memory.resource_source = AddFlatAddressDescriptor(*inst, state);
|
||||
}
|
||||
for (uint32_t i = 0; i < inst->src_count; i++) {
|
||||
const auto& source = inst->src[i];
|
||||
if (inst->memory.resource_source == ScalarProvenance::Unknown &&
|
||||
source.kind == OperandKind::Register &&
|
||||
source.reg.file == RegisterFile::Scalar) {
|
||||
inst->memory.resource_source = AddDescriptor(state, source.reg.index, 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (inst->memory.kind == ResourceKind::Buffer ||
|
||||
(inst->memory.kind == ResourceKind::ScalarBuffer &&
|
||||
inst->op == Opcode::SBufferLoadDword)) {
|
||||
const auto base = inst->memory.resource * 4u;
|
||||
inst->memory.resource_source = AddDescriptor(state, base, 4);
|
||||
return;
|
||||
}
|
||||
if (inst->memory.kind == ResourceKind::Image ||
|
||||
inst->memory.kind == ResourceKind::ImageUint ||
|
||||
inst->memory.kind == ResourceKind::StorageImage ||
|
||||
inst->memory.kind == ResourceKind::StorageImageUint) {
|
||||
inst->memory.resource_source = AddDescriptor(state, inst->memory.resource * 4u, 8);
|
||||
if (inst->op == Opcode::ImageSample || inst->op == Opcode::ImageGather4 ||
|
||||
inst->op == Opcode::ImageGetLod) {
|
||||
inst->memory.sampler_source = AddDescriptor(state, inst->memory.sampler * 4u, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AttachScalarSources(Instruction* inst, const ScalarState& state) {
|
||||
std::fill(std::begin(inst->scalar_sources), std::end(inst->scalar_sources),
|
||||
ScalarProvenance::Undefined);
|
||||
for (uint32_t i = 0; i < inst->src_count; i++) {
|
||||
const auto& source = inst->src[i];
|
||||
if (source.kind == OperandKind::Register && source.reg.file != RegisterFile::Vector &&
|
||||
source.reg.file != RegisterFile::Exec) {
|
||||
inst->scalar_sources[i] = OperandValue(source, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScalarState Execute(BasicBlock& block, ScalarState state, bool attach_sources) {
|
||||
for (size_t index = 0; index < block.instructions.size();) {
|
||||
auto& inst = block.instructions[index];
|
||||
uint32_t first_dst = 0;
|
||||
if (ScalarMemoryLoad(inst.op) && inst.memory.component_count > 1 &&
|
||||
ScalarRegister(inst.dst, first_dst)) {
|
||||
const auto before = state;
|
||||
for (uint32_t component = 0; component < inst.memory.component_count; component++) {
|
||||
auto& component_inst = block.instructions[index + component];
|
||||
if (attach_sources) {
|
||||
component_inst.scalar_value = ScalarProvenance::Undefined;
|
||||
AttachScalarSources(&component_inst, before);
|
||||
AttachSources(&component_inst, before);
|
||||
}
|
||||
auto component_state = before;
|
||||
WriteDestination(component_inst, &component_state);
|
||||
uint32_t dst = 0;
|
||||
ScalarRegister(component_inst.dst, dst);
|
||||
state.regs[dst] = component_state.regs[dst];
|
||||
if (attach_sources) {
|
||||
component_inst.scalar_value = component_state.regs[dst];
|
||||
}
|
||||
}
|
||||
index += inst.memory.component_count;
|
||||
continue;
|
||||
}
|
||||
if (attach_sources) {
|
||||
inst.scalar_value = ScalarProvenance::Undefined;
|
||||
AttachScalarSources(&inst, state);
|
||||
AttachSources(&inst, state);
|
||||
}
|
||||
WriteVectorDestination(inst, &state);
|
||||
WriteDestination(inst, &state);
|
||||
if (attach_sources) {
|
||||
uint32_t dst = 0;
|
||||
if (ScalarRegister(inst.dst, dst)) {
|
||||
inst.scalar_value = state.regs[dst];
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
ScalarState MergeEntry(size_t block_index) {
|
||||
ScalarState result;
|
||||
result.Fill(ScalarProvenance::Undefined);
|
||||
const auto& block = m_program->blocks[block_index];
|
||||
const auto merge = [&](uint32_t initial, auto predecessor_value, uint32_t* phi) {
|
||||
std::vector<uint32_t> incoming;
|
||||
if (initial != ScalarProvenance::Undefined) {
|
||||
incoming.push_back(initial);
|
||||
}
|
||||
for (const auto predecessor: block.predecessors) {
|
||||
const auto value = predecessor_value(predecessor, m_exit[predecessor]);
|
||||
if (value != ScalarProvenance::Undefined &&
|
||||
std::find(incoming.begin(), incoming.end(), value) == incoming.end()) {
|
||||
incoming.push_back(value);
|
||||
}
|
||||
}
|
||||
if (incoming.empty()) {
|
||||
return ScalarProvenance::Undefined;
|
||||
}
|
||||
if (incoming.size() == 1) {
|
||||
return incoming[0];
|
||||
}
|
||||
if (*phi == ScalarProvenance::Undefined) {
|
||||
*phi = AddValue({ScalarValueOp::Phi, block.start_pc});
|
||||
}
|
||||
m_graph.values[*phi].phi_args = std::move(incoming);
|
||||
return *phi;
|
||||
};
|
||||
|
||||
for (uint32_t reg = 0; reg < ScalarRegisters; reg++) {
|
||||
const auto initial =
|
||||
block_index == 0 ? m_user_data.regs[reg] : ScalarProvenance::Undefined;
|
||||
result.regs[reg] = merge(
|
||||
initial, [reg](uint32_t, const ScalarState& state) { return state.regs[reg]; },
|
||||
&m_phi[block_index].regs[reg]);
|
||||
}
|
||||
for (uint32_t reg = 0; reg < VectorRegisters; reg++) {
|
||||
result.address_bases[reg] = merge(
|
||||
block_index == 0 ? ScalarProvenance::Unknown : ScalarProvenance::Undefined,
|
||||
[reg](uint32_t, const ScalarState& state) { return state.address_bases[reg]; },
|
||||
&m_phi[block_index].address_bases[reg]);
|
||||
}
|
||||
result.scc = merge(
|
||||
block_index == 0 ? m_user_data.scc : ScalarProvenance::Undefined,
|
||||
[](uint32_t, const ScalarState& state) { return state.scc; }, &m_phi[block_index].scc);
|
||||
result.m0 = merge(
|
||||
block_index == 0 ? m_user_data.m0 : ScalarProvenance::Undefined,
|
||||
[](uint32_t, const ScalarState& state) { return state.m0; }, &m_phi[block_index].m0);
|
||||
|
||||
std::vector<uint64_t> vector_lanes;
|
||||
for (const auto predecessor: block.predecessors) {
|
||||
for (const auto& [lane, value]: m_exit[predecessor].vector_lanes) {
|
||||
(void)value;
|
||||
vector_lanes.push_back(lane);
|
||||
}
|
||||
}
|
||||
std::sort(vector_lanes.begin(), vector_lanes.end());
|
||||
vector_lanes.erase(std::unique(vector_lanes.begin(), vector_lanes.end()),
|
||||
vector_lanes.end());
|
||||
for (const auto lane: vector_lanes) {
|
||||
auto& phi = m_phi[block_index].vector_lanes[lane];
|
||||
result.vector_lanes[lane] = merge(
|
||||
block_index == 0 ? ScalarProvenance::Unknown : ScalarProvenance::Undefined,
|
||||
[this, lane](uint32_t predecessor, const ScalarState& state) {
|
||||
if (!m_exit_ready[predecessor]) {
|
||||
return ScalarProvenance::Undefined;
|
||||
}
|
||||
const auto found = state.vector_lanes.find(lane);
|
||||
return found != state.vector_lanes.end() ? found->second
|
||||
: ScalarProvenance::Unknown;
|
||||
},
|
||||
&phi);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Queue(uint32_t block) {
|
||||
if (!m_queued[block]) {
|
||||
m_queued[block] = true;
|
||||
m_work.push_back(block);
|
||||
}
|
||||
}
|
||||
|
||||
Program* m_program;
|
||||
ScalarProvenance& m_graph;
|
||||
ScalarState m_user_data;
|
||||
std::vector<ScalarState> m_entry;
|
||||
std::vector<ScalarState> m_exit;
|
||||
std::vector<ScalarState> m_phi;
|
||||
std::vector<bool> m_exit_ready;
|
||||
std::vector<bool> m_queued;
|
||||
std::deque<uint32_t> m_work;
|
||||
std::map<ValueKey, uint32_t> m_values;
|
||||
std::map<DescriptorKey, uint32_t> m_descriptors;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool BuildScalarProvenance(Program* program, std::string* error) {
|
||||
if (program == nullptr) {
|
||||
if (error != nullptr) {
|
||||
*error = "invalid scalar provenance program";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (program->resource_tracking_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = "cannot rebuild scalar provenance after resource tracking";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (program->user_data_count > 64) {
|
||||
if (error != nullptr) {
|
||||
*error = "scalar provenance user-data count exceeds 64 SGPRs";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (program->user_data_base > ScalarRegisters ||
|
||||
program->user_data_count > ScalarRegisters - program->user_data_base) {
|
||||
if (error != nullptr) {
|
||||
*error = "scalar provenance user-data window is out of range";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (program->srt_patching_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = "cannot rebuild scalar provenance after SRT patching";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
program->srt = {};
|
||||
program->srt_plan_complete = false;
|
||||
return Builder(program).Run(error);
|
||||
}
|
||||
|
||||
const DescriptorValue* GetDescriptorSource(const Program& program, uint32_t source) {
|
||||
if (source < 2 || source - 2 >= program.provenance.descriptors.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &program.provenance.descriptors[source - 2];
|
||||
}
|
||||
|
||||
bool DescriptorSourceResolved(const Program& program, uint32_t source) {
|
||||
const auto* descriptor = GetDescriptorSource(program, source);
|
||||
if (descriptor == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> visited(program.provenance.values.size());
|
||||
for (uint32_t i = 0; i < descriptor->dword_count; i++) {
|
||||
if (!ValueResolved(program.provenance, descriptor->dwords[i], &visited)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string ScalarValueToString(const ScalarProvenance& provenance, uint32_t value) {
|
||||
if (value >= provenance.values.size()) {
|
||||
return "invalid";
|
||||
}
|
||||
const auto& node = provenance.values[value];
|
||||
switch (node.op) {
|
||||
case ScalarValueOp::Undefined: return "undefined";
|
||||
case ScalarValueOp::Unknown: return "unknown";
|
||||
case ScalarValueOp::UserData: return fmt::format("ud{}", node.imm);
|
||||
case ScalarValueOp::Constant: return fmt::format("0x{:08x}", node.imm);
|
||||
case ScalarValueOp::PcRelativeLow: return fmt::format("pc_lo+0x{:x}", node.imm);
|
||||
case ScalarValueOp::PcRelativeHigh: return fmt::format("pc_hi+0x{:x}", node.imm);
|
||||
case ScalarValueOp::ReadConst:
|
||||
return fmt::format("read_const({}, {}, {}, +{})", node.args[0], node.args[1],
|
||||
node.args[2], node.imm);
|
||||
case ScalarValueOp::ReadConstBuffer:
|
||||
return fmt::format("read_const_buffer({}, {}, {}, {}, {}, +{})", node.args[0],
|
||||
node.args[1], node.args[2], node.args[3], node.args[4], node.imm);
|
||||
case ScalarValueOp::Phi: return fmt::format("phi{}", value);
|
||||
case ScalarValueOp::BitFieldMaskU32:
|
||||
return fmt::format("bfm_u32({}, {})", node.args[0], node.args[1]);
|
||||
default: return fmt::format("value{}", value);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SCALARPROVENANCE_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SCALARPROVENANCE_H_
|
||||
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
// Builds scalar reaching definitions and attaches descriptor sources to individual memory uses.
|
||||
bool BuildScalarProvenance(Program* program, std::string* error);
|
||||
|
||||
uint32_t ScalarValueArgCount(ScalarValueOp op);
|
||||
|
||||
const DescriptorValue* GetDescriptorSource(const Program& program, uint32_t source);
|
||||
bool DescriptorSourceResolved(const Program& program, uint32_t source);
|
||||
|
||||
std::string ScalarValueToString(const ScalarProvenance& provenance, uint32_t value);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SCALARPROVENANCE_H_ */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,118 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERCFG_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERCFG_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/shader/recompiler/ShaderDecoder.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::CFG {
|
||||
|
||||
enum class BranchCondition {
|
||||
Always,
|
||||
SccZero,
|
||||
SccNonZero,
|
||||
VccZero,
|
||||
VccNonZero,
|
||||
ExecZero,
|
||||
ExecNonZero,
|
||||
Unknown
|
||||
};
|
||||
|
||||
enum class TerminatorKind { Branch, ConditionalBranch, IndirectBranch, Return, Unsupported };
|
||||
|
||||
enum class FailureKind {
|
||||
None,
|
||||
InvalidInput,
|
||||
UnsupportedInstruction,
|
||||
InvalidBranchTarget,
|
||||
MissingFallthrough,
|
||||
InvalidLabel,
|
||||
IrreducibleControlFlow,
|
||||
StructuredControlFlow
|
||||
};
|
||||
|
||||
struct Terminator {
|
||||
TerminatorKind kind = TerminatorKind::Return;
|
||||
BranchCondition condition = BranchCondition::Always;
|
||||
uint32_t true_block = UINT32_MAX;
|
||||
uint32_t false_block = UINT32_MAX;
|
||||
uint32_t merge_block = UINT32_MAX;
|
||||
uint32_t continue_block = UINT32_MAX;
|
||||
uint32_t indirect_pc_sgpr = UINT32_MAX;
|
||||
uint32_t indirect_selector_code = UINT32_MAX;
|
||||
std::vector<uint32_t> indirect_target_pcs;
|
||||
std::vector<uint32_t> indirect_targets;
|
||||
std::vector<uint32_t> indirect_selector_values;
|
||||
std::vector<uint32_t> indirect_selector_targets;
|
||||
bool loop_header = false;
|
||||
};
|
||||
|
||||
struct BasicBlock {
|
||||
uint32_t id = 0;
|
||||
uint32_t start_pc = 0;
|
||||
uint32_t end_pc = 0;
|
||||
uint32_t inst_begin = 0;
|
||||
uint32_t inst_end = 0;
|
||||
std::vector<uint32_t> predecessors;
|
||||
std::vector<uint32_t> successors;
|
||||
std::vector<uint32_t> dominators;
|
||||
std::vector<uint32_t> post_dominators;
|
||||
Terminator terminator;
|
||||
};
|
||||
|
||||
struct BackEdge {
|
||||
uint32_t from = UINT32_MAX;
|
||||
uint32_t to = UINT32_MAX;
|
||||
bool natural = false;
|
||||
};
|
||||
|
||||
struct NaturalLoop {
|
||||
uint32_t header = UINT32_MAX;
|
||||
uint32_t latch = UINT32_MAX;
|
||||
uint32_t merge = UINT32_MAX;
|
||||
uint32_t continue_block = UINT32_MAX;
|
||||
std::vector<uint32_t> body_blocks;
|
||||
std::vector<uint32_t> exit_blocks;
|
||||
};
|
||||
|
||||
struct StronglyConnectedComponent {
|
||||
std::vector<uint32_t> blocks;
|
||||
std::vector<uint32_t> entry_blocks;
|
||||
bool irreducible = false;
|
||||
};
|
||||
|
||||
struct Graph {
|
||||
std::vector<BasicBlock> blocks;
|
||||
std::vector<BackEdge> back_edges;
|
||||
std::vector<NaturalLoop> natural_loops;
|
||||
std::vector<StronglyConnectedComponent> components;
|
||||
std::vector<uint32_t> code_table_load_pcs;
|
||||
uint32_t entry_block = UINT32_MAX;
|
||||
bool irreducible = false;
|
||||
bool unsupported = false;
|
||||
FailureKind failure_kind = FailureKind::None;
|
||||
uint32_t failure_block = UINT32_MAX;
|
||||
std::string unsupported_reason;
|
||||
|
||||
const BasicBlock* FindBlock(uint32_t id) const;
|
||||
BasicBlock* FindBlock(uint32_t id);
|
||||
const BasicBlock* FindBlockByPc(uint32_t pc) const;
|
||||
BasicBlock* FindBlockByPc(uint32_t pc);
|
||||
bool Dominates(uint32_t dominator, uint32_t block) const;
|
||||
bool PostDominates(uint32_t post_dominator, uint32_t block) const;
|
||||
uint32_t FindNearestCommonPostDominator(uint32_t block_a, uint32_t block_b) const;
|
||||
};
|
||||
|
||||
bool BuildGraph(const Decoder::Program& program, Graph* graph, std::string* error);
|
||||
bool Structurize(Graph* graph, std::string* error);
|
||||
std::string BranchConditionToString(BranchCondition condition);
|
||||
std::string FailureKindToString(FailureKind kind);
|
||||
std::string FormatBlockDiagnostic(const Graph& graph, uint32_t block_id, const char* stage,
|
||||
const std::string& reason);
|
||||
std::string GraphToString(const Graph& graph);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::CFG
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERCFG_H_ */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,677 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERDECODER_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERDECODER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
|
||||
enum class Family {
|
||||
Unknown,
|
||||
SOP1,
|
||||
SOP2,
|
||||
SOPK,
|
||||
SOPC,
|
||||
SOPP,
|
||||
VOP1,
|
||||
VOP2,
|
||||
VOP3,
|
||||
VOP3P,
|
||||
VOPC,
|
||||
VINTRP,
|
||||
SMEM,
|
||||
MUBUF,
|
||||
MTBUF,
|
||||
FLAT,
|
||||
DS,
|
||||
MIMG,
|
||||
EXP
|
||||
};
|
||||
|
||||
enum class Opcode {
|
||||
Unknown,
|
||||
Unsupported,
|
||||
|
||||
SMovB32,
|
||||
SMovB64,
|
||||
SMovkI32,
|
||||
SAbsI32,
|
||||
SBrevB32,
|
||||
SBcnt1I32B32,
|
||||
SBcnt1I32B64,
|
||||
SFf1I32B32,
|
||||
SFlbitI32B64,
|
||||
SBitreplicateB64B32,
|
||||
SGetpcB64,
|
||||
SSetpcB64,
|
||||
SAndSaveexecB32,
|
||||
SAndn1SaveexecB32,
|
||||
SAndSaveexecB64,
|
||||
SOrn2SaveexecB64,
|
||||
SAndn1SaveexecB64,
|
||||
SNotB32,
|
||||
SNotB64,
|
||||
SWqmB64,
|
||||
SAddU32,
|
||||
SAddcU32,
|
||||
SSubbU32,
|
||||
SSubU32,
|
||||
SAddI32,
|
||||
SSubI32,
|
||||
SBitcmp0B32,
|
||||
SBitcmp1B32,
|
||||
SBitset0B32,
|
||||
SBitset1B32,
|
||||
SMinI32,
|
||||
SMaxI32,
|
||||
SMinU32,
|
||||
SMaxU32,
|
||||
SAndB32,
|
||||
SAndB64,
|
||||
SAndn2B32,
|
||||
SAndn2B64,
|
||||
SOrB32,
|
||||
SOrB64,
|
||||
SOrn2B32,
|
||||
SOrn2B64,
|
||||
SXorB32,
|
||||
SXorB64,
|
||||
SNandB32,
|
||||
SNandB64,
|
||||
SNorB32,
|
||||
SNorB64,
|
||||
SXnorB32,
|
||||
SXnorB64,
|
||||
SLshlB32,
|
||||
SLshlB64,
|
||||
SLshl1AddU32,
|
||||
SLshl2AddU32,
|
||||
SLshl3AddU32,
|
||||
SLshl4AddU32,
|
||||
SLshrB32,
|
||||
SLshrB64,
|
||||
SAshrI32,
|
||||
SMulI32,
|
||||
SMulHiU32,
|
||||
SMulkI32,
|
||||
SBfeU32,
|
||||
SBfeU64,
|
||||
SBfmB32,
|
||||
SBfmB64,
|
||||
SCselectB32,
|
||||
SCselectB64,
|
||||
SPackLlB32B16,
|
||||
SPackLhB32B16,
|
||||
SPackHhB32B16,
|
||||
SCmpEqI32,
|
||||
SCmpLgI32,
|
||||
SCmpGtI32,
|
||||
SCmpGeI32,
|
||||
SCmpLtI32,
|
||||
SCmpLeI32,
|
||||
SCmpEqU32,
|
||||
SCmpLgU32,
|
||||
SCmpGtU32,
|
||||
SCmpGeU32,
|
||||
SCmpLtU32,
|
||||
SCmpLeU32,
|
||||
SCmpLgU64,
|
||||
|
||||
VNop,
|
||||
VMovB32,
|
||||
VMovreldB32,
|
||||
VMovrelsB32,
|
||||
VReadfirstlaneB32,
|
||||
VReadlaneB32,
|
||||
VWritelaneB32,
|
||||
VPermlane16B32,
|
||||
VPermlanex16B32,
|
||||
VCubeidF32,
|
||||
VCubescF32,
|
||||
VCubetcF32,
|
||||
VCubemaF32,
|
||||
VCndmaskB32,
|
||||
VDot2cF32F16,
|
||||
VCvtF32I32,
|
||||
VCvtF32U32,
|
||||
VCvtU32F32,
|
||||
VCvtI32F32,
|
||||
VCvtF16F32,
|
||||
VCvtF32F16,
|
||||
VCvtF16U16,
|
||||
VCvtU16F16,
|
||||
VCvtF16I16,
|
||||
VCvtI16F16,
|
||||
VCvtRpiI32F32,
|
||||
VCvtFlrI32F32,
|
||||
VCvtOffF32I4,
|
||||
VCvtF32Ubyte0,
|
||||
VCvtF32Ubyte1,
|
||||
VCvtF32Ubyte2,
|
||||
VCvtF32Ubyte3,
|
||||
VRcpF32,
|
||||
VFractF32,
|
||||
VTruncF32,
|
||||
VCeilF32,
|
||||
VRndneF32,
|
||||
VFloorF32,
|
||||
VExpF32,
|
||||
VLogF32,
|
||||
VRsqF32,
|
||||
VSqrtF32,
|
||||
VRcpF16,
|
||||
VSqrtF16,
|
||||
VRsqF16,
|
||||
VLogF16,
|
||||
VExpF16,
|
||||
VFloorF16,
|
||||
VCeilF16,
|
||||
VTruncF16,
|
||||
VRndneF16,
|
||||
VSinF32,
|
||||
VCosF32,
|
||||
VNotB32,
|
||||
VBfrevB32,
|
||||
VFfbhU32,
|
||||
VFfblB32,
|
||||
VAddF32,
|
||||
VSubF32,
|
||||
VSubrevF32,
|
||||
VMulF32,
|
||||
VMacF32,
|
||||
VMadmkF32,
|
||||
VMadakF32,
|
||||
VMinF32,
|
||||
VMaxF32,
|
||||
VAddF16,
|
||||
VSubF16,
|
||||
VSubrevF16,
|
||||
VMulF16,
|
||||
VFmacF16,
|
||||
VFmamkF16,
|
||||
VFmaakF16,
|
||||
VMinI32,
|
||||
VMaxI32,
|
||||
VMulI32I24,
|
||||
VMulU32U24,
|
||||
VMulLoU32,
|
||||
VMulHiU32,
|
||||
VMulLoI32,
|
||||
VMulHiI32,
|
||||
VCvtPkrtzF16F32,
|
||||
VCvtPkU8F32,
|
||||
VMadF32,
|
||||
VMadI32I24,
|
||||
VMadU32U24,
|
||||
VMadU64U32,
|
||||
VFmaF32,
|
||||
VFmaF16,
|
||||
VPackB32F16,
|
||||
VBfeU32,
|
||||
VBfeI32,
|
||||
VBfiB32,
|
||||
VAlignbitB32,
|
||||
VMin3F32,
|
||||
VMin3I32,
|
||||
VMin3U32,
|
||||
VMin3F16,
|
||||
VMax3F32,
|
||||
VMax3I32,
|
||||
VMax3U32,
|
||||
VMax3F16,
|
||||
VMed3F32,
|
||||
VMed3I32,
|
||||
VMed3U32,
|
||||
VMed3F16,
|
||||
VSadU32,
|
||||
VAdd3U32,
|
||||
VLshlAddU32,
|
||||
VAddLshlU32,
|
||||
VXadU32,
|
||||
VLshlOrB32,
|
||||
VAndOrB32,
|
||||
VOr3B32,
|
||||
VXor3B32,
|
||||
VAddI32,
|
||||
VSubI32,
|
||||
VSubrevI32,
|
||||
VBfmB32,
|
||||
VLdexpF32,
|
||||
VCvtPknormI16F32,
|
||||
VCvtPknormU16F32,
|
||||
VCvtPkU16U32,
|
||||
VPkMadI16,
|
||||
VPkMulLoU16,
|
||||
VPkAddI16,
|
||||
VPkSubI16,
|
||||
VPkLshlrevB16,
|
||||
VPkLshrrevB16,
|
||||
VPkAshrrevI16,
|
||||
VPkMaxI16,
|
||||
VPkMinI16,
|
||||
VPkMadU16,
|
||||
VPkAddF16,
|
||||
VPkMulF16,
|
||||
VPkMinF16,
|
||||
VPkMaxF16,
|
||||
VPkFmaF16,
|
||||
VPkFmacF16,
|
||||
VPkAddU16,
|
||||
VPkSubU16,
|
||||
VPkMaxU16,
|
||||
VPkMinU16,
|
||||
VMadMixloF16,
|
||||
VMadMixhiF16,
|
||||
VAddNcU32,
|
||||
VAddcU32,
|
||||
VSubNcU32,
|
||||
VSubrevNcU32,
|
||||
VAddNcU16,
|
||||
VSubNcU16,
|
||||
VMaxU16,
|
||||
VMaxI16,
|
||||
VMinU16,
|
||||
VMinI16,
|
||||
VAddNcI16,
|
||||
VSubNcI16,
|
||||
VAndB32,
|
||||
VOrB32,
|
||||
VXorB32,
|
||||
VXnorB32,
|
||||
VLshlB32,
|
||||
VLshlrevB32,
|
||||
VLshrB32,
|
||||
VLshrrevB32,
|
||||
VAshrI32,
|
||||
VAshrrevI32,
|
||||
VLshlrevB16,
|
||||
VLshrrevB16,
|
||||
VAshrrevI16,
|
||||
VBcntU32B32,
|
||||
VMbcntLoU32B32,
|
||||
VMbcntHiU32B32,
|
||||
VMinU32,
|
||||
VMaxU32,
|
||||
VMaxF16,
|
||||
VMinF16,
|
||||
VCmpFF32,
|
||||
VCmpLtF32,
|
||||
VCmpEqF32,
|
||||
VCmpLeF32,
|
||||
VCmpGtF32,
|
||||
VCmpLgF32,
|
||||
VCmpGeF32,
|
||||
VCmpOF32,
|
||||
VCmpUF32,
|
||||
VCmpNgeF32,
|
||||
VCmpNlgF32,
|
||||
VCmpNgtF32,
|
||||
VCmpNleF32,
|
||||
VCmpNeqF32,
|
||||
VCmpNltF32,
|
||||
VCmpTruF32,
|
||||
VCmpxLtF32,
|
||||
VCmpxEqF32,
|
||||
VCmpxLeF32,
|
||||
VCmpxGtF32,
|
||||
VCmpxLgF32,
|
||||
VCmpxGeF32,
|
||||
VCmpxNgeF32,
|
||||
VCmpxNlgF32,
|
||||
VCmpxNgtF32,
|
||||
VCmpxNleF32,
|
||||
VCmpxNeqF32,
|
||||
VCmpxNltF32,
|
||||
VCmpFI32,
|
||||
VCmpLtI32,
|
||||
VCmpEqI32,
|
||||
VCmpLeI32,
|
||||
VCmpGtI32,
|
||||
VCmpNeI32,
|
||||
VCmpGeI32,
|
||||
VCmpTI32,
|
||||
VCmpClassF32,
|
||||
VCmpLtI16,
|
||||
VCmpEqI16,
|
||||
VCmpLeI16,
|
||||
VCmpGtI16,
|
||||
VCmpNeI16,
|
||||
VCmpGeI16,
|
||||
VCmpLtF16,
|
||||
VCmpEqF16,
|
||||
VCmpLeF16,
|
||||
VCmpGtF16,
|
||||
VCmpLgF16,
|
||||
VCmpGeF16,
|
||||
VCmpNeqF16,
|
||||
VCmpxLtF16,
|
||||
VCmpxEqF16,
|
||||
VCmpxLeF16,
|
||||
VCmpxGtF16,
|
||||
VCmpxGeF16,
|
||||
VCmpxNeqF16,
|
||||
VCmpxNltF16,
|
||||
VCmpxLtI32,
|
||||
VCmpxEqI32,
|
||||
VCmpxLeI32,
|
||||
VCmpxGtI32,
|
||||
VCmpxNeI32,
|
||||
VCmpxGeI32,
|
||||
VCmpLtU16,
|
||||
VCmpEqU16,
|
||||
VCmpLeU16,
|
||||
VCmpGtU16,
|
||||
VCmpNeU16,
|
||||
VCmpGeU16,
|
||||
VCmpFU32,
|
||||
VCmpLtU32,
|
||||
VCmpEqU32,
|
||||
VCmpLeU32,
|
||||
VCmpGtU32,
|
||||
VCmpNeU32,
|
||||
VCmpGeU32,
|
||||
VCmpTU32,
|
||||
VCmpNeU64,
|
||||
VCmpxLtU32,
|
||||
VCmpxEqU32,
|
||||
VCmpxLeU32,
|
||||
VCmpxGtU32,
|
||||
VCmpxNeU32,
|
||||
VCmpxGeU32,
|
||||
|
||||
SLoadDword,
|
||||
SLoadDwordx2,
|
||||
SLoadDwordx4,
|
||||
SLoadDwordx8,
|
||||
SLoadDwordx16,
|
||||
SBufferLoadDword,
|
||||
SBufferLoadDwordx2,
|
||||
SBufferLoadDwordx4,
|
||||
SBufferLoadDwordx8,
|
||||
SBufferLoadDwordx16,
|
||||
BufferLoadFormatX,
|
||||
BufferLoadFormatXy,
|
||||
BufferLoadFormatXyz,
|
||||
BufferLoadFormatXyzw,
|
||||
BufferStoreFormatX,
|
||||
BufferStoreFormatXy,
|
||||
BufferStoreFormatXyz,
|
||||
BufferStoreFormatXyzw,
|
||||
BufferLoadUbyte,
|
||||
BufferLoadSbyte,
|
||||
BufferLoadUshort,
|
||||
BufferLoadSshort,
|
||||
BufferLoadDword,
|
||||
BufferLoadDwordx2,
|
||||
BufferLoadDwordx3,
|
||||
BufferLoadDwordx4,
|
||||
BufferStoreByte,
|
||||
BufferStoreShort,
|
||||
BufferStoreDword,
|
||||
BufferStoreDwordx2,
|
||||
BufferStoreDwordx3,
|
||||
BufferStoreDwordx4,
|
||||
TBufferLoadFormatX,
|
||||
TBufferLoadFormatXy,
|
||||
TBufferLoadFormatXyz,
|
||||
TBufferLoadFormatXyzw,
|
||||
TBufferStoreFormatX,
|
||||
TBufferStoreFormatXy,
|
||||
TBufferStoreFormatXyz,
|
||||
TBufferStoreFormatXyzw,
|
||||
BufferAtomicSwap,
|
||||
BufferAtomicAdd,
|
||||
BufferAtomicSub,
|
||||
BufferAtomicSMin,
|
||||
BufferAtomicUMin,
|
||||
BufferAtomicSMax,
|
||||
BufferAtomicUMax,
|
||||
BufferAtomicAnd,
|
||||
BufferAtomicOr,
|
||||
BufferAtomicXor,
|
||||
FlatLoadUbyte,
|
||||
FlatLoadSbyte,
|
||||
FlatLoadUshort,
|
||||
FlatLoadSshort,
|
||||
FlatLoadDword,
|
||||
FlatLoadDwordx2,
|
||||
FlatLoadDwordx3,
|
||||
FlatLoadDwordx4,
|
||||
FlatStoreByte,
|
||||
FlatStoreShort,
|
||||
FlatStoreDword,
|
||||
FlatStoreDwordx2,
|
||||
FlatStoreDwordx3,
|
||||
FlatStoreDwordx4,
|
||||
DsAddU32,
|
||||
DsAddRtnU32,
|
||||
DsSubU32,
|
||||
DsSubRtnU32,
|
||||
DsMinI32,
|
||||
DsMinRtnI32,
|
||||
DsMaxI32,
|
||||
DsMaxRtnI32,
|
||||
DsMinU32,
|
||||
DsMinRtnU32,
|
||||
DsMaxU32,
|
||||
DsMaxRtnU32,
|
||||
DsAndB32,
|
||||
DsAndRtnB32,
|
||||
DsOrB32,
|
||||
DsOrRtnB32,
|
||||
DsXorB32,
|
||||
DsXorRtnB32,
|
||||
DsWrxchgRtnB32,
|
||||
DsMinF32,
|
||||
DsMaxF32,
|
||||
DsSwizzleB32,
|
||||
DsConsume,
|
||||
DsAppend,
|
||||
DsReadSbyte,
|
||||
DsReadUbyte,
|
||||
DsReadSshort,
|
||||
DsReadUshort,
|
||||
DsRead2B32,
|
||||
DsReadB32,
|
||||
DsReadB64,
|
||||
DsRead2B64,
|
||||
DsRead2St64B64,
|
||||
DsReadB96,
|
||||
DsReadB128,
|
||||
DsWriteByte,
|
||||
DsWriteShort,
|
||||
DsWrite2B32,
|
||||
DsWrite2St64B32,
|
||||
DsWrite2B64,
|
||||
DsWrite2St64B64,
|
||||
DsWriteB32,
|
||||
DsWriteB64,
|
||||
DsWriteB96,
|
||||
DsWriteB128,
|
||||
DsWriteAddtidB32,
|
||||
DsReadAddtidB32,
|
||||
ImageGetResinfo,
|
||||
ImageGetLod,
|
||||
ImageLoad,
|
||||
ImageLoadMip,
|
||||
ImageStore,
|
||||
ImageStoreMip,
|
||||
ImageAtomicAdd,
|
||||
ImageAtomicUMin,
|
||||
ImageAtomicUMax,
|
||||
ImageAtomicAnd,
|
||||
ImageAtomicOr,
|
||||
ImageAtomicXor,
|
||||
ImageSample,
|
||||
ImageGather4Lz,
|
||||
ImageGather4C,
|
||||
ImageGather4CLz,
|
||||
ImageGather4LzO,
|
||||
ImageGather4CO,
|
||||
ImageGather4CLzO,
|
||||
ImageGather4H,
|
||||
VInterpP1F32,
|
||||
VInterpP2F32,
|
||||
VInterpMovF32,
|
||||
|
||||
SNop,
|
||||
SWaitcnt,
|
||||
SBarrier,
|
||||
SBranch,
|
||||
SCbranchScc0,
|
||||
SCbranchScc1,
|
||||
SCbranchVccz,
|
||||
SCbranchVccnz,
|
||||
SCbranchExecz,
|
||||
SCbranchExecnz,
|
||||
SSendmsg,
|
||||
SSetregB32,
|
||||
SSleep,
|
||||
STtraceData,
|
||||
SInstPrefetch,
|
||||
SEndpgm,
|
||||
Exp
|
||||
};
|
||||
|
||||
enum class OperandKind {
|
||||
Unknown,
|
||||
LiteralConstant,
|
||||
IntegerInlineConstant,
|
||||
FloatInlineConstant,
|
||||
Sgpr,
|
||||
VccLo,
|
||||
VccHi,
|
||||
VccZ,
|
||||
ExecLo,
|
||||
ExecHi,
|
||||
ExecZ,
|
||||
Scc,
|
||||
M0,
|
||||
PopsExitingWaveId,
|
||||
Null,
|
||||
Vgpr,
|
||||
};
|
||||
|
||||
enum ImageSampleFlag : uint32_t {
|
||||
ImageSampleFlagLod = 1u << 0u,
|
||||
ImageSampleFlagBias = 1u << 1u,
|
||||
ImageSampleFlagDerivative = 1u << 2u,
|
||||
ImageSampleFlagCompare = 1u << 3u,
|
||||
ImageSampleFlagOffset = 1u << 4u,
|
||||
ImageSampleFlagLevelZero = 1u << 5u,
|
||||
ImageSampleFlagLodClamp = 1u << 6u,
|
||||
ImageSampleFlagA16 = 1u << 7u,
|
||||
ImageSampleFlagCd = 1u << 8u,
|
||||
ImageSampleFlagGatherHorizontal = 1u << 9u,
|
||||
};
|
||||
|
||||
enum class ImageDimension : uint32_t {
|
||||
Unknown,
|
||||
Dim2D,
|
||||
Dim3D,
|
||||
Dim2DArray,
|
||||
};
|
||||
|
||||
constexpr uint32_t MaxInstructionRawWords = 5u;
|
||||
constexpr uint32_t MaxImageNsaAddressComponents = 12u;
|
||||
|
||||
struct Operand {
|
||||
OperandKind kind = OperandKind::Unknown;
|
||||
uint32_t value = 0;
|
||||
int32_t signed_val = 0;
|
||||
float float_val = 0.0f;
|
||||
uint32_t reg = 0;
|
||||
uint32_t sdwa_sel = 6;
|
||||
uint32_t sdwa_dst_unused = 0;
|
||||
uint32_t omod = 0;
|
||||
uint32_t dpp_ctrl = 0;
|
||||
uint32_t dpp_row_mask = 0xf;
|
||||
uint32_t dpp_bank_mask = 0xf;
|
||||
bool sdwa_sext = false;
|
||||
bool dpp_fetch_inactive = false;
|
||||
bool dpp_bound_ctrl = false;
|
||||
bool op_sel = false;
|
||||
bool op_sel_hi = false;
|
||||
bool negate = false;
|
||||
bool negate_hi = false;
|
||||
bool absolute = false;
|
||||
bool clamp = false;
|
||||
bool dpp = false;
|
||||
};
|
||||
|
||||
struct Instruction {
|
||||
uint32_t pc = 0;
|
||||
uint32_t word = 0;
|
||||
uint32_t word_count = 1;
|
||||
uint32_t raw[MaxInstructionRawWords] = {};
|
||||
uint32_t raw_count = 1;
|
||||
Family family = Family::Unknown;
|
||||
uint32_t opcode_id = 0;
|
||||
Opcode opcode = Opcode::Unknown;
|
||||
Operand dst;
|
||||
Operand dst2;
|
||||
Operand src0;
|
||||
Operand src1;
|
||||
Operand src2;
|
||||
Operand src3;
|
||||
uint32_t src_count = 0;
|
||||
uint32_t offset = 0;
|
||||
uint32_t secondary_offset = 0;
|
||||
uint32_t dmask = 0;
|
||||
uint32_t data_dwords = 1;
|
||||
uint32_t data_bits = 32;
|
||||
uint32_t data_format = 0;
|
||||
uint32_t number_format = 0;
|
||||
uint32_t image_sample_flags = 0;
|
||||
ImageDimension image_dimension = ImageDimension::Unknown;
|
||||
uint32_t image_address_components = 0;
|
||||
uint32_t image_nsa_dwords = 0;
|
||||
uint32_t image_nsa_addr[MaxImageNsaAddressComponents] = {};
|
||||
uint32_t memory_segment = 0;
|
||||
bool data_signed = false;
|
||||
bool typed = false;
|
||||
bool formatted = false;
|
||||
bool gds = false;
|
||||
bool glc = false;
|
||||
bool slc = false;
|
||||
bool idxen = false;
|
||||
bool offen = false;
|
||||
int32_t branch_offset = 0;
|
||||
uint32_t branch_target = 0;
|
||||
struct {
|
||||
uint32_t target = 0;
|
||||
uint32_t en = 0;
|
||||
bool done = false;
|
||||
bool compr = false;
|
||||
bool vm = false;
|
||||
} exp;
|
||||
std::string unsupported_reason;
|
||||
};
|
||||
|
||||
struct Program {
|
||||
std::span<const uint32_t> code;
|
||||
std::vector<Instruction> instructions;
|
||||
};
|
||||
|
||||
bool DecodeProgram(std::span<const uint32_t> code, Program* program, std::string* error);
|
||||
|
||||
bool DecodeScalarSource(uint32_t code, uint32_t pc, Operand* operand, std::string* error);
|
||||
bool DecodeScalarDestination(uint32_t code, uint32_t pc, Operand* operand, std::string* error);
|
||||
bool DecodeVectorGpr(uint32_t reg, Operand* operand, std::string* error);
|
||||
bool ReadLiteralOperands(std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error);
|
||||
void SetRawWords(Instruction* inst, std::span<const uint32_t> code, uint32_t word_index,
|
||||
uint32_t word_count);
|
||||
void SetUnsupported(Instruction* inst, Family family, uint32_t opcode_id, const char* reason);
|
||||
std::string FamilyToString(Family family);
|
||||
std::string OpcodeToString(Opcode opcode);
|
||||
std::string OperandToString(const Operand& operand);
|
||||
std::string InstructionToString(const Instruction& inst);
|
||||
std::string ProgramToString(const Program& program);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERDECODER_H_ */
|
||||
@@ -0,0 +1,763 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERIR_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERIR_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/shader/recompiler/ShaderCFG.h"
|
||||
#include "graphics/shader/recompiler/ShaderDecoder.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
enum class Opcode {
|
||||
ControlNop,
|
||||
Waitcnt,
|
||||
Barrier,
|
||||
Sendmsg,
|
||||
TtraceData,
|
||||
InstPrefetch,
|
||||
MoveU32,
|
||||
MoveF32Bits,
|
||||
MoveRelDestU32,
|
||||
MoveRelSourceU32,
|
||||
MoveU64,
|
||||
WqmB64,
|
||||
SaveexecB32,
|
||||
SaveexecB64,
|
||||
ReadFirstLaneU32,
|
||||
ReadLaneU32,
|
||||
WriteLaneU32,
|
||||
Permlane16B32,
|
||||
Permlanex16B32,
|
||||
AbsI32,
|
||||
IAddU32,
|
||||
IAddCarryU32,
|
||||
ISubBorrowU32,
|
||||
ScalarAddCarryU32,
|
||||
ScalarSubBorrowU32,
|
||||
ScalarSubBorrowCarryU32,
|
||||
ScalarSignedAddOverflowI32,
|
||||
ScalarSignedSubOverflowI32,
|
||||
ScalarShiftLeftAddCarryU32,
|
||||
ISubU32,
|
||||
IMulU32,
|
||||
UMulHighU32,
|
||||
SMulHighI32,
|
||||
IMadI24U32,
|
||||
UMadU24U32,
|
||||
UMadU64U32,
|
||||
SadU32,
|
||||
IAdd3U32,
|
||||
IMulI24U32,
|
||||
UMulU24U32,
|
||||
IMinI32,
|
||||
IMaxI32,
|
||||
IMin3I32,
|
||||
IMax3I32,
|
||||
IMed3I32,
|
||||
UMinU32,
|
||||
UMaxU32,
|
||||
UMin3U32,
|
||||
UMax3U32,
|
||||
UMed3U32,
|
||||
BitwiseAndU32,
|
||||
BitwiseAndU64,
|
||||
BitwiseAndNotU32,
|
||||
BitwiseAndNotU64,
|
||||
BitwiseOrU32,
|
||||
BitwiseOrU64,
|
||||
BitwiseOrNotU32,
|
||||
BitwiseOrNotU64,
|
||||
BitwiseAndOrU32,
|
||||
BitwiseOr3U32,
|
||||
BitwiseXorU32,
|
||||
BitwiseXorU64,
|
||||
BitwiseXor3U32,
|
||||
BitwiseNandU32,
|
||||
BitwiseNandU64,
|
||||
BitwiseNorU32,
|
||||
BitwiseNorU64,
|
||||
BitwiseXnorU32,
|
||||
BitwiseXnorU64,
|
||||
BitwiseNotU32,
|
||||
BitwiseNotU64,
|
||||
BitClearU32,
|
||||
BitSetU32,
|
||||
BitReverseU32,
|
||||
BitCountU32,
|
||||
BitCountU64,
|
||||
BitReplicateB64B32,
|
||||
BitCountAddU32,
|
||||
MaskedBitCountLowU32,
|
||||
MaskedBitCountHighU32,
|
||||
FindLsbU32,
|
||||
FindMsbFromHighU32,
|
||||
FindMsbFromHighU64,
|
||||
BitFieldMaskU32,
|
||||
BitFieldMaskU64,
|
||||
BitFieldExtractU32,
|
||||
BitFieldExtractU64,
|
||||
BitFieldExtract3U32,
|
||||
BitFieldExtract3I32,
|
||||
BitFieldInsertSelectU32,
|
||||
BitCompare0B32,
|
||||
BitCompare1B32,
|
||||
AlignBitU32,
|
||||
ShiftLeftAddU32,
|
||||
AddShiftLeftU32,
|
||||
XorAddU32,
|
||||
ShiftLeftOrU32,
|
||||
ShiftLeftLogicalU32,
|
||||
ShiftLeftLogicalU64,
|
||||
ShiftLeftLogicalU16,
|
||||
ShiftRightLogicalU32,
|
||||
ShiftRightLogicalU64,
|
||||
ShiftRightLogicalU16,
|
||||
ShiftRightArithmeticI32,
|
||||
ShiftRightArithmeticI16,
|
||||
SelectU32,
|
||||
SelectMaskU32,
|
||||
SelectF32Bits,
|
||||
SelectMaskF32Bits,
|
||||
SelectU64,
|
||||
PackLowLowU16,
|
||||
PackLowHighU16,
|
||||
PackHighHighU16,
|
||||
CompareFalse,
|
||||
CompareTrue,
|
||||
CompareEqU32,
|
||||
CompareNeU32,
|
||||
CompareGtU32,
|
||||
CompareGeU32,
|
||||
CompareLtU32,
|
||||
CompareLeU32,
|
||||
CompareNeU64,
|
||||
CompareMaskEqU32,
|
||||
CompareMaskNeU32,
|
||||
CompareMaskGtU32,
|
||||
CompareMaskGeU32,
|
||||
CompareMaskLtU32,
|
||||
CompareMaskLeU32,
|
||||
CompareEqI32,
|
||||
CompareNeI32,
|
||||
CompareGtI32,
|
||||
CompareGeI32,
|
||||
CompareLtI32,
|
||||
CompareLeI32,
|
||||
CompareEqI16,
|
||||
CompareNeI16,
|
||||
CompareGtI16,
|
||||
CompareGeI16,
|
||||
CompareLtI16,
|
||||
CompareLeI16,
|
||||
CompareMaskEqI32,
|
||||
CompareMaskNeI32,
|
||||
CompareMaskGtI32,
|
||||
CompareMaskGeI32,
|
||||
CompareMaskLtI32,
|
||||
CompareMaskLeI32,
|
||||
CompareEqU16,
|
||||
CompareNeU16,
|
||||
CompareGtU16,
|
||||
CompareGeU16,
|
||||
CompareLtU16,
|
||||
CompareLeU16,
|
||||
CompareEqF32,
|
||||
CompareNeF32,
|
||||
CompareGtF32,
|
||||
CompareGeF32,
|
||||
CompareLtF32,
|
||||
CompareLeF32,
|
||||
CompareOrderedF32,
|
||||
CompareUnorderedF32,
|
||||
CompareUnordEqF32,
|
||||
CompareUnordNeF32,
|
||||
CompareUnordGtF32,
|
||||
CompareUnordGeF32,
|
||||
CompareUnordLtF32,
|
||||
CompareUnordLeF32,
|
||||
CompareClassF32,
|
||||
CompareEqF16,
|
||||
CompareNeF16,
|
||||
CompareGtF16,
|
||||
CompareGeF16,
|
||||
CompareLtF16,
|
||||
CompareLeF16,
|
||||
CompareUnordNeF16,
|
||||
CompareMaskEqF16,
|
||||
CompareMaskNeF16,
|
||||
CompareMaskGtF16,
|
||||
CompareMaskGeF16,
|
||||
CompareMaskLtF16,
|
||||
CompareMaskLeF16,
|
||||
CompareMaskUnordNeF16,
|
||||
CompareMaskUnordGeF16,
|
||||
CompareMaskEqF32,
|
||||
CompareMaskNeF32,
|
||||
CompareMaskGtF32,
|
||||
CompareMaskGeF32,
|
||||
CompareMaskLtF32,
|
||||
CompareMaskLeF32,
|
||||
CompareMaskUnordEqF32,
|
||||
CompareMaskUnordNeF32,
|
||||
CompareMaskUnordGtF32,
|
||||
CompareMaskUnordGeF32,
|
||||
CompareMaskUnordLtF32,
|
||||
CompareMaskUnordLeF32,
|
||||
ConvertByteU32ToF32,
|
||||
ConvertU32ToF32,
|
||||
ConvertI32ToF32,
|
||||
ConvertF32ToU32,
|
||||
ConvertF32ToI32,
|
||||
ConvertF32ToF16,
|
||||
ConvertF16ToF32,
|
||||
ConvertU16ToF16,
|
||||
ConvertF16ToU16,
|
||||
ConvertI16ToF16,
|
||||
ConvertF16ToI16,
|
||||
ConvertRoundPlusInfF32ToI32,
|
||||
ConvertFloorF32ToI32,
|
||||
ConvertI4ToOffsetF32,
|
||||
LdexpF32,
|
||||
PackF32ToF16Rtz,
|
||||
PackSnorm2x16F32,
|
||||
PackUnorm2x16F32,
|
||||
PackU16U32,
|
||||
PackU8F32,
|
||||
PackB32F16,
|
||||
PackedMadI16,
|
||||
PackedMulLoU16,
|
||||
PackedAddI16,
|
||||
PackedSubI16,
|
||||
PackedLshlrevB16,
|
||||
PackedLshrrevB16,
|
||||
PackedAshrrevI16,
|
||||
PackedMaxI16,
|
||||
PackedMinI16,
|
||||
PackedMadU16,
|
||||
PackedAddU16,
|
||||
PackedSubU16,
|
||||
PackedMaxU16,
|
||||
PackedMinU16,
|
||||
PackedAddF16,
|
||||
PackedMulF16,
|
||||
PackedMinF16,
|
||||
PackedMaxF16,
|
||||
PackedFmaF16,
|
||||
AddF16,
|
||||
SubF16,
|
||||
MulF16,
|
||||
MinF16,
|
||||
MaxF16,
|
||||
FmaF16,
|
||||
MadMixF16,
|
||||
IAddU16,
|
||||
ISubI16,
|
||||
IMinI16,
|
||||
IMaxI16,
|
||||
UMinU16,
|
||||
UMaxU16,
|
||||
RcpF32,
|
||||
FractF32,
|
||||
TruncF32,
|
||||
CeilF32,
|
||||
RoundEvenF32,
|
||||
FloorF32,
|
||||
Exp2F32,
|
||||
Log2F32,
|
||||
InverseSqrtF32,
|
||||
SqrtF32,
|
||||
RcpF16,
|
||||
SqrtF16,
|
||||
InverseSqrtF16,
|
||||
Log2F16,
|
||||
Exp2F16,
|
||||
FloorF16,
|
||||
CeilF16,
|
||||
TruncF16,
|
||||
RoundEvenF16,
|
||||
SinF32,
|
||||
CosF32,
|
||||
CubeIdF32,
|
||||
CubeScF32,
|
||||
CubeTcF32,
|
||||
CubeMaF32,
|
||||
FAddF32,
|
||||
FSubF32,
|
||||
FMulF32,
|
||||
FMinF32,
|
||||
FMaxF32,
|
||||
FMadF32,
|
||||
Dot2AccF32F16,
|
||||
FMin3F32,
|
||||
FMax3F32,
|
||||
FMed3F32,
|
||||
Min3F16,
|
||||
Max3F16,
|
||||
Med3F16,
|
||||
LoadSrtDword,
|
||||
SLoadDword,
|
||||
SBufferLoadDword,
|
||||
BufferLoadUbyte,
|
||||
BufferLoadSbyte,
|
||||
BufferLoadUshort,
|
||||
BufferLoadSshort,
|
||||
BufferLoadDword,
|
||||
BufferStoreByte,
|
||||
BufferStoreShort,
|
||||
BufferStoreDword,
|
||||
AtomicSwapU32,
|
||||
AtomicAddU32,
|
||||
AtomicSubU32,
|
||||
AtomicSMinI32,
|
||||
AtomicUMinU32,
|
||||
AtomicSMaxI32,
|
||||
AtomicUMaxU32,
|
||||
AtomicAndU32,
|
||||
AtomicOrU32,
|
||||
AtomicXorU32,
|
||||
FlatLoadUbyte,
|
||||
FlatLoadSbyte,
|
||||
FlatLoadUshort,
|
||||
FlatLoadSshort,
|
||||
FlatLoadDword,
|
||||
FlatStoreByte,
|
||||
FlatStoreShort,
|
||||
FlatStoreDword,
|
||||
DsReadUbyte,
|
||||
DsReadSbyte,
|
||||
DsReadUshort,
|
||||
DsReadSshort,
|
||||
DsReadB32,
|
||||
DsWriteByte,
|
||||
DsWriteShort,
|
||||
DsWriteB32,
|
||||
DsMinF32,
|
||||
DsMaxF32,
|
||||
DsSwizzleB32,
|
||||
DsConsume,
|
||||
DsAppend,
|
||||
DsWriteAddtidB32,
|
||||
DsReadAddtidB32,
|
||||
ImageGetResinfo,
|
||||
ImageGetLod,
|
||||
ImageLoad,
|
||||
ImageStore,
|
||||
ImageSample,
|
||||
ImageGather4,
|
||||
LoadInputF32,
|
||||
Export,
|
||||
};
|
||||
|
||||
enum class RegisterFile { Scalar, Vector, Vcc, Exec, Scc, M0 };
|
||||
|
||||
struct Register {
|
||||
RegisterFile file = RegisterFile::Scalar;
|
||||
uint32_t index = 0;
|
||||
|
||||
bool operator==(const Register& other) const {
|
||||
return file == other.file && index == other.index;
|
||||
}
|
||||
};
|
||||
|
||||
enum class OperandKind { Register, ImmediateU32, PcRelativeU32, Null };
|
||||
|
||||
struct Operand {
|
||||
OperandKind kind = OperandKind::Null;
|
||||
Register reg;
|
||||
uint32_t imm = 0;
|
||||
bool sext_64 = false;
|
||||
uint32_t sdwa_sel = 6;
|
||||
uint32_t omod = 0;
|
||||
bool sdwa_sext = false;
|
||||
bool op_sel = false;
|
||||
bool op_sel_hi = false;
|
||||
bool negate = false;
|
||||
bool negate_hi = false;
|
||||
bool absolute = false;
|
||||
bool clamp = false;
|
||||
uint32_t dpp_ctrl = 0;
|
||||
uint32_t dpp_row_mask = 0xf;
|
||||
uint32_t dpp_bank_mask = 0xf;
|
||||
bool dpp_fetch_inactive = false;
|
||||
bool dpp_bound_ctrl = false;
|
||||
bool dpp = false;
|
||||
|
||||
bool operator==(const Operand& other) const = default;
|
||||
};
|
||||
|
||||
enum class ResourceKind {
|
||||
None,
|
||||
ScalarBuffer,
|
||||
Buffer,
|
||||
Flat,
|
||||
Global,
|
||||
Scratch,
|
||||
Lds,
|
||||
Gds,
|
||||
Image,
|
||||
ImageUint,
|
||||
StorageImage,
|
||||
StorageImageUint,
|
||||
Sampler
|
||||
};
|
||||
|
||||
struct MemoryInfo {
|
||||
ResourceKind kind = ResourceKind::None;
|
||||
uint32_t resource = 0;
|
||||
uint32_t sampler = 0;
|
||||
uint32_t offset = 0;
|
||||
uint32_t secondary_offset = 0;
|
||||
uint32_t dmask = 0;
|
||||
uint32_t data_dwords = 1;
|
||||
uint32_t data_bits = 32;
|
||||
uint32_t component_index = 0;
|
||||
uint32_t component_count = 1;
|
||||
uint32_t data_format = 0;
|
||||
uint32_t number_format = 0;
|
||||
uint32_t image_sample_flags = 0;
|
||||
Decoder::ImageDimension image_dimension = Decoder::ImageDimension::Unknown;
|
||||
uint32_t image_address_components = 0;
|
||||
uint32_t image_nsa_dwords = 0;
|
||||
uint32_t image_nsa_addr[Decoder::MaxImageNsaAddressComponents] = {};
|
||||
uint32_t memory_segment = 0;
|
||||
// Dense resource indices are assigned by resource tracking. These source IDs refer to the
|
||||
// exact scalar definitions reaching this instruction before that patching step.
|
||||
uint32_t resource_source = 0;
|
||||
uint32_t sampler_source = 0;
|
||||
bool data_signed = false;
|
||||
bool typed = false;
|
||||
bool formatted = false;
|
||||
bool image_has_mip = false;
|
||||
bool glc = false;
|
||||
bool slc = false;
|
||||
bool idxen = false;
|
||||
bool offen = false;
|
||||
|
||||
bool operator==(const MemoryInfo& other) const = default;
|
||||
};
|
||||
|
||||
enum class ExportTargetKind { Unknown, Null, Position, Primitive, Parameter, Mrt, MrtZ };
|
||||
|
||||
struct ExportInfo {
|
||||
ExportTargetKind kind = ExportTargetKind::Unknown;
|
||||
uint32_t target = 0;
|
||||
uint32_t index = 0;
|
||||
uint32_t en = 0;
|
||||
bool done = false;
|
||||
bool compr = false;
|
||||
bool vm = false;
|
||||
|
||||
bool operator==(const ExportInfo& other) const = default;
|
||||
};
|
||||
|
||||
struct InputInfo {
|
||||
uint32_t attr = 0;
|
||||
uint32_t chan = 0;
|
||||
|
||||
bool operator==(const InputInfo& other) const = default;
|
||||
};
|
||||
|
||||
enum class SaveexecMode { And, Orn2, Andn1 };
|
||||
|
||||
struct Instruction {
|
||||
uint32_t pc = 0;
|
||||
Opcode op = Opcode::MoveU32;
|
||||
Operand dst;
|
||||
Operand dst2;
|
||||
Operand src[4];
|
||||
uint32_t src_count = 0;
|
||||
uint32_t scalar_sources[4] = {};
|
||||
uint32_t scalar_value = 0;
|
||||
MemoryInfo memory;
|
||||
ExportInfo export_info;
|
||||
InputInfo input_info;
|
||||
SaveexecMode saveexec_mode = SaveexecMode::And;
|
||||
|
||||
bool operator==(const Instruction& other) const = default;
|
||||
};
|
||||
|
||||
struct BasicBlock {
|
||||
uint32_t id = 0;
|
||||
uint32_t start_pc = 0;
|
||||
uint32_t end_pc = 0;
|
||||
uint32_t inst_begin = 0;
|
||||
uint32_t inst_end = 0;
|
||||
std::vector<uint32_t> predecessors;
|
||||
std::vector<uint32_t> successors;
|
||||
CFG::Terminator terminator;
|
||||
std::vector<Instruction> instructions;
|
||||
};
|
||||
|
||||
enum class ScalarValueOp {
|
||||
Undefined,
|
||||
Unknown,
|
||||
UserData,
|
||||
Constant,
|
||||
PcRelativeLow,
|
||||
PcRelativeHigh,
|
||||
Add,
|
||||
AddCarry,
|
||||
Carry,
|
||||
Sub,
|
||||
SubBorrow,
|
||||
Borrow,
|
||||
Mul,
|
||||
And,
|
||||
AndNot,
|
||||
Or,
|
||||
OrNot,
|
||||
Xor,
|
||||
Not,
|
||||
ShiftLeft,
|
||||
ShiftRight,
|
||||
ShiftRightArithmetic,
|
||||
BitFieldMaskU32,
|
||||
BitFieldMaskU64Low,
|
||||
BitFieldMaskU64High,
|
||||
Add3,
|
||||
ShiftLeftAdd,
|
||||
ShiftLeftAddCarry,
|
||||
AddShiftLeft,
|
||||
XorAdd,
|
||||
ShiftLeftOr,
|
||||
ReadConst,
|
||||
ReadConstBuffer,
|
||||
Phi,
|
||||
};
|
||||
|
||||
struct ScalarValue {
|
||||
ScalarValueOp op = ScalarValueOp::Undefined;
|
||||
uint32_t pc = 0;
|
||||
uint32_t imm = 0;
|
||||
std::array<uint32_t, 6> args = {};
|
||||
std::vector<uint32_t> phi_args;
|
||||
|
||||
bool operator==(const ScalarValue& other) const = default;
|
||||
};
|
||||
|
||||
struct DescriptorValue {
|
||||
std::array<uint32_t, 8> dwords = {};
|
||||
uint32_t dword_count = 0;
|
||||
|
||||
bool operator==(const DescriptorValue& other) const {
|
||||
return dword_count == other.dword_count && dwords == other.dwords;
|
||||
}
|
||||
};
|
||||
|
||||
struct ScalarProvenance {
|
||||
static constexpr uint32_t Undefined = 0;
|
||||
static constexpr uint32_t Unknown = 1;
|
||||
|
||||
std::vector<ScalarValue> values;
|
||||
std::vector<DescriptorValue> descriptors;
|
||||
|
||||
bool operator==(const ScalarProvenance& other) const = default;
|
||||
};
|
||||
|
||||
struct SrtRead {
|
||||
uint32_t value = 0;
|
||||
uint32_t flat_offset = 0;
|
||||
uint32_t use_pc = 0;
|
||||
|
||||
bool operator==(const SrtRead& other) const = default;
|
||||
};
|
||||
|
||||
struct SrtPlan {
|
||||
std::vector<SrtRead> reads;
|
||||
std::vector<uint32_t> dynamic_reads;
|
||||
std::vector<uint32_t> dynamic_sources;
|
||||
|
||||
bool operator==(const SrtPlan& other) const = default;
|
||||
};
|
||||
|
||||
struct BufferResource {
|
||||
uint32_t source = 0;
|
||||
uint32_t first_use_pc = 0;
|
||||
uint32_t max_byte_extent = 0;
|
||||
uint32_t packed_stride = 0;
|
||||
uint32_t descriptor_format = 0;
|
||||
bool read = false;
|
||||
bool written = false;
|
||||
bool atomic = false;
|
||||
bool formatted = false;
|
||||
bool scalar = false;
|
||||
|
||||
bool operator==(const BufferResource& other) const = default;
|
||||
};
|
||||
|
||||
enum class ImageMipMode { None, DynamicStorage };
|
||||
|
||||
constexpr uint32_t StorageImageIdentitySwizzle = 0x00000facu;
|
||||
|
||||
struct ImageResource {
|
||||
uint32_t source = 0;
|
||||
uint32_t first_use_pc = 0;
|
||||
ResourceKind kind = ResourceKind::None;
|
||||
Decoder::ImageDimension dimension = Decoder::ImageDimension::Unknown;
|
||||
ImageMipMode mip_mode = ImageMipMode::None;
|
||||
uint32_t storage_swizzle = StorageImageIdentitySwizzle;
|
||||
bool read = false;
|
||||
bool written = false;
|
||||
bool atomic = false;
|
||||
bool depth_compare = false;
|
||||
|
||||
bool operator==(const ImageResource& other) const = default;
|
||||
};
|
||||
|
||||
struct SamplerResource {
|
||||
uint32_t source = 0;
|
||||
uint32_t first_use_pc = 0;
|
||||
|
||||
bool operator==(const SamplerResource& other) const = default;
|
||||
};
|
||||
|
||||
struct SampledResourcePair {
|
||||
uint32_t image = 0;
|
||||
uint32_t sampler = 0;
|
||||
uint32_t first_use_pc = 0;
|
||||
|
||||
bool operator==(const SampledResourcePair& other) const = default;
|
||||
};
|
||||
|
||||
struct AddressResource {
|
||||
uint32_t source = 0;
|
||||
uint32_t first_use_pc = 0;
|
||||
ResourceKind kind = ResourceKind::Flat;
|
||||
int32_t min_offset = 0;
|
||||
uint64_t specialized_base = 0;
|
||||
bool read = false;
|
||||
bool written = false;
|
||||
bool atomic = false;
|
||||
|
||||
bool operator==(const AddressResource& other) const = default;
|
||||
};
|
||||
|
||||
enum class StageInputKind {
|
||||
VertexIndex,
|
||||
InstanceIndex,
|
||||
FragCoord,
|
||||
FrontFacing,
|
||||
WorkgroupId,
|
||||
LocalInvocationId,
|
||||
LocalInvocationIndex,
|
||||
GlobalInvocationId,
|
||||
Parameter,
|
||||
};
|
||||
|
||||
enum class StageOutputKind { Position, Parameter, Mrt, Depth, SampleMask };
|
||||
|
||||
struct StageInput {
|
||||
StageInputKind kind = StageInputKind::VertexIndex;
|
||||
uint32_t location = 0;
|
||||
uint32_t component_count = 1;
|
||||
std::string debug_name;
|
||||
|
||||
bool operator==(const StageInput& other) const = default;
|
||||
};
|
||||
|
||||
struct StageOutput {
|
||||
StageOutputKind kind = StageOutputKind::Parameter;
|
||||
uint32_t index = 0;
|
||||
uint32_t location = 0;
|
||||
std::string debug_name;
|
||||
|
||||
bool operator==(const StageOutput& other) const = default;
|
||||
};
|
||||
|
||||
enum class DescriptorBindingKind {
|
||||
Buffers,
|
||||
Sampled2D,
|
||||
Sampled2DArray,
|
||||
Sampled3D,
|
||||
SampledUint2D,
|
||||
SampledUint2DArray,
|
||||
SampledUint3D,
|
||||
Storage2D,
|
||||
Storage2DArray,
|
||||
Storage3D,
|
||||
StorageUint2D,
|
||||
StorageUint2DArray,
|
||||
StorageUint3D,
|
||||
Samplers,
|
||||
Gds,
|
||||
AddressMemory,
|
||||
FlattenedSrt,
|
||||
UserData,
|
||||
Count,
|
||||
};
|
||||
|
||||
struct DescriptorBinding {
|
||||
DescriptorBindingKind kind = DescriptorBindingKind::Buffers;
|
||||
uint32_t binding = 0;
|
||||
std::vector<uint32_t> resources;
|
||||
|
||||
bool operator==(const DescriptorBinding& other) const = default;
|
||||
};
|
||||
|
||||
struct BindingLayout {
|
||||
uint32_t descriptor_set = 0;
|
||||
uint32_t push_constant_offset = 0;
|
||||
uint32_t push_constant_size = 0;
|
||||
std::vector<uint32_t> user_data_registers;
|
||||
std::vector<DescriptorBinding> descriptors;
|
||||
|
||||
bool operator==(const BindingLayout& other) const = default;
|
||||
};
|
||||
|
||||
struct ShaderInfo {
|
||||
static constexpr uint32_t MaxBuffers = 32;
|
||||
static constexpr uint32_t MaxAddresses = 32;
|
||||
static constexpr uint32_t MaxImages = 32;
|
||||
static constexpr uint32_t MaxSamplers = 32;
|
||||
static constexpr uint32_t MaxSampledPairs = 64;
|
||||
|
||||
std::vector<BufferResource> buffers;
|
||||
std::vector<AddressResource> addresses;
|
||||
std::vector<ImageResource> images;
|
||||
std::vector<SamplerResource> samplers;
|
||||
std::vector<SampledResourcePair> sampled_pairs;
|
||||
std::vector<StageInput> inputs;
|
||||
std::vector<StageOutput> outputs;
|
||||
int32_t vertex_offset_sgpr = -1;
|
||||
|
||||
bool operator==(const ShaderInfo& other) const = default;
|
||||
};
|
||||
|
||||
struct Program {
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
ShaderLaneMaskMode lane_mask_mode = ShaderLaneMaskMode::NativeWave;
|
||||
uint64_t shader_hash = 0;
|
||||
uint32_t wave_size = 64;
|
||||
uint32_t user_data_base = 0;
|
||||
uint32_t user_data_count = 64;
|
||||
bool dispatcher_fallback = false;
|
||||
CFG::FailureKind cfg_failure_kind = CFG::FailureKind::None;
|
||||
std::string fallback_reason;
|
||||
std::vector<BasicBlock> blocks;
|
||||
ScalarProvenance provenance;
|
||||
SrtPlan srt;
|
||||
bool srt_plan_complete = false;
|
||||
bool srt_patching_complete = false;
|
||||
ShaderInfo info;
|
||||
bool resource_tracking_complete = false;
|
||||
bool shader_info_complete = false;
|
||||
BindingLayout bindings;
|
||||
bool binding_layout_complete = false;
|
||||
};
|
||||
|
||||
bool LowerProgram(const Decoder::Program& decoded, const CFG::Graph& cfg, ShaderType stage,
|
||||
uint32_t wave_size, Program* program, std::string* error);
|
||||
|
||||
std::string RegisterToString(Register reg);
|
||||
std::string OperandToString(const Operand& operand);
|
||||
std::string ExportTargetKindToString(ExportTargetKind kind);
|
||||
std::string InstructionToString(const Instruction& inst);
|
||||
std::string ProgramToString(const Program& program);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERIR_H_ */
|
||||
@@ -0,0 +1,225 @@
|
||||
#include "graphics/shader/recompiler/ShaderInfoCollection.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
namespace {
|
||||
|
||||
bool HasInput(const ShaderInfo& info, StageInputKind kind, uint32_t location) {
|
||||
return std::any_of(info.inputs.begin(), info.inputs.end(), [=](const auto& input) {
|
||||
return input.kind == kind && input.location == location;
|
||||
});
|
||||
}
|
||||
|
||||
void AddInput(ShaderInfo* info, StageInputKind kind, uint32_t location, uint32_t components,
|
||||
std::string name) {
|
||||
if (!HasInput(*info, kind, location)) {
|
||||
info->inputs.push_back({kind, location, components, std::move(name)});
|
||||
}
|
||||
}
|
||||
|
||||
bool HasOutput(const ShaderInfo& info, StageOutputKind kind, uint32_t index) {
|
||||
return std::any_of(info.outputs.begin(), info.outputs.end(), [=](const auto& output) {
|
||||
return output.kind == kind && output.index == index;
|
||||
});
|
||||
}
|
||||
|
||||
void AddOutput(ShaderInfo* info, StageOutputKind kind, uint32_t index, uint32_t location,
|
||||
std::string name) {
|
||||
if (!HasOutput(*info, kind, index)) {
|
||||
info->outputs.push_back({kind, index, location, std::move(name)});
|
||||
}
|
||||
}
|
||||
|
||||
bool NeedsLocalInvocationIndex(const Program& program) {
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (inst.op == Opcode::DsWriteAddtidB32 || inst.op == Opcode::DsReadAddtidB32) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ValidateOptions(const Program& program, const ShaderInfoOptions& options, std::string* error) {
|
||||
auto Fail = [&](const char* message) {
|
||||
if (error != nullptr) {
|
||||
*error = message;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
switch (program.stage) {
|
||||
case ShaderType::Vertex:
|
||||
if (options.vertex == nullptr) {
|
||||
return Fail("vertex shader info requires vertex metadata");
|
||||
}
|
||||
if (options.vertex->resources_num < 0 ||
|
||||
options.vertex->resources_num > ShaderVertexInputInfo::RES_MAX) {
|
||||
return Fail("vertex resource count is out of range");
|
||||
}
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (inst.op == Opcode::LoadInputF32 &&
|
||||
(inst.input_info.chan >= 4 ||
|
||||
inst.input_info.attr >=
|
||||
static_cast<uint32_t>(options.vertex->resources_num))) {
|
||||
return Fail("vertex input reference is out of range");
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case ShaderType::Pixel:
|
||||
if (options.pixel == nullptr) {
|
||||
return Fail("pixel shader info requires pixel metadata");
|
||||
}
|
||||
return options.pixel->input_num <= std::size(options.pixel->interpolator_settings) ||
|
||||
Fail("pixel input count is out of range");
|
||||
case ShaderType::Compute:
|
||||
if (options.compute == nullptr) {
|
||||
return Fail("compute shader info requires compute metadata");
|
||||
}
|
||||
return (options.compute->thread_ids_num >= 0 && options.compute->thread_ids_num <= 3) ||
|
||||
Fail("compute thread ID count is out of range");
|
||||
default: return Fail("unsupported shader stage for info collection");
|
||||
}
|
||||
}
|
||||
|
||||
void CollectVertexInputs(const Program& program, const ShaderVertexInputInfo* vertex,
|
||||
ShaderInfo* info) {
|
||||
AddInput(info, StageInputKind::VertexIndex, 0, 1, "gl_VertexIndex");
|
||||
AddInput(info, StageInputKind::InstanceIndex, 0, 1, "gl_InstanceIndex");
|
||||
if (vertex == nullptr) {
|
||||
return;
|
||||
}
|
||||
uint32_t used_components[ShaderVertexInputInfo::RES_MAX] = {};
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (inst.op == Opcode::LoadInputF32 &&
|
||||
inst.input_info.attr < static_cast<uint32_t>(vertex->resources_num) &&
|
||||
inst.input_info.attr < ShaderVertexInputInfo::RES_MAX) {
|
||||
used_components[inst.input_info.attr] =
|
||||
std::max(used_components[inst.input_info.attr], inst.input_info.chan + 1u);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint32_t attr = 0; attr < static_cast<uint32_t>(vertex->resources_num) &&
|
||||
attr < ShaderVertexInputInfo::RES_MAX;
|
||||
attr++) {
|
||||
if (used_components[attr] != 0) {
|
||||
AddInput(info, StageInputKind::Parameter, attr, used_components[attr],
|
||||
fmt::format("in_attr_{}", attr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CollectPixelInputs(const ShaderPixelInputInfo* pixel, ShaderInfo* info) {
|
||||
if (pixel == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (pixel->HasPositionInput()) {
|
||||
AddInput(info, StageInputKind::FragCoord, 0, 4, "gl_FragCoord");
|
||||
}
|
||||
if (pixel->ps_front_face) {
|
||||
AddInput(info, StageInputKind::FrontFacing, 0, 1, "gl_FrontFacing");
|
||||
}
|
||||
for (uint32_t input = 0; input < pixel->input_num; input++) {
|
||||
AddInput(info, StageInputKind::Parameter, input, 4, fmt::format("in_param_{}", input));
|
||||
}
|
||||
}
|
||||
|
||||
void CollectComputeInputs(const Program& program, const ShaderComputeInputInfo* compute,
|
||||
ShaderInfo* info) {
|
||||
if (compute != nullptr) {
|
||||
if (compute->group_id[0] || compute->group_id[1] || compute->group_id[2]) {
|
||||
AddInput(info, StageInputKind::WorkgroupId, 0, 3, "gl_WorkGroupID");
|
||||
}
|
||||
if (compute->thread_ids_num > 0) {
|
||||
AddInput(info, StageInputKind::LocalInvocationId, 0, 3, "gl_LocalInvocationID");
|
||||
}
|
||||
if (compute->thread_ids_num > 0 || compute->tg_size_en) {
|
||||
AddInput(info, StageInputKind::LocalInvocationIndex, 0, 1, "gl_LocalInvocationIndex");
|
||||
}
|
||||
if (compute->dispatch_thread_dimensions) {
|
||||
AddInput(info, StageInputKind::GlobalInvocationId, 0, 3, "gl_GlobalInvocationID");
|
||||
}
|
||||
}
|
||||
if (NeedsLocalInvocationIndex(program)) {
|
||||
AddInput(info, StageInputKind::LocalInvocationIndex, 0, 1, "gl_LocalInvocationIndex");
|
||||
}
|
||||
}
|
||||
|
||||
void CollectOutputs(const Program& program, const ShaderPixelInputInfo* pixel, ShaderInfo* info) {
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (inst.op != Opcode::Export) {
|
||||
continue;
|
||||
}
|
||||
if (inst.export_info.kind == ExportTargetKind::MrtZ) {
|
||||
if (pixel != nullptr && (inst.export_info.en & 0x1u) != 0 &&
|
||||
pixel->ps_depth_export_enable) {
|
||||
AddOutput(info, StageOutputKind::Depth, 0, 0, "gl_FragDepth");
|
||||
}
|
||||
if (pixel != nullptr && (inst.export_info.en & 0x4u) != 0 &&
|
||||
pixel->ps_sample_mask_export_enable) {
|
||||
AddOutput(info, StageOutputKind::SampleMask, 0, 0, "gl_SampleMask");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (inst.export_info.en == 0) {
|
||||
continue;
|
||||
}
|
||||
switch (inst.export_info.kind) {
|
||||
case ExportTargetKind::Position:
|
||||
AddOutput(info, StageOutputKind::Position, inst.export_info.index, 0,
|
||||
"out_position");
|
||||
break;
|
||||
case ExportTargetKind::Parameter:
|
||||
AddOutput(info, StageOutputKind::Parameter, inst.export_info.index,
|
||||
inst.export_info.index,
|
||||
fmt::format("out_param_{}", inst.export_info.index));
|
||||
break;
|
||||
case ExportTargetKind::Mrt:
|
||||
AddOutput(info, StageOutputKind::Mrt, inst.export_info.index,
|
||||
inst.export_info.index,
|
||||
fmt::format("out_mrt_{}", inst.export_info.index));
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool CollectShaderInfo(Program* program, const ShaderInfoOptions& options, std::string* error) {
|
||||
if (program == nullptr || !program->resource_tracking_complete ||
|
||||
program->shader_info_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = program == nullptr ? "invalid shader info program"
|
||||
: !program->resource_tracking_complete ? "shader resources were not tracked"
|
||||
: "shader info already collected";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!ValidateOptions(*program, options, error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto next = program->info;
|
||||
next.inputs.clear();
|
||||
next.outputs.clear();
|
||||
switch (program->stage) {
|
||||
case ShaderType::Vertex: CollectVertexInputs(*program, options.vertex, &next); break;
|
||||
case ShaderType::Pixel: CollectPixelInputs(options.pixel, &next); break;
|
||||
case ShaderType::Compute: CollectComputeInputs(*program, options.compute, &next); break;
|
||||
default: return false;
|
||||
}
|
||||
CollectOutputs(*program, options.pixel, &next);
|
||||
program->info = std::move(next);
|
||||
program->shader_info_complete = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERINFOCOLLECTION_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERINFOCOLLECTION_H_
|
||||
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
struct ShaderInfoOptions {
|
||||
const ShaderVertexInputInfo* vertex = nullptr;
|
||||
const ShaderPixelInputInfo* pixel = nullptr;
|
||||
const ShaderComputeInputInfo* compute = nullptr;
|
||||
};
|
||||
|
||||
// Completes the immutable shader interface after resource tracking. On failure Program::info and
|
||||
// all completion state remain unchanged.
|
||||
bool CollectShaderInfo(Program* program, const ShaderInfoOptions& options, std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERINFOCOLLECTION_H_ */
|
||||
@@ -0,0 +1,872 @@
|
||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "graphics/shader/recompiler/BindingLayout.h"
|
||||
#include "graphics/shader/recompiler/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ResourceTracking.h"
|
||||
#include "graphics/shader/recompiler/ScalarProvenance.h"
|
||||
#include "graphics/shader/recompiler/ShaderCFG.h"
|
||||
#include "graphics/shader/recompiler/ShaderDecoder.h"
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ShaderInfoCollection.h"
|
||||
#include "graphics/shader/recompiler/SpirvEmitter.h"
|
||||
#include "graphics/shader/recompiler/SrtPatcher.h"
|
||||
#include "graphics/shader/recompiler/SrtWalker.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <fmt/format.h>
|
||||
#include <map>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler {
|
||||
|
||||
namespace {
|
||||
|
||||
bool ReadZeroMemory(void*, uint64_t, uint32_t* value) {
|
||||
if (value == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*value = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* GetDumpLabel(const CompileOptions& options) {
|
||||
return options.dump_label != nullptr ? options.dump_label : "ShaderRecompiler";
|
||||
}
|
||||
|
||||
std::string MakeIrDump(const CFG::Graph& cfg, const IR::Program& ir) {
|
||||
std::string dump = "CFG:\n";
|
||||
dump += CFG::GraphToString(cfg);
|
||||
dump += "\nIR:\n";
|
||||
dump += IR::ProgramToString(ir);
|
||||
return dump;
|
||||
}
|
||||
|
||||
const char* StageName(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Compute: return "CS";
|
||||
case ShaderType::Vertex: return "VS";
|
||||
case ShaderType::Pixel: return "PS";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
std::string FormatCfgFailure(const CFG::Graph& cfg, const CompileOptions& options,
|
||||
const std::string& reason) {
|
||||
const auto block_id = cfg.failure_block != UINT32_MAX ? cfg.failure_block : cfg.entry_block;
|
||||
return CFG::FormatBlockDiagnostic(cfg, block_id, StageName(options.stage), reason);
|
||||
}
|
||||
|
||||
bool InstructionMaySplitSpirvBlock(const IR::Instruction& inst) {
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::SLoadDword:
|
||||
case IR::Opcode::SBufferLoadDword:
|
||||
case IR::Opcode::BufferLoadUbyte:
|
||||
case IR::Opcode::BufferLoadSbyte:
|
||||
case IR::Opcode::BufferLoadUshort:
|
||||
case IR::Opcode::BufferLoadSshort:
|
||||
case IR::Opcode::BufferLoadDword:
|
||||
case IR::Opcode::BufferStoreByte:
|
||||
case IR::Opcode::BufferStoreShort:
|
||||
case IR::Opcode::BufferStoreDword:
|
||||
case IR::Opcode::AtomicSwapU32:
|
||||
case IR::Opcode::AtomicAddU32:
|
||||
case IR::Opcode::AtomicSubU32:
|
||||
case IR::Opcode::AtomicSMinI32:
|
||||
case IR::Opcode::AtomicUMinU32:
|
||||
case IR::Opcode::AtomicSMaxI32:
|
||||
case IR::Opcode::AtomicUMaxU32:
|
||||
case IR::Opcode::AtomicAndU32:
|
||||
case IR::Opcode::AtomicOrU32:
|
||||
case IR::Opcode::AtomicXorU32:
|
||||
case IR::Opcode::FlatLoadUbyte:
|
||||
case IR::Opcode::FlatLoadSbyte:
|
||||
case IR::Opcode::FlatLoadUshort:
|
||||
case IR::Opcode::FlatLoadSshort:
|
||||
case IR::Opcode::FlatLoadDword:
|
||||
case IR::Opcode::FlatStoreByte:
|
||||
case IR::Opcode::FlatStoreShort:
|
||||
case IR::Opcode::FlatStoreDword:
|
||||
case IR::Opcode::DsMinF32:
|
||||
case IR::Opcode::DsMaxF32:
|
||||
case IR::Opcode::DsWriteByte:
|
||||
case IR::Opcode::DsWriteShort:
|
||||
case IR::Opcode::DsWriteB32:
|
||||
case IR::Opcode::DsWriteAddtidB32:
|
||||
case IR::Opcode::DsAppend:
|
||||
case IR::Opcode::DsConsume:
|
||||
case IR::Opcode::ImageStore:
|
||||
case IR::Opcode::Export: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool NeedsDispatcherForStructuredLoopHeader(const IR::Program& ir, std::string* reason) {
|
||||
for (const auto& block: ir.blocks) {
|
||||
if (!block.terminator.loop_header) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (!InstructionMaySplitSpirvBlock(inst)) {
|
||||
continue;
|
||||
}
|
||||
if (reason != nullptr) {
|
||||
*reason = fmt::format("loop header block {} contains an instruction whose SPIR-V "
|
||||
"lowering emits internal control flow: {}",
|
||||
block.id, IR::InstructionToString(inst).c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
enum class EmbeddedFetchValueType {
|
||||
Unknown,
|
||||
Constant,
|
||||
AttribTable,
|
||||
Attrib,
|
||||
BufferTable,
|
||||
Buffer,
|
||||
Index
|
||||
};
|
||||
|
||||
struct EmbeddedFetchSgprInfo {
|
||||
EmbeddedFetchValueType type = EmbeddedFetchValueType::Unknown;
|
||||
int attrib_id = 0;
|
||||
uint32_t value = 0;
|
||||
std::vector<uint32_t> prolog_loads;
|
||||
};
|
||||
|
||||
struct EmbeddedFetchVgprInfo {
|
||||
EmbeddedFetchValueType type = EmbeddedFetchValueType::Unknown;
|
||||
};
|
||||
|
||||
using EmbeddedFetchVectorLanes = std::map<uint64_t, EmbeddedFetchSgprInfo>;
|
||||
|
||||
uint64_t EmbeddedFetchVectorLaneKey(uint32_t reg, uint32_t lane) {
|
||||
return (static_cast<uint64_t>(reg) << 32u) | lane;
|
||||
}
|
||||
|
||||
uint32_t EmbeddedFetchLane(uint32_t lane, uint32_t wave_size) {
|
||||
return wave_size == 32 || wave_size == 64 ? lane % wave_size : lane;
|
||||
}
|
||||
|
||||
void ClearEmbeddedFetchVectorLanes(EmbeddedFetchVectorLanes* lanes, uint32_t reg) {
|
||||
const auto first = lanes->lower_bound(EmbeddedFetchVectorLaneKey(reg, 0));
|
||||
const auto last = lanes->lower_bound(EmbeddedFetchVectorLaneKey(reg + 1u, 0));
|
||||
lanes->erase(first, last);
|
||||
}
|
||||
|
||||
struct EmbeddedFetchLoad {
|
||||
uint32_t pc = 0;
|
||||
int attrib_id = -1;
|
||||
uint32_t components = 0;
|
||||
std::vector<uint32_t> prolog_loads;
|
||||
};
|
||||
|
||||
struct EmbeddedFetchData {
|
||||
std::vector<EmbeddedFetchLoad> loads;
|
||||
int32_t vertex_offset_sgpr = -1;
|
||||
};
|
||||
|
||||
bool IsDecodedSgpr(const Decoder::Operand& op) {
|
||||
return op.kind == Decoder::OperandKind::Sgpr || op.kind == Decoder::OperandKind::VccLo ||
|
||||
op.kind == Decoder::OperandKind::VccHi;
|
||||
}
|
||||
|
||||
uint32_t DecodedSgprReg(const Decoder::Operand& op) {
|
||||
return op.kind == Decoder::OperandKind::VccLo
|
||||
? 106u
|
||||
: (op.kind == Decoder::OperandKind::VccHi ? 107u : op.reg);
|
||||
}
|
||||
|
||||
bool IsDecodedVgpr(const Decoder::Operand& op) {
|
||||
return op.kind == Decoder::OperandKind::Vgpr;
|
||||
}
|
||||
|
||||
uint32_t DecodedDstSize(const Decoder::Instruction& inst) {
|
||||
return std::max(inst.data_dwords, 1u);
|
||||
}
|
||||
|
||||
uint32_t EmbeddedFetchDstSize(const Decoder::Instruction& inst) {
|
||||
return inst.opcode == Decoder::Opcode::VMadU64U32 ? 2u : DecodedDstSize(inst);
|
||||
}
|
||||
|
||||
bool EmbeddedFetchHasBranch(Decoder::Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Decoder::Opcode::SSetpcB64:
|
||||
case Decoder::Opcode::SBranch:
|
||||
case Decoder::Opcode::SCbranchScc0:
|
||||
case Decoder::Opcode::SCbranchScc1:
|
||||
case Decoder::Opcode::SCbranchVccz:
|
||||
case Decoder::Opcode::SCbranchVccnz:
|
||||
case Decoder::Opcode::SCbranchExecz:
|
||||
case Decoder::Opcode::SCbranchExecnz: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ClearEmbeddedFetchSgprs(std::array<EmbeddedFetchSgprInfo, 108>& sgprs,
|
||||
const Decoder::Operand& dst, uint32_t size) {
|
||||
if (!IsDecodedSgpr(dst)) {
|
||||
return;
|
||||
}
|
||||
const auto register_id = DecodedSgprReg(dst);
|
||||
for (uint32_t i = 0; i < size && register_id + i < sgprs.size(); i++) {
|
||||
sgprs[register_id + i] = {};
|
||||
}
|
||||
}
|
||||
|
||||
bool TryDecodedOperandConstant(const std::array<EmbeddedFetchSgprInfo, 108>& sgprs,
|
||||
const Decoder::Operand& op, uint32_t& value) {
|
||||
switch (op.kind) {
|
||||
case Decoder::OperandKind::LiteralConstant:
|
||||
case Decoder::OperandKind::IntegerInlineConstant:
|
||||
case Decoder::OperandKind::FloatInlineConstant: value = op.value; return true;
|
||||
case Decoder::OperandKind::Null: value = 0; return true;
|
||||
default: break;
|
||||
}
|
||||
if (IsDecodedSgpr(op) && DecodedSgprReg(op) < sgprs.size() &&
|
||||
sgprs[DecodedSgprReg(op)].type == EmbeddedFetchValueType::Constant) {
|
||||
value = sgprs[DecodedSgprReg(op)].value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryDecodedSmemOffset(const std::array<EmbeddedFetchSgprInfo, 108>& sgprs,
|
||||
const Decoder::Instruction& inst, uint32_t& raw_offset) {
|
||||
uint32_t base = 0;
|
||||
if (!TryDecodedOperandConstant(sgprs, inst.src1, base)) {
|
||||
return false;
|
||||
}
|
||||
const auto value = static_cast<uint64_t>(base) + inst.offset;
|
||||
if (value > 0xffffffffull) {
|
||||
return false;
|
||||
}
|
||||
raw_offset = static_cast<uint32_t>(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsEmbeddedFetchSLoad(const Decoder::Instruction& inst) {
|
||||
switch (inst.opcode) {
|
||||
case Decoder::Opcode::SLoadDword:
|
||||
case Decoder::Opcode::SLoadDwordx2:
|
||||
case Decoder::Opcode::SLoadDwordx4:
|
||||
case Decoder::Opcode::SLoadDwordx8:
|
||||
case Decoder::Opcode::SLoadDwordx16: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsEmbeddedFetchBufferLoad(const Decoder::Instruction& inst) {
|
||||
switch (inst.opcode) {
|
||||
case Decoder::Opcode::BufferLoadFormatX:
|
||||
case Decoder::Opcode::BufferLoadFormatXy:
|
||||
case Decoder::Opcode::BufferLoadFormatXyz:
|
||||
case Decoder::Opcode::BufferLoadFormatXyzw: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsEmbeddedFetchAttribPropagationAlu(const Decoder::Instruction& inst) {
|
||||
switch (inst.opcode) {
|
||||
case Decoder::Opcode::SBfeU32:
|
||||
case Decoder::Opcode::SAndB32:
|
||||
case Decoder::Opcode::SAddI32:
|
||||
case Decoder::Opcode::SAddU32:
|
||||
case Decoder::Opcode::SLshlB32: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
int BufferTableAttribFromOffset(uint32_t raw_offset, int dword) {
|
||||
return static_cast<int>((raw_offset + static_cast<uint32_t>(dword) * 4u) / 16u);
|
||||
}
|
||||
|
||||
void AppendUniquePcs(std::vector<uint32_t>* dst, const std::vector<uint32_t>& src) {
|
||||
EXIT_IF(dst == nullptr);
|
||||
dst->reserve(dst->size() + src.size());
|
||||
for (auto pc: src) {
|
||||
if (std::find(dst->begin(), dst->end(), pc) == dst->end()) {
|
||||
dst->push_back(pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded,
|
||||
const ShaderVertexInputInfo* input_info,
|
||||
uint32_t user_data_base, uint32_t user_data_count,
|
||||
uint32_t wave_size) {
|
||||
EmbeddedFetchData data;
|
||||
if (input_info == nullptr || !input_info->fetch_embedded) {
|
||||
return data;
|
||||
}
|
||||
data.loads.reserve(input_info->resources_num);
|
||||
int32_t offset_candidate = -1;
|
||||
bool offset_conflict = false;
|
||||
|
||||
const int shift_regs = 8;
|
||||
const int attrib_reg = input_info->fetch_attrib_reg + shift_regs;
|
||||
const int buffer_reg = input_info->fetch_buffer_reg + shift_regs;
|
||||
|
||||
std::array<EmbeddedFetchSgprInfo, 108> sgprs {};
|
||||
std::array<EmbeddedFetchVgprInfo, 256> vgprs {};
|
||||
EmbeddedFetchVectorLanes vector_lanes;
|
||||
const bool track_vector_lanes =
|
||||
std::none_of(decoded.instructions.begin(), decoded.instructions.end(),
|
||||
[](const auto& inst) { return EmbeddedFetchHasBranch(inst.opcode); });
|
||||
|
||||
if (attrib_reg >= 0 && attrib_reg < static_cast<int>(sgprs.size())) {
|
||||
sgprs[attrib_reg].type = EmbeddedFetchValueType::AttribTable;
|
||||
}
|
||||
if (attrib_reg + 1 >= 0 && attrib_reg + 1 < static_cast<int>(sgprs.size())) {
|
||||
sgprs[attrib_reg + 1].type = EmbeddedFetchValueType::AttribTable;
|
||||
}
|
||||
if (buffer_reg >= 0 && buffer_reg < static_cast<int>(sgprs.size())) {
|
||||
sgprs[buffer_reg].type = EmbeddedFetchValueType::BufferTable;
|
||||
}
|
||||
if (buffer_reg + 1 >= 0 && buffer_reg + 1 < static_cast<int>(sgprs.size())) {
|
||||
sgprs[buffer_reg + 1].type = EmbeddedFetchValueType::BufferTable;
|
||||
}
|
||||
|
||||
for (const auto& inst: decoded.instructions) {
|
||||
if (data.loads.empty() && inst.opcode == Decoder::Opcode::VAddI32 &&
|
||||
IsDecodedVgpr(inst.dst) && inst.dst.reg == 0 && IsDecodedSgpr(inst.src0) &&
|
||||
IsDecodedVgpr(inst.src1) && inst.src1.reg == 0) {
|
||||
const auto reg = DecodedSgprReg(inst.src0);
|
||||
if (reg >= user_data_base && reg - user_data_base < user_data_count) {
|
||||
if (offset_candidate >= 0 && offset_candidate != static_cast<int32_t>(reg)) {
|
||||
offset_conflict = true;
|
||||
} else {
|
||||
offset_candidate = static_cast<int32_t>(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (inst.opcode) {
|
||||
case Decoder::Opcode::VWritelaneB32: {
|
||||
uint32_t lane = 0;
|
||||
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgprs.size()) {
|
||||
vgprs[inst.dst.reg] = {};
|
||||
}
|
||||
if (track_vector_lanes && IsDecodedVgpr(inst.dst) && IsDecodedSgpr(inst.src0) &&
|
||||
DecodedSgprReg(inst.src0) < sgprs.size() &&
|
||||
TryDecodedOperandConstant(sgprs, inst.src1, lane)) {
|
||||
vector_lanes[EmbeddedFetchVectorLaneKey(inst.dst.reg,
|
||||
EmbeddedFetchLane(lane, wave_size))] =
|
||||
sgprs[DecodedSgprReg(inst.src0)];
|
||||
} else if (IsDecodedVgpr(inst.dst)) {
|
||||
ClearEmbeddedFetchVectorLanes(&vector_lanes, inst.dst.reg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Decoder::Opcode::VReadlaneB32: {
|
||||
uint32_t lane = 0;
|
||||
if (track_vector_lanes && IsDecodedSgpr(inst.dst) &&
|
||||
DecodedSgprReg(inst.dst) < sgprs.size() && IsDecodedVgpr(inst.src0) &&
|
||||
TryDecodedOperandConstant(sgprs, inst.src1, lane)) {
|
||||
const auto found = vector_lanes.find(EmbeddedFetchVectorLaneKey(
|
||||
inst.src0.reg, EmbeddedFetchLane(lane, wave_size)));
|
||||
sgprs[DecodedSgprReg(inst.dst)] =
|
||||
found != vector_lanes.end() ? found->second : EmbeddedFetchSgprInfo {};
|
||||
} else if (IsDecodedSgpr(inst.dst)) {
|
||||
ClearEmbeddedFetchSgprs(sgprs, inst.dst, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Decoder::Opcode::SMovB32:
|
||||
if (IsDecodedSgpr(inst.dst) && IsDecodedSgpr(inst.src0) &&
|
||||
DecodedSgprReg(inst.src0) < sgprs.size()) {
|
||||
sgprs[DecodedSgprReg(inst.dst)] = sgprs[DecodedSgprReg(inst.src0)];
|
||||
} else if (IsDecodedSgpr(inst.dst)) {
|
||||
uint32_t value = 0;
|
||||
if (TryDecodedOperandConstant(sgprs, inst.src0, value)) {
|
||||
auto& dst = sgprs[DecodedSgprReg(inst.dst)];
|
||||
dst.type = EmbeddedFetchValueType::Constant;
|
||||
dst.value = value;
|
||||
dst.prolog_loads.clear();
|
||||
} else {
|
||||
ClearEmbeddedFetchSgprs(sgprs, inst.dst, 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Decoder::Opcode::SMovkI32:
|
||||
if (IsDecodedSgpr(inst.dst)) {
|
||||
auto& dst = sgprs[DecodedSgprReg(inst.dst)];
|
||||
dst.type = EmbeddedFetchValueType::Constant;
|
||||
dst.value = inst.src0.value;
|
||||
dst.prolog_loads.clear();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (IsEmbeddedFetchSLoad(inst)) {
|
||||
if (IsDecodedSgpr(inst.src0) && DecodedSgprReg(inst.src0) < sgprs.size() &&
|
||||
sgprs[DecodedSgprReg(inst.src0)].type ==
|
||||
EmbeddedFetchValueType::AttribTable) {
|
||||
uint32_t raw_offset = 0;
|
||||
if (TryDecodedSmemOffset(sgprs, inst, raw_offset)) {
|
||||
const auto register_id = DecodedSgprReg(inst.dst);
|
||||
const int index = static_cast<int>(raw_offset / 4u);
|
||||
for (uint32_t i = 0;
|
||||
i < DecodedDstSize(inst) && register_id + i < sgprs.size(); i++) {
|
||||
auto& dst = sgprs[register_id + i];
|
||||
dst.type = EmbeddedFetchValueType::Attrib;
|
||||
dst.attrib_id = index + static_cast<int>(i);
|
||||
dst.prolog_loads = {inst.pc};
|
||||
}
|
||||
} else {
|
||||
ClearEmbeddedFetchSgprs(sgprs, inst.dst, DecodedDstSize(inst));
|
||||
}
|
||||
} else if (IsDecodedSgpr(inst.src0) &&
|
||||
DecodedSgprReg(inst.src0) < sgprs.size() &&
|
||||
sgprs[DecodedSgprReg(inst.src0)].type ==
|
||||
EmbeddedFetchValueType::BufferTable) {
|
||||
const auto register_id = DecodedSgprReg(inst.dst);
|
||||
uint32_t raw_offset = 0;
|
||||
if (TryDecodedSmemOffset(sgprs, inst, raw_offset)) {
|
||||
for (uint32_t i = 0;
|
||||
i < DecodedDstSize(inst) && register_id + i < sgprs.size(); i++) {
|
||||
auto& dst = sgprs[register_id + i];
|
||||
dst.type = EmbeddedFetchValueType::Buffer;
|
||||
dst.attrib_id =
|
||||
BufferTableAttribFromOffset(raw_offset, static_cast<int>(i));
|
||||
dst.prolog_loads = {inst.pc};
|
||||
}
|
||||
} else if (IsDecodedSgpr(inst.src1) &&
|
||||
DecodedSgprReg(inst.src1) < sgprs.size() &&
|
||||
sgprs[DecodedSgprReg(inst.src1)].type ==
|
||||
EmbeddedFetchValueType::Attrib &&
|
||||
(inst.offset & 0x3u) == 0) {
|
||||
for (uint32_t i = 0;
|
||||
i < DecodedDstSize(inst) && register_id + i < sgprs.size(); i++) {
|
||||
auto& dst = sgprs[register_id + i];
|
||||
dst.type = EmbeddedFetchValueType::Buffer;
|
||||
dst.attrib_id = sgprs[DecodedSgprReg(inst.src1)].attrib_id;
|
||||
dst.prolog_loads = sgprs[DecodedSgprReg(inst.src1)].prolog_loads;
|
||||
dst.prolog_loads.push_back(inst.pc);
|
||||
}
|
||||
} else {
|
||||
ClearEmbeddedFetchSgprs(sgprs, inst.dst, DecodedDstSize(inst));
|
||||
}
|
||||
} else {
|
||||
ClearEmbeddedFetchSgprs(sgprs, inst.dst, DecodedDstSize(inst));
|
||||
}
|
||||
} else if (inst.opcode == Decoder::Opcode::VCndmaskB32) {
|
||||
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgprs.size()) {
|
||||
ClearEmbeddedFetchVectorLanes(&vector_lanes, inst.dst.reg);
|
||||
}
|
||||
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgprs.size() &&
|
||||
IsDecodedVgpr(inst.src0) && inst.src0.reg == 8 &&
|
||||
IsDecodedVgpr(inst.src1) && inst.src1.reg == 5) {
|
||||
vgprs[inst.dst.reg].type = EmbeddedFetchValueType::Index;
|
||||
}
|
||||
} else if (IsEmbeddedFetchAttribPropagationAlu(inst)) {
|
||||
if (IsDecodedSgpr(inst.dst) && IsDecodedSgpr(inst.src0) &&
|
||||
DecodedSgprReg(inst.src0) < sgprs.size() &&
|
||||
sgprs[DecodedSgprReg(inst.src0)].type == EmbeddedFetchValueType::Attrib) {
|
||||
sgprs[DecodedSgprReg(inst.dst)] = sgprs[DecodedSgprReg(inst.src0)];
|
||||
} else if (IsDecodedSgpr(inst.dst)) {
|
||||
uint32_t src0 = 0;
|
||||
uint32_t src1 = 0;
|
||||
if (TryDecodedOperandConstant(sgprs, inst.src0, src0) &&
|
||||
TryDecodedOperandConstant(sgprs, inst.src1, src1)) {
|
||||
auto& dst = sgprs[DecodedSgprReg(inst.dst)];
|
||||
dst.type = EmbeddedFetchValueType::Constant;
|
||||
switch (inst.opcode) {
|
||||
case Decoder::Opcode::SAndB32: dst.value = src0 & src1; break;
|
||||
case Decoder::Opcode::SLshlB32:
|
||||
dst.value = src0 << (src1 & 31u);
|
||||
break;
|
||||
case Decoder::Opcode::SBfeU32:
|
||||
dst.value = src0 >> (src1 & 31u);
|
||||
break;
|
||||
default: dst.value = src0 + src1; break;
|
||||
}
|
||||
dst.prolog_loads.clear();
|
||||
} else {
|
||||
ClearEmbeddedFetchSgprs(sgprs, inst.dst, 1);
|
||||
}
|
||||
}
|
||||
} else if (IsEmbeddedFetchBufferLoad(inst)) {
|
||||
if (IsDecodedVgpr(inst.src0) && inst.src0.reg < vgprs.size() &&
|
||||
vgprs[inst.src0.reg].type == EmbeddedFetchValueType::Index &&
|
||||
IsDecodedSgpr(inst.src1) && DecodedSgprReg(inst.src1) < sgprs.size() &&
|
||||
sgprs[DecodedSgprReg(inst.src1)].type == EmbeddedFetchValueType::Buffer) {
|
||||
const auto& buffer = sgprs[DecodedSgprReg(inst.src1)];
|
||||
EmbeddedFetchLoad load;
|
||||
load.pc = inst.pc;
|
||||
load.attrib_id = buffer.attrib_id;
|
||||
load.components = DecodedDstSize(inst);
|
||||
load.prolog_loads = buffer.prolog_loads;
|
||||
if (data.loads.empty() && !offset_conflict) {
|
||||
data.vertex_offset_sgpr = offset_candidate;
|
||||
}
|
||||
data.loads.push_back(load);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (inst.opcode == Decoder::Opcode::VMovreldB32) {
|
||||
vector_lanes.clear();
|
||||
} else if (inst.opcode != Decoder::Opcode::VWritelaneB32 && IsDecodedVgpr(inst.dst)) {
|
||||
for (uint32_t i = 0; i < EmbeddedFetchDstSize(inst) && inst.dst.reg + i < vgprs.size();
|
||||
i++) {
|
||||
ClearEmbeddedFetchVectorLanes(&vector_lanes, inst.dst.reg + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
const EmbeddedFetchLoad* FindEmbeddedFetchLoad(const std::vector<EmbeddedFetchLoad>& loads,
|
||||
uint32_t pc) {
|
||||
for (const auto& load: loads) {
|
||||
if (load.pc == pc) {
|
||||
return &load;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool EmbeddedFetchPcInList(const std::vector<uint32_t>& pcs, uint32_t pc) {
|
||||
return std::find(pcs.begin(), pcs.end(), pc) != pcs.end();
|
||||
}
|
||||
|
||||
bool IsIrFetchPrologLoad(const IR::Instruction& inst) {
|
||||
return inst.op == IR::Opcode::SLoadDword || inst.op == IR::Opcode::SBufferLoadDword;
|
||||
}
|
||||
|
||||
int ResolveEmbeddedFetchResource(const ShaderVertexInputInfo* input_info,
|
||||
const EmbeddedFetchLoad& load) {
|
||||
if (input_info == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
if (load.attrib_id >= 0 && load.attrib_id < input_info->resources_num &&
|
||||
input_info->resources_dst[load.attrib_id].attr_id == load.attrib_id) {
|
||||
return load.attrib_id;
|
||||
}
|
||||
for (int i = 0; i < input_info->resources_num; i++) {
|
||||
const auto& dst = input_info->resources_dst[i];
|
||||
if (dst.attr_id == load.attrib_id &&
|
||||
load.components <= static_cast<uint32_t>(std::max(dst.registers_num, 1))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < input_info->resources_num; i++) {
|
||||
if (input_info->resources_dst[i].attr_id == load.attrib_id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t RewriteEmbeddedVertexFetches(IR::Program* ir, const ShaderVertexInputInfo* input_info,
|
||||
const std::vector<EmbeddedFetchLoad>& loads) {
|
||||
if (ir == nullptr || input_info == nullptr || loads.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> prolog_pcs;
|
||||
prolog_pcs.reserve(loads.size());
|
||||
for (const auto& load: loads) {
|
||||
AppendUniquePcs(&prolog_pcs, load.prolog_loads);
|
||||
}
|
||||
|
||||
auto* mutable_input_info = const_cast<ShaderVertexInputInfo*>(input_info);
|
||||
uint32_t rewritten = 0;
|
||||
for (auto& block: ir->blocks) {
|
||||
for (auto& inst: block.instructions) {
|
||||
if (IsIrFetchPrologLoad(inst) && EmbeddedFetchPcInList(prolog_pcs, inst.pc)) {
|
||||
auto pc = inst.pc;
|
||||
inst = {};
|
||||
inst.pc = pc;
|
||||
inst.op = IR::Opcode::ControlNop;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inst.op != IR::Opcode::BufferLoadDword) {
|
||||
continue;
|
||||
}
|
||||
const auto* load = FindEmbeddedFetchLoad(loads, inst.pc);
|
||||
if (load == nullptr || inst.memory.component_index >= load->components) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto resource_id = ResolveEmbeddedFetchResource(input_info, *load);
|
||||
if (resource_id < 0 || resource_id >= input_info->resources_num) {
|
||||
LOGF("ShaderRecompiler VS embedded fetch remap failed: pc=0x%08" PRIx32
|
||||
" attrib=%d resources=%d\n",
|
||||
inst.pc, load->attrib_id, input_info->resources_num);
|
||||
continue;
|
||||
}
|
||||
|
||||
inst.op = IR::Opcode::LoadInputF32;
|
||||
inst.input_info.attr = static_cast<uint32_t>(resource_id);
|
||||
inst.input_info.chan = inst.memory.component_index;
|
||||
inst.memory = {};
|
||||
inst.src_count = 0;
|
||||
|
||||
mutable_input_info->resource_fetch_components[resource_id] =
|
||||
std::max(mutable_input_info->resource_fetch_components[resource_id],
|
||||
static_cast<int>(inst.input_info.chan + 1u));
|
||||
rewritten++;
|
||||
}
|
||||
}
|
||||
|
||||
return rewritten;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
CompileResult* result, std::string* error) {
|
||||
if (code.empty() || result == nullptr) {
|
||||
if (error != nullptr) {
|
||||
*error = "invalid shader recompiler input";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (options.stage != ShaderType::Compute && options.stage != ShaderType::Vertex &&
|
||||
options.stage != ShaderType::Pixel) {
|
||||
if (error != nullptr) {
|
||||
*error = "shader recompiler supports compute, vertex, and pixel stages";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto compile_begin = std::chrono::steady_clock::now();
|
||||
const auto phase_ms = [&compile_begin]() {
|
||||
return static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - compile_begin)
|
||||
.count());
|
||||
};
|
||||
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " code_words=%" PRIu64 " decode\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(code.size()));
|
||||
|
||||
Decoder::Program decoded;
|
||||
if (!Decoder::DecodeProgram(code, &decoded, error)) {
|
||||
return false;
|
||||
}
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " decode instructions=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(decoded.instructions.size()), phase_ms());
|
||||
|
||||
std::string decoded_dump;
|
||||
if (options.dump_ir) {
|
||||
decoded_dump = Decoder::ProgramToString(decoded);
|
||||
if (options.early_dump) {
|
||||
LOGF("%s decoded RDNA2 (early):\n%s", GetDumpLabel(options), decoded_dump.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
CFG::Graph cfg;
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG BuildGraph\n", GetDumpLabel(options),
|
||||
StageName(options.stage), options.shader_hash);
|
||||
if (!CFG::BuildGraph(decoded, &cfg, error)) {
|
||||
return false;
|
||||
}
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " CFG BuildGraph blocks=%" PRIu64
|
||||
" loops=%" PRIu64 " back_edges=%" PRIu64 " elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(cfg.blocks.size()), static_cast<uint64_t>(cfg.natural_loops.size()),
|
||||
static_cast<uint64_t>(cfg.back_edges.size()), phase_ms());
|
||||
bool dispatcher_fallback = false;
|
||||
std::string dispatcher_reason;
|
||||
if (cfg.irreducible) {
|
||||
dispatcher_fallback = true;
|
||||
dispatcher_reason = cfg.unsupported_reason;
|
||||
const auto diagnostic = FormatCfgFailure(cfg, options, cfg.unsupported_reason);
|
||||
LOGF("%s irreducible CFG detected: %s\n", GetDumpLabel(options), diagnostic.c_str());
|
||||
} else {
|
||||
std::string structure_error;
|
||||
const auto unstructured_cfg = cfg;
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG Structurize\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
|
||||
if (!CFG::Structurize(&cfg, &structure_error)) {
|
||||
const auto diagnostic = FormatCfgFailure(cfg, options, structure_error);
|
||||
LOGF("%s structured CFG bug/failure: %s\n", GetDumpLabel(options), diagnostic.c_str());
|
||||
dispatcher_fallback = true;
|
||||
dispatcher_reason = structure_error;
|
||||
const auto failure_kind = cfg.failure_kind;
|
||||
const auto failure_block = cfg.failure_block;
|
||||
cfg = unstructured_cfg;
|
||||
cfg.unsupported = true;
|
||||
cfg.failure_kind = failure_kind;
|
||||
cfg.failure_block = failure_block;
|
||||
cfg.unsupported_reason = structure_error;
|
||||
} else {
|
||||
LOGF("%s structured CFG success: blocks=%" PRIu64 "\n", GetDumpLabel(options),
|
||||
static_cast<uint64_t>(cfg.blocks.size()));
|
||||
}
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " CFG Structurize blocks=%" PRIu64
|
||||
" loops=%" PRIu64 " elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(cfg.blocks.size()),
|
||||
static_cast<uint64_t>(cfg.natural_loops.size()), phase_ms());
|
||||
}
|
||||
|
||||
IR::Program ir;
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " IR LowerProgram\n", GetDumpLabel(options),
|
||||
StageName(options.stage), options.shader_hash);
|
||||
if (!IR::LowerProgram(decoded, cfg, options.stage, options.wave_size, &ir, error)) {
|
||||
return false;
|
||||
}
|
||||
ir.lane_mask_mode = options.lane_mask_mode;
|
||||
ir.shader_hash = options.shader_hash;
|
||||
ir.user_data_base = options.user_data_base;
|
||||
ir.user_data_count = options.user_data_count;
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " IR LowerProgram blocks=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(ir.blocks.size()), phase_ms());
|
||||
EmbeddedFetchData embedded_fetch;
|
||||
if (options.stage == ShaderType::Vertex && options.vertex_input_info != nullptr &&
|
||||
options.vertex_input_info->fetch_embedded) {
|
||||
embedded_fetch =
|
||||
DetectEmbeddedVertexFetch(decoded, options.vertex_input_info, ir.user_data_base,
|
||||
ir.user_data_count, options.wave_size);
|
||||
auto rewritten =
|
||||
RewriteEmbeddedVertexFetches(&ir, options.vertex_input_info, embedded_fetch.loads);
|
||||
if (rewritten > 0 || !embedded_fetch.loads.empty()) {
|
||||
LOGF("%s embedded vertex fetch rewrite: detected=%" PRIu64 " rewritten=%" PRIu32 "\n",
|
||||
GetDumpLabel(options), static_cast<uint64_t>(embedded_fetch.loads.size()),
|
||||
rewritten);
|
||||
}
|
||||
}
|
||||
if (!IR::BuildScalarProvenance(&ir, error)) {
|
||||
return false;
|
||||
}
|
||||
std::string srt_error;
|
||||
if (!IR::BuildSrtPlan(&ir, &srt_error)) {
|
||||
LOGF("%s SRT planning failed: %s\n", GetDumpLabel(options), srt_error.c_str());
|
||||
if (error != nullptr) {
|
||||
*error = std::move(srt_error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
ir.dispatcher_fallback = dispatcher_fallback;
|
||||
if (!dispatcher_reason.empty()) {
|
||||
ir.fallback_reason = dispatcher_reason;
|
||||
}
|
||||
|
||||
if (!IR::PatchSrtReads(&ir, error) || !IR::TrackResources(&ir, error)) {
|
||||
return false;
|
||||
}
|
||||
if (options.stage == ShaderType::Vertex) {
|
||||
ir.info.vertex_offset_sgpr = embedded_fetch.vertex_offset_sgpr;
|
||||
}
|
||||
if (!dispatcher_fallback) {
|
||||
std::string emitter_reason;
|
||||
if (NeedsDispatcherForStructuredLoopHeader(ir, &emitter_reason)) {
|
||||
dispatcher_fallback = true;
|
||||
dispatcher_reason = emitter_reason;
|
||||
ir.dispatcher_fallback = true;
|
||||
ir.cfg_failure_kind = CFG::FailureKind::StructuredControlFlow;
|
||||
ir.fallback_reason = emitter_reason;
|
||||
LOGF("%s structured CFG emitter fallback: %s\n", GetDumpLabel(options),
|
||||
emitter_reason.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
IR::ResourceSnapshot resources;
|
||||
if (options.resource_snapshot != nullptr) {
|
||||
resources = *options.resource_snapshot;
|
||||
} else {
|
||||
IR::SrtRuntime runtime;
|
||||
std::array<uint32_t, 64> zero_user_data {};
|
||||
if (options.user_data == nullptr) {
|
||||
for (uint32_t i = 2; i < zero_user_data.size(); i += 4) {
|
||||
zero_user_data[i] = UINT32_MAX;
|
||||
}
|
||||
}
|
||||
const auto* user_data =
|
||||
options.user_data != nullptr ? options.user_data : zero_user_data.data();
|
||||
runtime.user_data = std::span<const uint32_t>(user_data, options.user_data_count);
|
||||
runtime.shader_base = options.shader_base != 0 ? options.shader_base
|
||||
: reinterpret_cast<uint64_t>(code.data());
|
||||
runtime.read_memory = options.read_memory != nullptr ? options.read_memory
|
||||
: options.user_data == nullptr ? ReadZeroMemory
|
||||
: nullptr;
|
||||
runtime.userdata = options.read_memory_data;
|
||||
runtime.flat_memory_base = options.flat_memory_base;
|
||||
if (!IR::MaterializeResources(ir, runtime, &resources, error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!IR::SpecializeResources(&ir, resources, error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ShaderVertexInputInfo default_vertex {};
|
||||
ShaderPixelInputInfo default_pixel {};
|
||||
ShaderComputeInputInfo default_compute {};
|
||||
IR::ShaderInfoOptions info_options;
|
||||
info_options.vertex =
|
||||
options.vertex_input_info != nullptr ? options.vertex_input_info : &default_vertex;
|
||||
info_options.pixel =
|
||||
options.pixel_input_info != nullptr ? options.pixel_input_info : &default_pixel;
|
||||
info_options.compute =
|
||||
options.compute_input_info != nullptr ? options.compute_input_info : &default_compute;
|
||||
if (!IR::CollectShaderInfo(&ir, info_options, error)) {
|
||||
return false;
|
||||
}
|
||||
IR::BindingLayoutOptions layout_options;
|
||||
layout_options.descriptor_set = options.descriptor_set;
|
||||
layout_options.push_constant_offset = options.push_constant_offset;
|
||||
if (!IR::AllocateBindings(&ir, layout_options, error)) {
|
||||
return false;
|
||||
}
|
||||
std::string ir_dump;
|
||||
if (options.dump_ir) {
|
||||
ir_dump = MakeIrDump(cfg, ir);
|
||||
if (options.early_dump) {
|
||||
LOGF("%s native IR and bindings (early):\n%s", GetDumpLabel(options), ir_dump.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint32_t> spirv;
|
||||
std::string emit_error;
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
|
||||
if (!Spirv::EmitProgram(ir, resources, options.vertex_input_info, options.pixel_input_info,
|
||||
options.compute_input_info, &spirv, &emit_error)) {
|
||||
if (dispatcher_fallback && error != nullptr) {
|
||||
*error = fmt::format("dispatcher fallback failed after {}: {}",
|
||||
dispatcher_reason.c_str(), emit_error.c_str());
|
||||
LOGF("%s dispatcher fallback emit failed: %s\n", GetDumpLabel(options), error->c_str());
|
||||
} else if (error != nullptr) {
|
||||
*error = emit_error;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(spirv.size()), phase_ms());
|
||||
if (dispatcher_fallback) {
|
||||
LOGF("%s dispatcher fallback used: %s\n", GetDumpLabel(options), dispatcher_reason.c_str());
|
||||
}
|
||||
|
||||
result->spirv = std::move(spirv);
|
||||
result->program = std::move(ir);
|
||||
result->resources = std::move(resources);
|
||||
if (options.dump_ir) {
|
||||
result->decoded_dump = std::move(decoded_dump);
|
||||
result->ir_dump = std::move(ir_dump);
|
||||
} else {
|
||||
result->decoded_dump.clear();
|
||||
result->ir_dump.clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERRECOMPILER_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERRECOMPILER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/shader/recompiler/ResourceMaterialization.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler {
|
||||
|
||||
struct CompileOptions {
|
||||
ShaderType stage = ShaderType::Compute;
|
||||
ShaderLaneMaskMode lane_mask_mode = ShaderLaneMaskMode::NativeWave;
|
||||
uint32_t wave_size = 64;
|
||||
uint32_t user_data_base = 0;
|
||||
uint32_t user_data_count = 64;
|
||||
uint64_t shader_hash = 0;
|
||||
uint64_t shader_base = 0;
|
||||
std::optional<uint64_t> flat_memory_base;
|
||||
uint32_t descriptor_set = 0;
|
||||
uint32_t push_constant_offset = 0;
|
||||
bool dump_ir = true;
|
||||
bool early_dump = false;
|
||||
const char* dump_label = nullptr;
|
||||
const uint32_t* user_data = nullptr;
|
||||
IR::SrtMemoryReader read_memory = nullptr;
|
||||
void* read_memory_data = nullptr;
|
||||
const IR::ResourceSnapshot* resource_snapshot = nullptr;
|
||||
const ShaderVertexInputInfo* vertex_input_info = nullptr;
|
||||
const ShaderPixelInputInfo* pixel_input_info = nullptr;
|
||||
const ShaderComputeInputInfo* compute_input_info = nullptr;
|
||||
};
|
||||
|
||||
struct CompileResult {
|
||||
std::vector<uint32_t> spirv;
|
||||
std::string decoded_dump;
|
||||
std::string ir_dump;
|
||||
IR::Program program;
|
||||
IR::ResourceSnapshot resources;
|
||||
};
|
||||
|
||||
bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
CompileResult* result, std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERRECOMPILER_H_ */
|
||||
@@ -0,0 +1,160 @@
|
||||
#include "graphics/shader/recompiler/SpirvBuilder.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
|
||||
static constexpr size_t InitialSpirvSectionReserve = 4096;
|
||||
static constexpr size_t InitialSpirvFunctionSectionReserve = 450000;
|
||||
|
||||
static void AppendInstructionWords(std::vector<uint32_t>* section, const uint32_t* words,
|
||||
size_t words_num) {
|
||||
if (words_num == 0) {
|
||||
return;
|
||||
}
|
||||
const auto opcode = words[0];
|
||||
const auto word_count = static_cast<uint32_t>(words_num);
|
||||
section->push_back((word_count << 16u) | opcode);
|
||||
section->insert(section->end(), words + 1, words + words_num);
|
||||
}
|
||||
|
||||
Builder::Builder() {
|
||||
m_debug.reserve(InitialSpirvSectionReserve);
|
||||
m_annotations.reserve(InitialSpirvSectionReserve);
|
||||
m_types.reserve(InitialSpirvSectionReserve);
|
||||
m_functions.reserve(InitialSpirvFunctionSectionReserve);
|
||||
}
|
||||
|
||||
uint32_t Builder::AllocateId() {
|
||||
return m_next_id++;
|
||||
}
|
||||
|
||||
void Builder::AppendString(std::vector<uint32_t>* words, const char* text) {
|
||||
const auto len = text != nullptr ? std::strlen(text) : 0;
|
||||
const auto word_count = (len + 1u + 3u) / 4u;
|
||||
for (size_t i = 0; i < word_count; i++) {
|
||||
uint32_t word = 0;
|
||||
for (size_t byte = 0; byte < 4; byte++) {
|
||||
const auto index = i * 4u + byte;
|
||||
if (index < len) {
|
||||
word |= static_cast<uint32_t>(static_cast<unsigned char>(text[index]))
|
||||
<< (byte * 8u);
|
||||
}
|
||||
}
|
||||
words->push_back(word);
|
||||
}
|
||||
}
|
||||
|
||||
void Builder::AppendInstruction(std::vector<uint32_t>* section, uint32_t opcode,
|
||||
const std::vector<uint32_t>& operands) {
|
||||
const uint32_t word_count = static_cast<uint32_t>(operands.size() + 1u);
|
||||
section->push_back((word_count << 16u) | opcode);
|
||||
section->insert(section->end(), operands.begin(), operands.end());
|
||||
}
|
||||
|
||||
void Builder::AppendInstruction(std::vector<uint32_t>* section, uint32_t opcode,
|
||||
std::initializer_list<uint32_t> operands) {
|
||||
const uint32_t word_count = static_cast<uint32_t>(operands.size() + 1u);
|
||||
section->push_back((word_count << 16u) | opcode);
|
||||
section->insert(section->end(), operands.begin(), operands.end());
|
||||
}
|
||||
|
||||
void Builder::AddCapability(std::initializer_list<uint32_t> operands) {
|
||||
AppendInstruction(&m_capabilities, 17u, operands);
|
||||
}
|
||||
|
||||
void Builder::AddExtension(const char* name) {
|
||||
std::vector<uint32_t> operands;
|
||||
AppendString(&operands, name);
|
||||
AppendInstruction(&m_extensions, 10u, operands);
|
||||
}
|
||||
|
||||
void Builder::AddExtInstImport(uint32_t id, const char* name) {
|
||||
std::vector<uint32_t> operands = {id};
|
||||
AppendString(&operands, name);
|
||||
AppendInstruction(&m_ext_inst_imports, 11u, operands);
|
||||
}
|
||||
|
||||
void Builder::AddMemoryModel(std::initializer_list<uint32_t> operands) {
|
||||
AppendInstruction(&m_memory_model, 14u, operands);
|
||||
}
|
||||
|
||||
void Builder::AddEntryPoint(uint32_t execution_model, uint32_t entry_point, const char* name,
|
||||
const std::vector<uint32_t>& interfaces) {
|
||||
std::vector<uint32_t> operands = {execution_model, entry_point};
|
||||
AppendString(&operands, name);
|
||||
operands.insert(operands.end(), interfaces.begin(), interfaces.end());
|
||||
AppendInstruction(&m_entry_points, 15u, operands);
|
||||
}
|
||||
|
||||
void Builder::AddExecutionMode(std::initializer_list<uint32_t> operands) {
|
||||
AppendInstruction(&m_execution_modes, 16u, operands);
|
||||
}
|
||||
|
||||
void Builder::AddName(uint32_t target, const char* name) {
|
||||
std::vector<uint32_t> operands = {target};
|
||||
AppendString(&operands, name);
|
||||
AppendInstruction(&m_debug, 5u, operands);
|
||||
}
|
||||
|
||||
void Builder::AddAnnotation(std::initializer_list<uint32_t> words) {
|
||||
AppendInstructionWords(&m_annotations, words.begin(), words.size());
|
||||
}
|
||||
|
||||
void Builder::AddType(std::initializer_list<uint32_t> words) {
|
||||
AppendInstructionWords(&m_types, words.begin(), words.size());
|
||||
}
|
||||
|
||||
void Builder::AddFunction(std::initializer_list<uint32_t> words) {
|
||||
AppendInstructionWords(&m_functions, words.begin(), words.size());
|
||||
}
|
||||
|
||||
void Builder::AddFunction(const std::vector<uint32_t>& words) {
|
||||
AppendInstructionWords(&m_functions, words.data(), words.size());
|
||||
}
|
||||
|
||||
std::vector<uint32_t> Builder::Build() const {
|
||||
if (m_debug.size() > InitialSpirvSectionReserve) {
|
||||
std::printf("SPIR-V builder reserve exceeded: section=debug size=%zu reserve=%zu\n",
|
||||
m_debug.size(), InitialSpirvSectionReserve);
|
||||
}
|
||||
if (m_annotations.size() > InitialSpirvSectionReserve) {
|
||||
std::printf("SPIR-V builder reserve exceeded: section=annotations size=%zu reserve=%zu\n",
|
||||
m_annotations.size(), InitialSpirvSectionReserve);
|
||||
}
|
||||
if (m_types.size() > InitialSpirvSectionReserve) {
|
||||
std::printf("SPIR-V builder reserve exceeded: section=types size=%zu reserve=%zu\n",
|
||||
m_types.size(), InitialSpirvSectionReserve);
|
||||
}
|
||||
if (m_functions.size() > InitialSpirvFunctionSectionReserve) {
|
||||
std::printf("SPIR-V builder reserve exceeded: section=functions size=%zu reserve=%zu\n",
|
||||
m_functions.size(), InitialSpirvFunctionSectionReserve);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> module;
|
||||
module.reserve(5u + m_capabilities.size() + m_extensions.size() + m_ext_inst_imports.size() +
|
||||
m_memory_model.size() + m_entry_points.size() + m_execution_modes.size() +
|
||||
m_debug.size() + m_annotations.size() + m_types.size() + m_functions.size());
|
||||
|
||||
module.push_back(0x07230203u);
|
||||
module.push_back(0x00010300u);
|
||||
module.push_back(0u);
|
||||
module.push_back(m_next_id);
|
||||
module.push_back(0u);
|
||||
|
||||
module.insert(module.end(), m_capabilities.begin(), m_capabilities.end());
|
||||
module.insert(module.end(), m_extensions.begin(), m_extensions.end());
|
||||
module.insert(module.end(), m_ext_inst_imports.begin(), m_ext_inst_imports.end());
|
||||
module.insert(module.end(), m_memory_model.begin(), m_memory_model.end());
|
||||
module.insert(module.end(), m_entry_points.begin(), m_entry_points.end());
|
||||
module.insert(module.end(), m_execution_modes.begin(), m_execution_modes.end());
|
||||
module.insert(module.end(), m_debug.begin(), m_debug.end());
|
||||
module.insert(module.end(), m_annotations.begin(), m_annotations.end());
|
||||
module.insert(module.end(), m_types.begin(), m_types.end());
|
||||
module.insert(module.end(), m_functions.begin(), m_functions.end());
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
|
||||
@@ -0,0 +1,56 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SPIRVBUILDER_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SPIRVBUILDER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include <initializer_list>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
|
||||
class Builder {
|
||||
public:
|
||||
Builder();
|
||||
~Builder() = default;
|
||||
KYTY_CLASS_DEFAULT_COPY(Builder);
|
||||
|
||||
uint32_t AllocateId();
|
||||
|
||||
void AddCapability(std::initializer_list<uint32_t> operands);
|
||||
void AddExtension(const char* name);
|
||||
void AddExtInstImport(uint32_t id, const char* name);
|
||||
void AddMemoryModel(std::initializer_list<uint32_t> operands);
|
||||
void AddEntryPoint(uint32_t execution_model, uint32_t entry_point, const char* name,
|
||||
const std::vector<uint32_t>& interfaces);
|
||||
void AddExecutionMode(std::initializer_list<uint32_t> operands);
|
||||
void AddName(uint32_t target, const char* name);
|
||||
void AddAnnotation(std::initializer_list<uint32_t> words);
|
||||
void AddType(std::initializer_list<uint32_t> words);
|
||||
void AddFunction(std::initializer_list<uint32_t> words);
|
||||
void AddFunction(const std::vector<uint32_t>& words);
|
||||
|
||||
[[nodiscard]] std::vector<uint32_t> Build() const;
|
||||
|
||||
private:
|
||||
static void AppendInstruction(std::vector<uint32_t>* section, uint32_t opcode,
|
||||
const std::vector<uint32_t>& operands);
|
||||
static void AppendInstruction(std::vector<uint32_t>* section, uint32_t opcode,
|
||||
std::initializer_list<uint32_t> operands);
|
||||
static void AppendString(std::vector<uint32_t>* words, const char* text);
|
||||
|
||||
uint32_t m_next_id = 1;
|
||||
std::vector<uint32_t> m_capabilities;
|
||||
std::vector<uint32_t> m_extensions;
|
||||
std::vector<uint32_t> m_ext_inst_imports;
|
||||
std::vector<uint32_t> m_memory_model;
|
||||
std::vector<uint32_t> m_entry_points;
|
||||
std::vector<uint32_t> m_execution_modes;
|
||||
std::vector<uint32_t> m_debug;
|
||||
std::vector<uint32_t> m_annotations;
|
||||
std::vector<uint32_t> m_types;
|
||||
std::vector<uint32_t> m_functions;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SPIRVBUILDER_H_ */
|
||||
@@ -0,0 +1,491 @@
|
||||
#include "graphics/shader/recompiler/SpirvEmitter.h"
|
||||
|
||||
#include "graphics/shader/recompiler/SrtWalker.h"
|
||||
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
|
||||
namespace {
|
||||
|
||||
bool Fail(std::string* error, const std::string& message) {
|
||||
if (error != nullptr) {
|
||||
*error = message;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ImageBinding(const IR::ImageResource& image, IR::DescriptorBindingKind& kind) {
|
||||
using Kind = IR::DescriptorBindingKind;
|
||||
using Dim = Decoder::ImageDimension;
|
||||
if (image.kind == IR::ResourceKind::Image || image.kind == IR::ResourceKind::ImageUint) {
|
||||
const bool integer = image.kind == IR::ResourceKind::ImageUint;
|
||||
switch (image.dimension) {
|
||||
case Dim::Dim2D: kind = integer ? Kind::SampledUint2D : Kind::Sampled2D; return true;
|
||||
case Dim::Dim3D: kind = integer ? Kind::SampledUint3D : Kind::Sampled3D; return true;
|
||||
case Dim::Dim2DArray:
|
||||
kind = integer ? Kind::SampledUint2DArray : Kind::Sampled2DArray;
|
||||
return true;
|
||||
case Dim::Unknown: return false;
|
||||
}
|
||||
}
|
||||
const bool uint_image = image.kind == IR::ResourceKind::StorageImageUint;
|
||||
if (image.kind != IR::ResourceKind::StorageImage && !uint_image) {
|
||||
return false;
|
||||
}
|
||||
switch (image.dimension) {
|
||||
case Dim::Dim2D: kind = uint_image ? Kind::StorageUint2D : Kind::Storage2D; return true;
|
||||
case Dim::Dim3D: kind = uint_image ? Kind::StorageUint3D : Kind::Storage3D; return true;
|
||||
case Dim::Dim2DArray:
|
||||
kind = uint_image ? Kind::StorageUint2DArray : Kind::Storage2DArray;
|
||||
return true;
|
||||
case Dim::Unknown: return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NeedsSampler(IR::Opcode op) {
|
||||
return op == IR::Opcode::ImageSample || op == IR::Opcode::ImageGather4 ||
|
||||
op == IR::Opcode::ImageGetLod;
|
||||
}
|
||||
|
||||
bool IsBufferLoad(IR::Opcode op) {
|
||||
switch (op) {
|
||||
case IR::Opcode::BufferLoadUbyte:
|
||||
case IR::Opcode::BufferLoadSbyte:
|
||||
case IR::Opcode::BufferLoadUshort:
|
||||
case IR::Opcode::BufferLoadSshort:
|
||||
case IR::Opcode::BufferLoadDword: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsBufferStore(IR::Opcode op) {
|
||||
return op == IR::Opcode::BufferStoreByte || op == IR::Opcode::BufferStoreShort ||
|
||||
op == IR::Opcode::BufferStoreDword;
|
||||
}
|
||||
|
||||
bool IsFlatLoad(IR::Opcode op) {
|
||||
return op == IR::Opcode::FlatLoadUbyte || op == IR::Opcode::FlatLoadSbyte ||
|
||||
op == IR::Opcode::FlatLoadUshort || op == IR::Opcode::FlatLoadSshort ||
|
||||
op == IR::Opcode::FlatLoadDword;
|
||||
}
|
||||
|
||||
bool IsFlatStore(IR::Opcode op) {
|
||||
return op == IR::Opcode::FlatStoreByte || op == IR::Opcode::FlatStoreShort ||
|
||||
op == IR::Opcode::FlatStoreDword;
|
||||
}
|
||||
|
||||
bool IsDsKind(IR::ResourceKind kind) {
|
||||
return kind == IR::ResourceKind::Lds || kind == IR::ResourceKind::Gds;
|
||||
}
|
||||
|
||||
bool IsDsRead(IR::Opcode op) {
|
||||
switch (op) {
|
||||
case IR::Opcode::DsReadUbyte:
|
||||
case IR::Opcode::DsReadSbyte:
|
||||
case IR::Opcode::DsReadUshort:
|
||||
case IR::Opcode::DsReadSshort:
|
||||
case IR::Opcode::DsReadB32: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsDsWrite(IR::Opcode op) {
|
||||
switch (op) {
|
||||
case IR::Opcode::DsWriteByte:
|
||||
case IR::Opcode::DsWriteShort:
|
||||
case IR::Opcode::DsWriteB32: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsAtomic(IR::Opcode op) {
|
||||
switch (op) {
|
||||
case IR::Opcode::AtomicSwapU32:
|
||||
case IR::Opcode::AtomicAddU32:
|
||||
case IR::Opcode::AtomicSubU32:
|
||||
case IR::Opcode::AtomicSMinI32:
|
||||
case IR::Opcode::AtomicUMinU32:
|
||||
case IR::Opcode::AtomicSMaxI32:
|
||||
case IR::Opcode::AtomicUMaxU32:
|
||||
case IR::Opcode::AtomicAndU32:
|
||||
case IR::Opcode::AtomicOrU32:
|
||||
case IR::Opcode::AtomicXorU32: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t BufferAddressOperandCount(const IR::Instruction& inst) {
|
||||
return 1u + (inst.memory.idxen ? 1u : 0u) + (inst.memory.offen ? 1u : 0u);
|
||||
}
|
||||
|
||||
bool ValidateInstructionContract(const IR::Instruction& inst, std::string* error) {
|
||||
if (inst.src_count > std::size(inst.src)) {
|
||||
return Fail(error, "instruction source count exceeds fixed IR storage");
|
||||
}
|
||||
const auto kind = inst.memory.kind;
|
||||
const auto flat_kind = kind == IR::ResourceKind::Flat || kind == IR::ResourceKind::Global ||
|
||||
kind == IR::ResourceKind::Scratch;
|
||||
const auto buffer_address_count = BufferAddressOperandCount(inst);
|
||||
if ((IsBufferLoad(inst.op) &&
|
||||
(kind != IR::ResourceKind::Buffer || inst.src_count != buffer_address_count)) ||
|
||||
(IsBufferStore(inst.op) &&
|
||||
(kind != IR::ResourceKind::Buffer || inst.src_count != buffer_address_count + 1u)) ||
|
||||
(inst.op == IR::Opcode::SLoadDword &&
|
||||
(kind != IR::ResourceKind::ScalarBuffer || inst.src_count != 1)) ||
|
||||
(inst.op == IR::Opcode::SBufferLoadDword &&
|
||||
(kind != IR::ResourceKind::ScalarBuffer || inst.src_count != 1)) ||
|
||||
(IsFlatLoad(inst.op) && (!flat_kind || inst.src_count != 2)) ||
|
||||
(IsFlatStore(inst.op) && (!flat_kind || inst.src_count != 3))) {
|
||||
return Fail(error, "memory instruction opcode/kind/operand contract is invalid");
|
||||
}
|
||||
const auto ds_kind = IsDsKind(kind);
|
||||
const auto ds_resource = !ds_kind || inst.memory.resource == 0;
|
||||
if ((IsDsRead(inst.op) && (!ds_kind || !ds_resource || inst.src_count != 1 ||
|
||||
inst.dst.kind != IR::OperandKind::Register)) ||
|
||||
(IsDsWrite(inst.op) && (!ds_kind || !ds_resource || inst.src_count != 2 ||
|
||||
inst.dst.kind != IR::OperandKind::Null)) ||
|
||||
((inst.op == IR::Opcode::DsAppend || inst.op == IR::Opcode::DsConsume) &&
|
||||
(!ds_kind || !ds_resource || inst.src_count != 1 ||
|
||||
inst.dst.kind != IR::OperandKind::Register ||
|
||||
(kind == IR::ResourceKind::Gds && inst.memory.offset != 0))) ||
|
||||
((inst.op == IR::Opcode::DsMinF32 || inst.op == IR::Opcode::DsMaxF32) &&
|
||||
(!ds_kind || !ds_resource || inst.src_count != 3 ||
|
||||
inst.dst.kind != IR::OperandKind::Null)) ||
|
||||
(inst.op == IR::Opcode::DsWriteAddtidB32 &&
|
||||
(kind != IR::ResourceKind::Lds || !ds_resource || inst.src_count != 2 ||
|
||||
inst.dst.kind != IR::OperandKind::Null)) ||
|
||||
(inst.op == IR::Opcode::DsReadAddtidB32 &&
|
||||
(kind != IR::ResourceKind::Lds || !ds_resource || inst.src_count != 1 ||
|
||||
inst.dst.kind != IR::OperandKind::Register)) ||
|
||||
(inst.op == IR::Opcode::DsSwizzleB32 &&
|
||||
(kind != IR::ResourceKind::None || inst.src_count != 2 ||
|
||||
inst.dst.kind != IR::OperandKind::Register))) {
|
||||
return Fail(error, "DS instruction has an invalid resource/operand contract");
|
||||
}
|
||||
if ((inst.op == IR::Opcode::ImageSample || inst.op == IR::Opcode::ImageGather4 ||
|
||||
inst.op == IR::Opcode::ImageGetLod || inst.op == IR::Opcode::ImageLoad ||
|
||||
inst.op == IR::Opcode::ImageGetResinfo) &&
|
||||
((kind != IR::ResourceKind::Image && kind != IR::ResourceKind::ImageUint) ||
|
||||
inst.src_count != 1)) {
|
||||
return Fail(error, "sampled image instruction has an invalid resource class");
|
||||
}
|
||||
if (inst.op == IR::Opcode::ImageStore &&
|
||||
((kind != IR::ResourceKind::StorageImage && kind != IR::ResourceKind::StorageImageUint) ||
|
||||
inst.src_count != 2)) {
|
||||
return Fail(error, "storage image instruction has an invalid resource class");
|
||||
}
|
||||
if (IsAtomic(inst.op) &&
|
||||
!((kind == IR::ResourceKind::Buffer && inst.src_count == buffer_address_count + 1u) ||
|
||||
((kind == IR::ResourceKind::Lds || kind == IR::ResourceKind::Gds) &&
|
||||
inst.memory.resource == 0 && inst.src_count == 2 &&
|
||||
(inst.dst.kind == IR::OperandKind::Null ||
|
||||
inst.dst.kind == IR::OperandKind::Register)) ||
|
||||
(kind == IR::ResourceKind::StorageImageUint && inst.src_count == 2))) {
|
||||
return Fail(error, "atomic instruction has an invalid resource/operand contract");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
using Kind = IR::DescriptorBindingKind;
|
||||
constexpr auto KindCount = static_cast<size_t>(Kind::Count);
|
||||
std::array<std::vector<uint32_t>, KindCount> expected;
|
||||
std::array<bool, KindCount> present {};
|
||||
const auto Dense = [](size_t size) {
|
||||
std::vector<uint32_t> values(size);
|
||||
for (uint32_t i = 0; i < values.size(); i++) {
|
||||
values[i] = i;
|
||||
}
|
||||
return values;
|
||||
};
|
||||
auto Expect = [&](Kind kind, std::vector<uint32_t> resources = {}) {
|
||||
const auto index = static_cast<size_t>(kind);
|
||||
present[index] = true;
|
||||
expected[index] = std::move(resources);
|
||||
};
|
||||
if (!program.info.buffers.empty()) {
|
||||
Expect(Kind::Buffers, Dense(program.info.buffers.size()));
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
Kind kind;
|
||||
if (!ImageBinding(program.info.images[i], kind)) {
|
||||
return Fail(error, "native shader plan has an invalid image class");
|
||||
}
|
||||
present[static_cast<size_t>(kind)] = true;
|
||||
expected[static_cast<size_t>(kind)].push_back(i);
|
||||
}
|
||||
if (!program.info.samplers.empty()) {
|
||||
Expect(Kind::Samplers, Dense(program.info.samplers.size()));
|
||||
}
|
||||
const auto uses_gds =
|
||||
std::any_of(program.blocks.begin(), program.blocks.end(), [](const auto& block) {
|
||||
return std::any_of(
|
||||
block.instructions.begin(), block.instructions.end(),
|
||||
[](const auto& inst) { return inst.memory.kind == IR::ResourceKind::Gds; });
|
||||
});
|
||||
if (uses_gds) {
|
||||
Expect(Kind::Gds);
|
||||
}
|
||||
if (!program.info.addresses.empty()) {
|
||||
Expect(Kind::AddressMemory, Dense(program.info.addresses.size()));
|
||||
}
|
||||
if (!program.srt.reads.empty()) {
|
||||
Expect(Kind::FlattenedSrt);
|
||||
}
|
||||
if (!program.bindings.user_data_registers.empty() && program.bindings.push_constant_size == 0) {
|
||||
Expect(Kind::UserData);
|
||||
}
|
||||
|
||||
std::array<bool, KindCount> seen {};
|
||||
for (uint32_t i = 0; i < program.bindings.descriptors.size(); i++) {
|
||||
const auto& binding = program.bindings.descriptors[i];
|
||||
const auto kind = static_cast<size_t>(binding.kind);
|
||||
if (kind >= KindCount || seen[kind] || !present[kind] ||
|
||||
binding.resources != expected[kind]) {
|
||||
return Fail(error, "native descriptor groups do not match shader topology");
|
||||
}
|
||||
for (uint32_t j = 0; j < i; j++) {
|
||||
if (program.bindings.descriptors[j].binding == binding.binding) {
|
||||
return Fail(error, "native descriptor binding numbers are not unique");
|
||||
}
|
||||
}
|
||||
seen[kind] = true;
|
||||
}
|
||||
for (size_t i = 0; i < KindCount; i++) {
|
||||
if (present[i] != seen[i]) {
|
||||
return Fail(error, "native shader plan is missing a required descriptor group");
|
||||
}
|
||||
}
|
||||
const auto has_user_storage = present[static_cast<size_t>(Kind::UserData)];
|
||||
if ((!has_user_storage &&
|
||||
program.bindings.push_constant_size != program.bindings.user_data_registers.size() * 4u) ||
|
||||
(has_user_storage && program.bindings.push_constant_size != 0) ||
|
||||
!std::is_sorted(program.bindings.user_data_registers.begin(),
|
||||
program.bindings.user_data_registers.end()) ||
|
||||
std::adjacent_find(program.bindings.user_data_registers.begin(),
|
||||
program.bindings.user_data_registers.end()) !=
|
||||
program.bindings.user_data_registers.end()) {
|
||||
return Fail(error, "native user-data layout is inconsistent");
|
||||
}
|
||||
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (!ValidateInstructionContract(inst, error)) {
|
||||
return false;
|
||||
}
|
||||
const auto memory = inst.memory.kind;
|
||||
if ((memory == IR::ResourceKind::Buffer || (memory == IR::ResourceKind::ScalarBuffer &&
|
||||
inst.op == IR::Opcode::SBufferLoadDword)) &&
|
||||
inst.memory.resource >= program.info.buffers.size()) {
|
||||
return Fail(error, "buffer instruction has an invalid dense resource");
|
||||
}
|
||||
if ((memory == IR::ResourceKind::Image || memory == IR::ResourceKind::ImageUint ||
|
||||
memory == IR::ResourceKind::StorageImage ||
|
||||
memory == IR::ResourceKind::StorageImageUint) &&
|
||||
(inst.memory.resource >= program.info.images.size() ||
|
||||
program.info.images[inst.memory.resource].kind != memory ||
|
||||
program.info.images[inst.memory.resource].dimension !=
|
||||
inst.memory.image_dimension)) {
|
||||
return Fail(error, "image instruction has an invalid dense resource");
|
||||
}
|
||||
const bool address =
|
||||
inst.op == IR::Opcode::SLoadDword || memory == IR::ResourceKind::Flat ||
|
||||
memory == IR::ResourceKind::Global || memory == IR::ResourceKind::Scratch;
|
||||
if (address && (inst.memory.resource >= program.info.addresses.size() ||
|
||||
program.info.addresses[inst.memory.resource].kind != memory)) {
|
||||
return Fail(error, "address instruction has an invalid dense resource");
|
||||
}
|
||||
if (NeedsSampler(inst.op) && inst.memory.sampler >= program.info.samplers.size()) {
|
||||
return Fail(error, "sample instruction has an invalid dense sampler");
|
||||
}
|
||||
if (inst.op == IR::Opcode::LoadSrtDword &&
|
||||
(inst.src_count != 1 || inst.src[0].kind != IR::OperandKind::ImmediateU32 ||
|
||||
inst.src[0].imm >= program.srt.reads.size())) {
|
||||
return Fail(error, "flattened SRT instruction has an invalid dense slot");
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static constexpr size_t InitialEmitterVectorReserve = 4096;
|
||||
|
||||
static void ReportReserveExceeded(const IR::Program& program, const char* vector_name,
|
||||
size_t size) {
|
||||
if (size <= InitialEmitterVectorReserve) {
|
||||
return;
|
||||
}
|
||||
std::printf("SPIR-V emitter reserve exceeded: hash=0x%016" PRIx64
|
||||
" stage=%u vector=%s size=%zu reserve=%zu\n",
|
||||
program.shader_hash, static_cast<unsigned>(program.stage), vector_name, size,
|
||||
InitialEmitterVectorReserve);
|
||||
}
|
||||
|
||||
static bool IsExecDestination(const IR::Operand& operand) {
|
||||
return operand.kind == IR::OperandKind::Register && operand.reg.file == IR::RegisterFile::Exec;
|
||||
}
|
||||
|
||||
static bool IsUniformZeroExecWrite(const IR::Program& program, const IR::Instruction& inst) {
|
||||
if (inst.op != IR::Opcode::BitFieldMaskU64 || !IsExecDestination(inst.dst) ||
|
||||
inst.src_count < 1u) {
|
||||
return false;
|
||||
}
|
||||
uint32_t count = 0;
|
||||
return IR::FoldScalarConstant(program.provenance, inst.scalar_sources[0], &count) &&
|
||||
(count & 63u) == 0u;
|
||||
}
|
||||
|
||||
static bool ProducesLaneMask(const IR::Instruction& inst) {
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::IAddCarryU32:
|
||||
case IR::Opcode::ISubBorrowU32:
|
||||
case IR::Opcode::UMadU64U32: return inst.dst2.kind != IR::OperandKind::Null;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ProgramRequiresExactSubgroupSize(const IR::Program& program) {
|
||||
for (const auto& block: program.blocks) {
|
||||
if (block.terminator.kind == CFG::TerminatorKind::ConditionalBranch) {
|
||||
switch (block.terminator.condition) {
|
||||
case CFG::BranchCondition::VccZero:
|
||||
case CFG::BranchCondition::VccNonZero:
|
||||
case CFG::BranchCondition::ExecZero:
|
||||
case CFG::BranchCondition::ExecNonZero: return true;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (Emitter::InstructionHasDppSource(inst)) {
|
||||
return true;
|
||||
}
|
||||
if (Emitter::IsCompareOpcode(inst.op) && !Emitter::IsSccOperand(inst.dst)) {
|
||||
return true;
|
||||
}
|
||||
if ((IsExecDestination(inst.dst) || IsExecDestination(inst.dst2)) &&
|
||||
!IsUniformZeroExecWrite(program, inst)) {
|
||||
return true;
|
||||
}
|
||||
if (ProducesLaneMask(inst)) {
|
||||
return true;
|
||||
}
|
||||
if (inst.op == IR::Opcode::SelectMaskU32 || inst.op == IR::Opcode::SelectMaskF32Bits) {
|
||||
return true;
|
||||
}
|
||||
if (inst.op == IR::Opcode::SaveexecB32 || inst.op == IR::Opcode::SaveexecB64) {
|
||||
return true;
|
||||
}
|
||||
switch (inst.op) {
|
||||
case IR::Opcode::WqmB64:
|
||||
if (inst.dst.kind == IR::OperandKind::Register &&
|
||||
inst.dst.reg.file != IR::RegisterFile::Scalar) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case IR::Opcode::MaskedBitCountLowU32:
|
||||
case IR::Opcode::MaskedBitCountHighU32:
|
||||
case IR::Opcode::ReadFirstLaneU32:
|
||||
case IR::Opcode::ReadLaneU32:
|
||||
case IR::Opcode::WriteLaneU32:
|
||||
case IR::Opcode::Permlane16B32:
|
||||
case IR::Opcode::Permlanex16B32:
|
||||
case IR::Opcode::DsSwizzleB32:
|
||||
case IR::Opcode::DsConsume:
|
||||
case IR::Opcode::DsAppend: return true;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resources,
|
||||
const ShaderVertexInputInfo* vertex_input_info,
|
||||
const ShaderPixelInputInfo* pixel_input_info,
|
||||
const ShaderComputeInputInfo* compute_input_info, std::vector<uint32_t>* spirv,
|
||||
std::string* error) {
|
||||
using namespace Emitter;
|
||||
|
||||
if (spirv == nullptr) {
|
||||
SetError(error, "invalid SPIR-V output");
|
||||
return false;
|
||||
}
|
||||
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex &&
|
||||
program.stage != ShaderType::Pixel) {
|
||||
SetError(error, "binary SPIR-V emitter supports compute, vertex, and pixel shaders");
|
||||
return false;
|
||||
}
|
||||
if (!program.srt_patching_complete || !program.resource_tracking_complete ||
|
||||
!program.shader_info_complete || !program.binding_layout_complete) {
|
||||
SetError(error, "SPIR-V emitter requires a fully planned native shader program");
|
||||
return false;
|
||||
}
|
||||
if (!IR::ValidateResourceSnapshot(program, resources, error)) {
|
||||
return false;
|
||||
}
|
||||
if (!IR::ValidateResourceSpecialization(program, resources, error)) {
|
||||
return false;
|
||||
}
|
||||
if (!ValidateNativeProgram(program, error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EmitterState state;
|
||||
state.program = &program;
|
||||
state.resources = &resources;
|
||||
state.vertex_input_info = vertex_input_info;
|
||||
state.pixel_input_info = pixel_input_info;
|
||||
state.compute_input_info = compute_input_info;
|
||||
state.stage = program.stage;
|
||||
state.wave_size = program.wave_size;
|
||||
state.per_invocation_masks = program.lane_mask_mode == ShaderLaneMaskMode::PerInvocation;
|
||||
state.exact_subgroup_operations =
|
||||
!state.per_invocation_masks && ProgramRequiresExactSubgroupSize(program);
|
||||
state.registers.reserve(InitialEmitterVectorReserve);
|
||||
state.inputs.reserve(InitialEmitterVectorReserve);
|
||||
state.outputs.reserve(InitialEmitterVectorReserve);
|
||||
state.interface_variables.reserve(InitialEmitterVectorReserve);
|
||||
state.reachable_blocks.reserve(InitialEmitterVectorReserve);
|
||||
CollectRegisters(program, &state.registers);
|
||||
CopyProgramInputsAndOutputs(&state, program);
|
||||
state.needs_subgroup_ballot = ProgramNeedsSubgroupBallot(program);
|
||||
state.needs_subgroup_shuffle = ProgramNeedsSubgroupShuffle(program);
|
||||
state.needs_subgroup_local_invocation_id = ProgramNeedsSubgroupLocalInvocationId(program);
|
||||
state.needs_compute_derivatives = ProgramNeedsComputeDerivatives(program);
|
||||
state.needs_image_gather_extended = ProgramNeedsImageGatherExtended(program);
|
||||
state.needs_function_lds = ProgramNeedsFunctionLds(program);
|
||||
state.needs_pixel_valid_mask = ProgramNeedsPixelValidMask(program);
|
||||
ComputeReachableBlocks(&state, program);
|
||||
AllocateInputVariables(&state);
|
||||
AllocateOutputVariables(&state);
|
||||
AllocateDescriptorVariables(&state);
|
||||
EmitHeaderAndTypes(&state);
|
||||
AllocateRegisterVariables(&state);
|
||||
AllocateBlockLabels(&state, program);
|
||||
AllocateDispatcherState(&state, program);
|
||||
EmitFunction(&state, program);
|
||||
|
||||
ReportReserveExceeded(program, "registers", state.registers.size());
|
||||
ReportReserveExceeded(program, "inputs", state.inputs.size());
|
||||
ReportReserveExceeded(program, "outputs", state.outputs.size());
|
||||
ReportReserveExceeded(program, "interface_variables", state.interface_variables.size());
|
||||
ReportReserveExceeded(program, "reachable_blocks", state.reachable_blocks.size());
|
||||
|
||||
auto binary = state.builder.Build();
|
||||
if (binary.empty()) {
|
||||
SetError(error, "SPIR-V builder returned an empty module");
|
||||
return false;
|
||||
}
|
||||
*spirv = std::move(binary);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SPIRVEMITTER_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SPIRVEMITTER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/shader/recompiler/ResourceMaterialization.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
|
||||
bool ProgramRequiresExactSubgroupSize(const IR::Program& program);
|
||||
|
||||
bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resources,
|
||||
const ShaderVertexInputInfo* vertex_input_info,
|
||||
const ShaderPixelInputInfo* pixel_input_info,
|
||||
const ShaderComputeInputInfo* compute_input_info, std::vector<uint32_t>* spirv,
|
||||
std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SPIRVEMITTER_H_ */
|
||||
@@ -0,0 +1,133 @@
|
||||
#include "graphics/shader/recompiler/SrtPatcher.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
bool PatchSrtReads(Program* program, std::string* error) {
|
||||
if (program == nullptr || !program->srt_plan_complete || program->srt_patching_complete ||
|
||||
program->resource_tracking_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = program == nullptr ? "invalid SRT patch program"
|
||||
: !program->srt_plan_complete ? "SRT plan is not ready"
|
||||
: program->srt_patching_complete ? "SRT reads already patched"
|
||||
: "resources already tracked";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<uint32_t, uint32_t> offsets;
|
||||
std::map<uint32_t, uint32_t> values;
|
||||
for (const auto& read: program->srt.reads) {
|
||||
if (read.value >= program->provenance.values.size() ||
|
||||
read.flat_offset >= program->srt.reads.size() ||
|
||||
(program->provenance.values[read.value].op != ScalarValueOp::ReadConst &&
|
||||
program->provenance.values[read.value].op != ScalarValueOp::ReadConstBuffer)) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("invalid SRT read value={} flat_offset={}", read.value,
|
||||
read.flat_offset);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const auto [it, inserted] = offsets.emplace(read.value, read.flat_offset);
|
||||
if (!inserted && it->second != read.flat_offset) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("SRT read value {} has conflicting flat offsets", read.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const auto [value, unique] = values.emplace(read.flat_offset, read.value);
|
||||
if (!unique && value->second != read.value) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("SRT flat offset {} has conflicting values", read.flat_offset);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (offsets.size() != program->srt.reads.size() || values.size() != program->srt.reads.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = "SRT reads do not form a dense value-to-offset bijection";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::set<uint32_t> dynamic_reads;
|
||||
for (const auto value: program->srt.dynamic_reads) {
|
||||
if (value >= program->provenance.values.size() || offsets.contains(value) ||
|
||||
(program->provenance.values[value].op != ScalarValueOp::ReadConst &&
|
||||
program->provenance.values[value].op != ScalarValueOp::ReadConstBuffer) ||
|
||||
!dynamic_reads.insert(value).second) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("invalid dynamic SRT read value={}", value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
std::set<uint32_t> dynamic_sources;
|
||||
for (const auto source: program->srt.dynamic_sources) {
|
||||
if (source == ScalarProvenance::Undefined ||
|
||||
(source > ScalarProvenance::Unknown &&
|
||||
source - 2u >= program->provenance.descriptors.size()) ||
|
||||
!dynamic_sources.insert(source).second) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("invalid dynamic descriptor source={}", source);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
struct Patch {
|
||||
Instruction* inst = nullptr;
|
||||
uint32_t flat_offset = 0;
|
||||
};
|
||||
std::vector<Patch> patches;
|
||||
std::map<uint32_t, bool> matched;
|
||||
for (const auto& [value, offset]: offsets) {
|
||||
matched.emplace(value, false);
|
||||
}
|
||||
for (auto& block: program->blocks) {
|
||||
for (auto& inst: block.instructions) {
|
||||
if (inst.op != Opcode::SLoadDword && inst.op != Opcode::SBufferLoadDword) {
|
||||
continue;
|
||||
}
|
||||
if (const auto found = offsets.find(inst.scalar_value); found != offsets.end()) {
|
||||
const auto value_op = program->provenance.values[found->first].op;
|
||||
if ((inst.op == Opcode::SLoadDword) != (value_op == ScalarValueOp::ReadConst)) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("SRT read value {} has the wrong scalar-load producer",
|
||||
found->first);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
patches.push_back({&inst, found->second});
|
||||
matched[found->first] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& [value, found]: matched) {
|
||||
if (!found) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("SRT read value {} has no scalar-load producer", value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& patch: patches) {
|
||||
patch.inst->op = Opcode::LoadSrtDword;
|
||||
patch.inst->src_count = 1;
|
||||
for (auto& src: patch.inst->src) {
|
||||
src = {};
|
||||
}
|
||||
patch.inst->src[0].kind = OperandKind::ImmediateU32;
|
||||
patch.inst->src[0].imm = patch.flat_offset;
|
||||
patch.inst->memory = {};
|
||||
}
|
||||
program->srt_patching_complete = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SRTPATCHER_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SRTPATCHER_H_
|
||||
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
// Replaces each immediate ReadConst producer selected by the SRT plan with a dense flat-buffer
|
||||
// dword load. Dynamic-offset reads remain explicit. On failure no instruction is changed.
|
||||
bool PatchSrtReads(Program* program, std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SRTPATCHER_H_ */
|
||||
@@ -0,0 +1,655 @@
|
||||
#include "graphics/shader/recompiler/SrtWalker.h"
|
||||
|
||||
#include "graphics/host_gpu/hostMemory.h"
|
||||
#include "graphics/shader/recompiler/ScalarProvenance.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
namespace {
|
||||
|
||||
constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
|
||||
|
||||
const char* StageName(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Vertex: return "vertex";
|
||||
case ShaderType::Pixel: return "pixel";
|
||||
case ShaderType::Fetch: return "fetch";
|
||||
case ShaderType::Compute: return "compute";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
std::string Diagnostic(const Program& program, uint32_t pc, const std::string& message) {
|
||||
return fmt::format("shader SRT: hash=0x{:016x} stage={} pc=0x{:08x} {}", program.shader_hash,
|
||||
StageName(program.stage), pc, message);
|
||||
}
|
||||
|
||||
bool AddSignedAddress(uint64_t base, int64_t offset, uint64_t* result) {
|
||||
if (result == nullptr || base > AddressMask) {
|
||||
return false;
|
||||
}
|
||||
if (offset < 0) {
|
||||
const auto magnitude = uint64_t {0} - static_cast<uint64_t>(offset);
|
||||
if (magnitude > base) {
|
||||
return false;
|
||||
}
|
||||
*result = base - magnitude;
|
||||
return true;
|
||||
}
|
||||
const auto magnitude = static_cast<uint64_t>(offset);
|
||||
if (magnitude > AddressMask - base) {
|
||||
return false;
|
||||
}
|
||||
*result = base + magnitude;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ApplyOperation(ScalarValueOp op, const uint32_t args[3], uint32_t* result) {
|
||||
if (result == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const auto shift = args[1] & 31u;
|
||||
switch (op) {
|
||||
case ScalarValueOp::Add: *result = args[0] + args[1]; break;
|
||||
case ScalarValueOp::AddCarry: *result = args[0] + args[1] + (args[2] & 1u); break;
|
||||
case ScalarValueOp::Carry:
|
||||
*result = static_cast<uint32_t>(
|
||||
(static_cast<uint64_t>(args[0]) + args[1] + (args[2] & 1u)) >> 32u);
|
||||
break;
|
||||
case ScalarValueOp::Sub: *result = args[0] - args[1]; break;
|
||||
case ScalarValueOp::SubBorrow: *result = args[0] - args[1] - (args[2] & 1u); break;
|
||||
case ScalarValueOp::Borrow:
|
||||
*result = static_cast<uint64_t>(args[1]) + (args[2] & 1u) > args[0] ? 1u : 0u;
|
||||
break;
|
||||
case ScalarValueOp::Mul: *result = args[0] * args[1]; break;
|
||||
case ScalarValueOp::And: *result = args[0] & args[1]; break;
|
||||
case ScalarValueOp::AndNot: *result = args[0] & ~args[1]; break;
|
||||
case ScalarValueOp::Or: *result = args[0] | args[1]; break;
|
||||
case ScalarValueOp::OrNot: *result = args[0] | ~args[1]; break;
|
||||
case ScalarValueOp::Xor: *result = args[0] ^ args[1]; break;
|
||||
case ScalarValueOp::Not: *result = ~args[0]; break;
|
||||
case ScalarValueOp::ShiftLeft: *result = args[0] << shift; break;
|
||||
case ScalarValueOp::ShiftRight: *result = args[0] >> shift; break;
|
||||
case ScalarValueOp::ShiftRightArithmetic:
|
||||
*result = static_cast<uint32_t>(static_cast<int32_t>(args[0]) >> shift);
|
||||
break;
|
||||
case ScalarValueOp::BitFieldMaskU32: {
|
||||
const auto count = args[0] & 31u;
|
||||
const auto offset = args[1] & 31u;
|
||||
*result = ((uint32_t {1} << count) - 1u) << offset;
|
||||
break;
|
||||
}
|
||||
case ScalarValueOp::BitFieldMaskU64Low:
|
||||
case ScalarValueOp::BitFieldMaskU64High: {
|
||||
const auto count = args[0] & 63u;
|
||||
const auto value = ((uint64_t {1} << count) - 1u) << (args[1] & 63u);
|
||||
*result = op == ScalarValueOp::BitFieldMaskU64Low ? static_cast<uint32_t>(value)
|
||||
: static_cast<uint32_t>(value >> 32u);
|
||||
break;
|
||||
}
|
||||
case ScalarValueOp::Add3: *result = args[0] + args[1] + args[2]; break;
|
||||
case ScalarValueOp::ShiftLeftAdd: *result = (args[0] << shift) + args[2]; break;
|
||||
case ScalarValueOp::ShiftLeftAddCarry:
|
||||
*result =
|
||||
static_cast<uint32_t>(((static_cast<uint64_t>(args[0]) << shift) + args[2]) >> 32u);
|
||||
break;
|
||||
case ScalarValueOp::AddShiftLeft: *result = (args[0] + args[1]) << (args[2] & 31u); break;
|
||||
case ScalarValueOp::XorAdd: *result = (args[0] ^ args[1]) + args[2]; break;
|
||||
case ScalarValueOp::ShiftLeftOr: *result = (args[0] << shift) | args[2]; break;
|
||||
default: return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class ConstantFolder {
|
||||
public:
|
||||
explicit ConstantFolder(const ScalarProvenance& provenance)
|
||||
: m_provenance(provenance), m_values(provenance.values.size()),
|
||||
m_state(provenance.values.size()) {}
|
||||
|
||||
bool Fold(uint32_t id, uint32_t& result) {
|
||||
if (id >= m_provenance.values.size() || m_state[id] == 1 || m_state[id] == 3) {
|
||||
return false;
|
||||
}
|
||||
if (m_state[id] == 2) {
|
||||
result = m_values[id];
|
||||
return true;
|
||||
}
|
||||
m_state[id] = 1;
|
||||
const auto& value = m_provenance.values[id];
|
||||
uint32_t out = 0;
|
||||
switch (value.op) {
|
||||
case ScalarValueOp::Constant: out = value.imm; break;
|
||||
case ScalarValueOp::Phi:
|
||||
if (value.phi_args.empty() || !Fold(value.phi_args[0], out)) {
|
||||
return Dynamic(id);
|
||||
}
|
||||
for (size_t i = 1; i < value.phi_args.size(); i++) {
|
||||
uint32_t other = 0;
|
||||
if (!Fold(value.phi_args[i], other) || other != out) {
|
||||
return Dynamic(id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
const auto count = ScalarValueArgCount(value.op);
|
||||
if (count == 0 || count > 3 || value.op == ScalarValueOp::ReadConst) {
|
||||
return Dynamic(id);
|
||||
}
|
||||
uint32_t args[3] = {};
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (!Fold(value.args[i], args[i])) {
|
||||
return Dynamic(id);
|
||||
}
|
||||
}
|
||||
if (!ApplyOperation(value.op, args, &out)) {
|
||||
return Dynamic(id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_state[id] = 2;
|
||||
m_values[id] = out;
|
||||
result = out;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool Dynamic(uint32_t id) {
|
||||
m_state[id] = 3;
|
||||
return false;
|
||||
}
|
||||
|
||||
const ScalarProvenance& m_provenance;
|
||||
std::vector<uint32_t> m_values;
|
||||
std::vector<uint8_t> m_state;
|
||||
};
|
||||
|
||||
class PlanBuilder {
|
||||
public:
|
||||
explicit PlanBuilder(Program* program): m_program(program), m_folder(program->provenance) {
|
||||
m_state.resize(program->provenance.values.size());
|
||||
}
|
||||
|
||||
bool Run(std::string* error) {
|
||||
m_program->srt = {};
|
||||
for (const auto& block: m_program->blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (!CollectDescriptor(inst.memory.resource_source, inst.pc, error) ||
|
||||
!CollectDescriptor(inst.memory.sampler_source, inst.pc, error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& block: m_program->blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (inst.op == Opcode::SLoadDword && inst.scalar_value < m_state.size() &&
|
||||
m_state[inst.scalar_value] == 0) {
|
||||
const auto& value = m_program->provenance.values[inst.scalar_value];
|
||||
uint32_t ignored = 0;
|
||||
if (value.op == ScalarValueOp::ReadConst &&
|
||||
m_folder.Fold(value.args[2], ignored) &&
|
||||
!CollectValue(inst.scalar_value, inst.pc, error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool Fail(uint32_t pc, std::string* error, const std::string& message) const {
|
||||
if (error != nullptr) {
|
||||
*error = Diagnostic(*m_program, pc, message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CollectDescriptor(uint32_t source, uint32_t use_pc, std::string* error) {
|
||||
const auto* descriptor = GetDescriptorSource(*m_program, source);
|
||||
if (descriptor == nullptr) {
|
||||
if (source <= ScalarProvenance::Unknown) {
|
||||
return source == ScalarProvenance::Undefined || AddDynamicSource(source);
|
||||
}
|
||||
return Fail(use_pc, error, fmt::format("invalid descriptor source {}", source));
|
||||
}
|
||||
if (!DescriptorSourceResolved(*m_program, source) ||
|
||||
DescriptorNeedsControlFlow(*descriptor)) {
|
||||
MarkDescriptor(*descriptor);
|
||||
return AddDynamicSource(source);
|
||||
}
|
||||
for (uint32_t i = 0; i < descriptor->dword_count; i++) {
|
||||
if (!CollectValue(descriptor->dwords[i], use_pc, error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MarkDescriptor(const DescriptorValue& descriptor) {
|
||||
for (uint32_t i = 0; i < descriptor.dword_count; i++) {
|
||||
MarkValue(descriptor.dwords[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void MarkValue(uint32_t id) {
|
||||
if (id >= m_state.size() || m_state[id] != 0) {
|
||||
return;
|
||||
}
|
||||
m_state[id] = 2;
|
||||
const auto& value = m_program->provenance.values[id];
|
||||
if (value.op == ScalarValueOp::Phi) {
|
||||
for (const auto arg: value.phi_args) {
|
||||
MarkValue(arg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (uint32_t i = 0; i < ScalarValueArgCount(value.op); i++) {
|
||||
MarkValue(value.args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool DescriptorNeedsControlFlow(const DescriptorValue& descriptor) {
|
||||
std::vector<uint8_t> visited(m_program->provenance.values.size());
|
||||
for (uint32_t i = 0; i < descriptor.dword_count; i++) {
|
||||
if (ValueNeedsControlFlow(descriptor.dwords[i], &visited)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ValueNeedsControlFlow(uint32_t id, std::vector<uint8_t>* visited) {
|
||||
if (id >= m_program->provenance.values.size() || (*visited)[id] != 0) {
|
||||
return false;
|
||||
}
|
||||
(*visited)[id] = 1;
|
||||
const auto& value = m_program->provenance.values[id];
|
||||
if (value.op == ScalarValueOp::Phi) {
|
||||
uint32_t ignored = 0;
|
||||
return !m_folder.Fold(id, ignored);
|
||||
}
|
||||
for (uint32_t i = 0; i < ScalarValueArgCount(value.op); i++) {
|
||||
if (ValueNeedsControlFlow(value.args[i], visited)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AddDynamicSource(uint32_t source) {
|
||||
if (std::find(m_program->srt.dynamic_sources.begin(), m_program->srt.dynamic_sources.end(),
|
||||
source) == m_program->srt.dynamic_sources.end()) {
|
||||
m_program->srt.dynamic_sources.push_back(source);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CollectValue(uint32_t id, uint32_t use_pc, std::string* error) {
|
||||
if (id >= m_program->provenance.values.size()) {
|
||||
return Fail(use_pc, error, fmt::format("invalid scalar value {}", id));
|
||||
}
|
||||
if (m_state[id] == 2) {
|
||||
return true;
|
||||
}
|
||||
if (m_state[id] == 1) {
|
||||
return Fail(use_pc, error, fmt::format("cyclic scalar value {}", id));
|
||||
}
|
||||
m_state[id] = 1;
|
||||
const auto& value = m_program->provenance.values[id];
|
||||
if (value.op == ScalarValueOp::Phi) {
|
||||
for (const auto arg: value.phi_args) {
|
||||
if (!CollectValue(arg, use_pc, error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (uint32_t i = 0; i < ScalarValueArgCount(value.op); i++) {
|
||||
if (!CollectValue(value.args[i], use_pc, error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (value.op == ScalarValueOp::ReadConst || value.op == ScalarValueOp::ReadConstBuffer) {
|
||||
const auto offset_arg = value.op == ScalarValueOp::ReadConst ? 2u : 4u;
|
||||
uint32_t ignored = 0;
|
||||
if (m_folder.Fold(value.args[offset_arg], ignored)) {
|
||||
m_program->srt.reads.push_back(
|
||||
{id, static_cast<uint32_t>(m_program->srt.reads.size()), use_pc});
|
||||
} else {
|
||||
m_program->srt.dynamic_reads.push_back(id);
|
||||
}
|
||||
}
|
||||
m_state[id] = 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
Program* m_program;
|
||||
ConstantFolder m_folder;
|
||||
std::vector<uint8_t> m_state;
|
||||
};
|
||||
|
||||
class Evaluator {
|
||||
public:
|
||||
Evaluator(const Program& program, const SrtRuntime& runtime, uint32_t use_pc)
|
||||
: m_program(program), m_runtime(runtime), m_use_pc(use_pc),
|
||||
m_values(program.provenance.values.size()), m_state(program.provenance.values.size()) {}
|
||||
|
||||
void SetUsePc(uint32_t use_pc) { m_use_pc = use_pc; }
|
||||
|
||||
bool Evaluate(uint32_t id, uint32_t& result, std::string* error) {
|
||||
if (id >= m_program.provenance.values.size()) {
|
||||
return Fail(error, fmt::format("invalid scalar value {}", id));
|
||||
}
|
||||
if (m_state[id] == 2) {
|
||||
result = m_values[id];
|
||||
return true;
|
||||
}
|
||||
if (m_state[id] == 1) {
|
||||
return Fail(error, fmt::format("cyclic scalar value {}", id));
|
||||
}
|
||||
m_state[id] = 1;
|
||||
const auto& value = m_program.provenance.values[id];
|
||||
uint32_t out = 0;
|
||||
switch (value.op) {
|
||||
case ScalarValueOp::UserData:
|
||||
if (value.imm < m_program.user_data_base ||
|
||||
value.imm - m_program.user_data_base >= m_runtime.user_data.size()) {
|
||||
return Fail(error, fmt::format("user SGPR {} is unavailable", value.imm));
|
||||
}
|
||||
out = m_runtime.user_data[value.imm - m_program.user_data_base];
|
||||
break;
|
||||
case ScalarValueOp::Constant: out = value.imm; break;
|
||||
case ScalarValueOp::PcRelativeLow:
|
||||
out = static_cast<uint32_t>(m_runtime.shader_base + value.imm);
|
||||
break;
|
||||
case ScalarValueOp::PcRelativeHigh:
|
||||
out = static_cast<uint32_t>((m_runtime.shader_base + value.imm) >> 32u);
|
||||
break;
|
||||
case ScalarValueOp::Phi: {
|
||||
const auto first = std::find_if(value.phi_args.begin(), value.phi_args.end(),
|
||||
[id](uint32_t arg) { return arg != id; });
|
||||
if (first == value.phi_args.end()) {
|
||||
return Fail(error, fmt::format("empty scalar phi {}", id));
|
||||
}
|
||||
if (!Evaluate(*first, out, error)) {
|
||||
return false;
|
||||
}
|
||||
for (const auto arg: value.phi_args) {
|
||||
if (arg == id || arg == *first) {
|
||||
continue;
|
||||
}
|
||||
uint32_t other = 0;
|
||||
if (!Evaluate(arg, other, error)) {
|
||||
return false;
|
||||
}
|
||||
if (other != out) {
|
||||
return Fail(error,
|
||||
fmt::format("scalar phi {} has runtime-dependent values", id));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ScalarValueOp::ReadConst:
|
||||
case ScalarValueOp::ReadConstBuffer:
|
||||
if (!Read(value, &out, error)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case ScalarValueOp::Undefined:
|
||||
case ScalarValueOp::Unknown:
|
||||
return Fail(error, fmt::format("scalar value {} is unresolved", id));
|
||||
default:
|
||||
if (!EvaluateOperation(value, &out, error)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
m_state[id] = 2;
|
||||
m_values[id] = out;
|
||||
result = out;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool Fail(std::string* error, const std::string& message) const {
|
||||
if (error != nullptr) {
|
||||
*error = Diagnostic(m_program, m_use_pc, message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EvaluateOperation(const ScalarValue& value, uint32_t* result, std::string* error) {
|
||||
const auto count = ScalarValueArgCount(value.op);
|
||||
if (count == 0 || count > 3) {
|
||||
return Fail(error, fmt::format("unsupported scalar operation {}",
|
||||
static_cast<uint32_t>(value.op)));
|
||||
}
|
||||
uint32_t args[3] = {};
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (!Evaluate(value.args[i], args[i], error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return ApplyOperation(value.op, args, result) ||
|
||||
Fail(error, fmt::format("unsupported scalar operation {}",
|
||||
static_cast<uint32_t>(value.op)));
|
||||
}
|
||||
|
||||
bool Read(const ScalarValue& value, uint32_t* result, std::string* error) {
|
||||
const bool buffer = value.op == ScalarValueOp::ReadConstBuffer;
|
||||
uint32_t lo = 0;
|
||||
uint32_t hi = 0;
|
||||
uint32_t offset = 0;
|
||||
if (!Evaluate(value.args[0], lo, error) || !Evaluate(value.args[1], hi, error) ||
|
||||
!Evaluate(value.args[buffer ? 4u : 2u], offset, error)) {
|
||||
return false;
|
||||
}
|
||||
const auto base = (static_cast<uint64_t>(hi) << 32u | lo) & AddressMask;
|
||||
const auto immediate = static_cast<int64_t>(static_cast<int32_t>(value.imm));
|
||||
uint64_t address = 0;
|
||||
if (buffer) {
|
||||
uint32_t num_records = 0;
|
||||
uint32_t ignored = 0;
|
||||
if (!Evaluate(value.args[2], num_records, error) ||
|
||||
!Evaluate(value.args[3], ignored, error)) {
|
||||
return false;
|
||||
}
|
||||
if (immediate < 0) {
|
||||
return Fail(error,
|
||||
fmt::format("ReadConstBuffer pc=0x{:08x} has negative immediate {}",
|
||||
value.pc, immediate));
|
||||
}
|
||||
const auto byte_offset = static_cast<uint64_t>(immediate) + offset;
|
||||
const auto aligned_offset = byte_offset & ~uint64_t {3};
|
||||
const auto stride = (hi >> 16u) & 0x3fffu;
|
||||
const auto size = stride == 0 ? static_cast<uint64_t>(num_records)
|
||||
: static_cast<uint64_t>(stride) * num_records;
|
||||
if (aligned_offset > size || size - aligned_offset < sizeof(uint32_t)) {
|
||||
return Fail(error,
|
||||
fmt::format("ReadConstBuffer pc=0x{:08x} offset={} exceeds size={}",
|
||||
value.pc, aligned_offset, size));
|
||||
}
|
||||
address = ((base & ~uint64_t {3}) + byte_offset) & ~uint64_t {3};
|
||||
} else {
|
||||
const auto base_aligned = base & ~uint64_t {3};
|
||||
const auto immediate_aligned = immediate & ~int64_t {3};
|
||||
const auto relative = immediate_aligned + static_cast<int64_t>(offset & ~uint32_t {3});
|
||||
if (!AddSignedAddress(base_aligned, relative, &address)) {
|
||||
return Fail(error,
|
||||
fmt::format("ReadConst pc=0x{:08x} address base=0x{:016x} offset={} "
|
||||
"is outside the 48-bit address space",
|
||||
value.pc, base_aligned, relative));
|
||||
}
|
||||
}
|
||||
const auto read_memory =
|
||||
m_runtime.read_memory != nullptr ? m_runtime.read_memory : HostMemoryReadDword;
|
||||
if (!read_memory(m_runtime.userdata, address, result)) {
|
||||
return Fail(
|
||||
error, fmt::format("ReadConst pc=0x{:08x} failed at 0x{:016x}", value.pc, address));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const Program& m_program;
|
||||
const SrtRuntime& m_runtime;
|
||||
uint32_t m_use_pc;
|
||||
std::vector<uint32_t> m_values;
|
||||
std::vector<uint8_t> m_state;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool FoldScalarConstant(const ScalarProvenance& provenance, uint32_t value, uint32_t* result) {
|
||||
if (result == nullptr) {
|
||||
return false;
|
||||
}
|
||||
ConstantFolder folder(provenance);
|
||||
return folder.Fold(value, *result);
|
||||
}
|
||||
|
||||
bool BuildSrtPlan(Program* program, std::string* error) {
|
||||
if (program == nullptr) {
|
||||
if (error != nullptr) {
|
||||
*error = "invalid SRT program";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (program->resource_tracking_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = "cannot rebuild SRT plan after resource tracking";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (program->srt_patching_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = "cannot rebuild SRT plan after SRT patching";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
program->srt_plan_complete = false;
|
||||
if (!PlanBuilder(program).Run(error)) {
|
||||
return false;
|
||||
}
|
||||
program->srt_plan_complete = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EvaluateDescriptorSource(const Program& program, uint32_t source, uint32_t use_pc,
|
||||
const SrtRuntime& runtime, DescriptorValue* result,
|
||||
std::string* error) {
|
||||
if (result == nullptr) {
|
||||
if (error != nullptr) {
|
||||
*error = Diagnostic(program, use_pc, "invalid descriptor result");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const DescriptorSourceRequest request {source, use_pc};
|
||||
std::vector<DescriptorValue> results;
|
||||
if (!EvaluateDescriptorSources(program, std::span {&request, 1}, runtime, &results, error)) {
|
||||
return false;
|
||||
}
|
||||
*result = results[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool EvaluateRuntimeSourcesImpl(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime,
|
||||
std::vector<DescriptorValue>* results,
|
||||
std::vector<uint32_t>* flat, bool evaluate_flat,
|
||||
std::string* error) {
|
||||
if (results == nullptr || flat == nullptr || !program.srt_plan_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = Diagnostic(program, 0,
|
||||
results == nullptr || flat == nullptr
|
||||
? "invalid runtime evaluation output"
|
||||
: "SRT plan is not ready");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
std::vector<DescriptorValue> evaluated;
|
||||
evaluated.reserve(requests.size());
|
||||
std::vector<uint32_t> flattened(evaluate_flat ? program.srt.reads.size() : 0u);
|
||||
Evaluator evaluator(program, runtime, 0);
|
||||
for (const auto& request: requests) {
|
||||
const auto* descriptor = GetDescriptorSource(program, request.source);
|
||||
const auto dynamic =
|
||||
std::find(program.srt.dynamic_sources.begin(), program.srt.dynamic_sources.end(),
|
||||
request.source) != program.srt.dynamic_sources.end();
|
||||
if (descriptor == nullptr || dynamic ||
|
||||
!DescriptorSourceResolved(program, request.source)) {
|
||||
if (error != nullptr) {
|
||||
*error = Diagnostic(program, request.use_pc,
|
||||
fmt::format("descriptor source {} is {}", request.source,
|
||||
dynamic ? "GPU-dynamic" : "unresolved"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
evaluator.SetUsePc(request.use_pc);
|
||||
auto value = *descriptor;
|
||||
for (uint32_t i = 0; i < descriptor->dword_count; i++) {
|
||||
if (!evaluator.Evaluate(descriptor->dwords[i], value.dwords[i], error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
evaluated.push_back(value);
|
||||
}
|
||||
if (evaluate_flat) {
|
||||
for (const auto& read: program.srt.reads) {
|
||||
evaluator.SetUsePc(read.use_pc);
|
||||
if (read.flat_offset >= flattened.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = Diagnostic(program, read.use_pc, "invalid flat SRT offset");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!evaluator.Evaluate(read.value, flattened[read.flat_offset], error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
*results = std::move(evaluated);
|
||||
if (evaluate_flat) {
|
||||
*flat = std::move(flattened);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EvaluateDescriptorSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>* results,
|
||||
std::string* error) {
|
||||
if (results == nullptr) {
|
||||
if (error != nullptr) {
|
||||
*error = Diagnostic(program, 0, "invalid descriptor results");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
std::vector<uint32_t> ignored;
|
||||
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, &ignored, false, error);
|
||||
}
|
||||
|
||||
bool EvaluateRuntimeSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>* results,
|
||||
std::vector<uint32_t>* flat, std::string* error) {
|
||||
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, flat, true, error);
|
||||
}
|
||||
|
||||
bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector<uint32_t>* flat,
|
||||
std::string* error) {
|
||||
if (flat == nullptr) {
|
||||
if (error != nullptr) {
|
||||
*error = Diagnostic(program, 0, "invalid SRT walker output");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
std::vector<DescriptorValue> ignored;
|
||||
return EvaluateRuntimeSources(program, {}, runtime, &ignored, flat, error);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -0,0 +1,55 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SRTWALKER_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SRTWALKER_H_
|
||||
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
using SrtMemoryReader = bool (*)(void* userdata, uint64_t address, uint32_t* value);
|
||||
|
||||
struct SrtRuntime {
|
||||
std::span<const uint32_t> user_data;
|
||||
uint64_t shader_base = 0;
|
||||
SrtMemoryReader read_memory = nullptr;
|
||||
void* userdata = nullptr;
|
||||
std::optional<uint64_t> flat_memory_base;
|
||||
};
|
||||
|
||||
struct DescriptorSourceRequest {
|
||||
uint32_t source = 0;
|
||||
uint32_t use_pc = 0;
|
||||
};
|
||||
|
||||
bool FoldScalarConstant(const ScalarProvenance& provenance, uint32_t value, uint32_t* result);
|
||||
|
||||
// Collects reachable ReadConst values. Immediate offsets receive compact flat-buffer slots;
|
||||
// dynamic offsets remain explicit and are never assigned a fake slot.
|
||||
bool BuildSrtPlan(Program* program, std::string* error);
|
||||
|
||||
bool EvaluateDescriptorSource(const Program& program, uint32_t source, uint32_t use_pc,
|
||||
const SrtRuntime& runtime, DescriptorValue* result,
|
||||
std::string* error);
|
||||
|
||||
// Evaluates one runtime snapshot transactionally. Scalar values and ReadConst results shared by
|
||||
// several descriptors are memoized once across the batch.
|
||||
bool EvaluateDescriptorSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>* results,
|
||||
std::string* error);
|
||||
|
||||
// Evaluates descriptor sources and the flattened immediate SRT with one memoized scalar walk.
|
||||
// On failure neither destination is changed.
|
||||
bool EvaluateRuntimeSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>* results,
|
||||
std::vector<uint32_t>* flat, std::string* error);
|
||||
|
||||
bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector<uint32_t>* flat,
|
||||
std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SRTWALKER_H_ */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,23 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_VECTORALUOPS_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_VECTORALUOPS_H_
|
||||
|
||||
#include "graphics/shader/recompiler/ShaderDecoder.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
|
||||
bool DecodeVop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error);
|
||||
bool DecodeVop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error);
|
||||
bool DecodeVopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error);
|
||||
bool DecodeVop3(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction* inst,
|
||||
std::string* error);
|
||||
bool DecodeVop3p(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction* inst, std::string* error);
|
||||
bool DecodeVintrp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
Instruction* inst, std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_VECTORALUOPS_H_ */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,57 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_SHADER_RECOMPILER_SHADERIR_SHADERIRINTERNAL_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_SHADER_RECOMPILER_SHADERIR_SHADERIRINTERNAL_H_
|
||||
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
void SetError(std::string* error, const char* message);
|
||||
|
||||
Opcode LookupIrOpcode(Decoder::Opcode opcode);
|
||||
bool IsImplemented(Decoder::Opcode opcode);
|
||||
bool IsReversedBinary(Decoder::Opcode opcode);
|
||||
bool IsVectorCarryOutOpcode(Decoder::Opcode opcode);
|
||||
bool ScalarResultWritesSccNonZero(Decoder::Opcode opcode);
|
||||
bool ScalarResultIs64Bit(Decoder::Opcode opcode);
|
||||
|
||||
Operand MakeSccOperand();
|
||||
Operand MakeImmediateU32(uint32_t value);
|
||||
Operand MakePcRelativeU32(uint32_t value);
|
||||
bool AppendScalarResultSccNonZero(const Decoder::Instruction& decoded, BasicBlock* block,
|
||||
std::string* error);
|
||||
uint32_t VectorByteConvertIndex(Decoder::Opcode opcode);
|
||||
void CopyOperandModifiers(const Decoder::Operand& decoded, Operand& operand);
|
||||
bool LowerRegisterOperand(const Decoder::Operand& decoded, Operand* operand,
|
||||
std::string* error);
|
||||
bool LowerSourceOperand(const Decoder::Operand& decoded, Operand* operand, std::string* error);
|
||||
void ApplyDppDestinationMask(const Decoder::Instruction& decoded, Operand& dst);
|
||||
uint32_t ResourceIndexFromOperand(const Decoder::Operand& operand);
|
||||
MemoryInfo MemoryInfoFromDecoded(const Decoder::Instruction& decoded, ResourceKind kind);
|
||||
uint32_t RawScalarLoadBase(const Decoder::Operand& operand);
|
||||
void ClearRegisterOffsetModifiers(Decoder::Operand& operand);
|
||||
bool TryGetScalarDestinationCode(const Decoder::Operand& operand, uint32_t& code);
|
||||
bool TryOffsetScalarDestination(const Decoder::Operand& operand, uint32_t index,
|
||||
Decoder::Operand* result);
|
||||
Decoder::Operand OffsetDecodedRegister(const Decoder::Operand& operand, uint32_t index);
|
||||
MemoryInfo OffsetMemoryInfo(const Decoder::Instruction& decoded, ResourceKind kind,
|
||||
uint32_t dword_index);
|
||||
uint32_t TypedBufferComponentOffsetBytes(const Decoder::Instruction& decoded,
|
||||
uint32_t component_index);
|
||||
uint32_t TypedBufferFormatComponentCount(const Decoder::Instruction& decoded);
|
||||
bool LowerMoveImmediateU32(uint32_t pc, const Decoder::Operand& dst, uint32_t value,
|
||||
BasicBlock* block, std::string* error);
|
||||
MemoryInfo OffsetBufferMemoryInfo(const Decoder::Instruction& decoded,
|
||||
uint32_t component_index);
|
||||
MemoryInfo ByteOffsetMemoryInfo(const Decoder::Instruction& decoded, ResourceKind kind,
|
||||
uint32_t byte_offset);
|
||||
ResourceKind FlatSegmentResourceKind(uint32_t segment);
|
||||
const Decoder::Operand& DecodedSourceAt(const Decoder::Instruction& decoded, uint32_t index);
|
||||
Decoder::Operand M0Operand();
|
||||
|
||||
bool IsMemoryOpcode(Decoder::Opcode opcode);
|
||||
bool LowerMemoryInstruction(const Decoder::Instruction& decoded, BasicBlock* block,
|
||||
std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_SHADER_RECOMPILER_SHADERIR_SHADERIRINTERNAL_H_
|
||||
@@ -0,0 +1,559 @@
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
|
||||
#include "graphics/shader/recompiler/shaderIR/ShaderIRInternal.h"
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
std::string RegisterToString(Register reg) {
|
||||
switch (reg.file) {
|
||||
case RegisterFile::Scalar: return fmt::format("s{}", reg.index);
|
||||
case RegisterFile::Vector: return fmt::format("v{}", reg.index);
|
||||
case RegisterFile::Vcc:
|
||||
return reg.index == 0 ? std::string("vcc_lo") : std::string("vcc_hi");
|
||||
case RegisterFile::Exec:
|
||||
return reg.index == 0 ? std::string("exec_lo") : std::string("exec_hi");
|
||||
case RegisterFile::Scc:
|
||||
if (reg.index == static_cast<uint32_t>(Decoder::OperandKind::Scc)) {
|
||||
return "scc";
|
||||
}
|
||||
if (reg.index == static_cast<uint32_t>(Decoder::OperandKind::VccZ)) {
|
||||
return "vccz";
|
||||
}
|
||||
if (reg.index == static_cast<uint32_t>(Decoder::OperandKind::ExecZ)) {
|
||||
return "execz";
|
||||
}
|
||||
return fmt::format("scc_like_{}", reg.index);
|
||||
case RegisterFile::M0: return "m0";
|
||||
default: return "unknown_reg";
|
||||
}
|
||||
}
|
||||
|
||||
std::string OperandToString(const Operand& operand) {
|
||||
std::string text;
|
||||
switch (operand.kind) {
|
||||
case OperandKind::Register: text = RegisterToString(operand.reg); break;
|
||||
case OperandKind::ImmediateU32: text = fmt::format("0x{:08x}", operand.imm); break;
|
||||
case OperandKind::PcRelativeU32: text = fmt::format("pc_rel(0x{:08x})", operand.imm); break;
|
||||
case OperandKind::Null: text = "null"; break;
|
||||
default: text = "unknown"; break;
|
||||
}
|
||||
if (operand.sdwa_sel != 6 || operand.sdwa_sext) {
|
||||
text += fmt::format(".sdwa(sel={},sext={})", operand.sdwa_sel, operand.sdwa_sext ? 1u : 0u);
|
||||
}
|
||||
if (operand.absolute) {
|
||||
text += ".abs";
|
||||
}
|
||||
if (operand.negate) {
|
||||
text += ".neg";
|
||||
}
|
||||
if (operand.op_sel || operand.op_sel_hi || operand.negate_hi) {
|
||||
text += fmt::format(".opsel(lo={},hi={},neghi={})", operand.op_sel ? 1u : 0u,
|
||||
operand.op_sel_hi ? 1u : 0u, operand.negate_hi ? 1u : 0u);
|
||||
}
|
||||
if (operand.omod != 0) {
|
||||
text += fmt::format(".omod({})", operand.omod);
|
||||
}
|
||||
if (operand.clamp) {
|
||||
text += ".clamp";
|
||||
}
|
||||
if (operand.dpp) {
|
||||
text += fmt::format(".dpp(ctrl=0x{:x},fi={},bc={})", operand.dpp_ctrl,
|
||||
operand.dpp_fetch_inactive ? 1u : 0u, operand.dpp_bound_ctrl ? 1u : 0u);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string ResourceKindToString(ResourceKind kind) {
|
||||
switch (kind) {
|
||||
case ResourceKind::ScalarBuffer: return "scalar_buffer";
|
||||
case ResourceKind::Buffer: return "buffer";
|
||||
case ResourceKind::Flat: return "flat";
|
||||
case ResourceKind::Global: return "global";
|
||||
case ResourceKind::Scratch: return "scratch";
|
||||
case ResourceKind::Lds: return "lds";
|
||||
case ResourceKind::Gds: return "gds";
|
||||
case ResourceKind::Image: return "image";
|
||||
case ResourceKind::ImageUint: return "image_uint";
|
||||
case ResourceKind::StorageImage: return "storage_image";
|
||||
case ResourceKind::StorageImageUint: return "storage_image_uint";
|
||||
case ResourceKind::Sampler: return "sampler";
|
||||
default: return "none";
|
||||
}
|
||||
}
|
||||
|
||||
const char* ImageDimensionToString(Decoder::ImageDimension dimension) {
|
||||
switch (dimension) {
|
||||
case Decoder::ImageDimension::Dim2D: return "2d";
|
||||
case Decoder::ImageDimension::Dim3D: return "3d";
|
||||
case Decoder::ImageDimension::Dim2DArray: return "2d_array";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
std::string MemoryInfoToString(const MemoryInfo& mem) {
|
||||
if (mem.kind == ResourceKind::None) {
|
||||
return "";
|
||||
}
|
||||
std::string text = fmt::format(
|
||||
" ; {} resource={} sampler={} offset={} offset2={} dmask=0x{:x} data_dwords={} "
|
||||
"data_bits={} component={}/{} dfmt={} nfmt={} signed={} typed={} formatted={} segment={} "
|
||||
"glc={} slc={} idxen={} offen={} image_flags=0x{:x} image_dim={} image_addr={} "
|
||||
"image_mip={}",
|
||||
ResourceKindToString(mem.kind).c_str(), mem.resource, mem.sampler, mem.offset,
|
||||
mem.secondary_offset, mem.dmask, mem.data_dwords, mem.data_bits, mem.component_index,
|
||||
mem.component_count, mem.data_format, mem.number_format, mem.data_signed ? 1u : 0u,
|
||||
mem.typed ? 1u : 0u, mem.formatted ? 1u : 0u, mem.memory_segment, mem.glc ? 1u : 0u,
|
||||
mem.slc ? 1u : 0u, mem.idxen ? 1u : 0u, mem.offen ? 1u : 0u, mem.image_sample_flags,
|
||||
ImageDimensionToString(mem.image_dimension), mem.image_address_components,
|
||||
mem.image_has_mip ? 1u : 0u);
|
||||
if (mem.image_nsa_dwords != 0) {
|
||||
text += fmt::format(" image_nsa_dwords={} image_nsa_addr=", mem.image_nsa_dwords);
|
||||
const auto count =
|
||||
std::min<uint32_t>(mem.image_nsa_dwords * 4u, Decoder::MaxImageNsaAddressComponents);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (i != 0) {
|
||||
text += ",";
|
||||
}
|
||||
text += fmt::format("{}", mem.image_nsa_addr[i]);
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string ExportInfoToString(const ExportInfo& exp) {
|
||||
return fmt::format(" ; target={} index={} raw=0x{:02x} en=0x{:x} done={} compr={} vm={}",
|
||||
ExportTargetKindToString(exp.kind).c_str(), exp.index, exp.target, exp.en,
|
||||
exp.done ? 1u : 0u, exp.compr ? 1u : 0u, exp.vm ? 1u : 0u);
|
||||
}
|
||||
|
||||
std::string InputInfoToString(Opcode op, const InputInfo& input) {
|
||||
if (op != Opcode::LoadInputF32) {
|
||||
return "";
|
||||
}
|
||||
return fmt::format(" ; input_attr={} input_chan={}", input.attr, input.chan);
|
||||
}
|
||||
|
||||
std::string TerminatorToString(const CFG::Terminator& term) {
|
||||
return fmt::format(
|
||||
"term={} cond={} true={} false={} merge={} continue={} loop={} indirect_sgpr={} "
|
||||
"indirect_selector={} indirect_targets={} selector_values={}",
|
||||
static_cast<uint32_t>(term.kind), CFG::BranchConditionToString(term.condition).c_str(),
|
||||
term.true_block, term.false_block, term.merge_block, term.continue_block,
|
||||
term.loop_header ? 1u : 0u, term.indirect_pc_sgpr, term.indirect_selector_code,
|
||||
static_cast<uint32_t>(term.indirect_targets.size()),
|
||||
static_cast<uint32_t>(term.indirect_selector_values.size()));
|
||||
}
|
||||
|
||||
std::string ExportSourcesToString(const Instruction& inst) {
|
||||
std::string text;
|
||||
for (uint32_t i = 0; i < inst.src_count && i < 4u; i++) {
|
||||
text += i == 0 ? " " : ", ";
|
||||
text += OperandToString(inst.src[i]);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string InstructionToString(const Instruction& inst) {
|
||||
std::string text = fmt::format("0x{:08x}: ", inst.pc);
|
||||
if (inst.op == Opcode::Export) {
|
||||
text += "Export";
|
||||
text += ExportSourcesToString(inst);
|
||||
text += ExportInfoToString(inst.export_info);
|
||||
return text;
|
||||
}
|
||||
switch (inst.op) {
|
||||
case Opcode::ControlNop: text += "ControlNop"; break;
|
||||
case Opcode::Waitcnt: text += "Waitcnt"; break;
|
||||
case Opcode::Barrier: text += "Barrier"; break;
|
||||
case Opcode::Sendmsg: text += "Sendmsg"; break;
|
||||
case Opcode::TtraceData: text += "TtraceData"; break;
|
||||
case Opcode::InstPrefetch: text += "InstPrefetch"; break;
|
||||
case Opcode::MoveU32: text += "MoveU32"; break;
|
||||
case Opcode::MoveF32Bits: text += "MoveF32Bits"; break;
|
||||
case Opcode::MoveRelDestU32: text += "MoveRelDestU32"; break;
|
||||
case Opcode::MoveRelSourceU32: text += "MoveRelSourceU32"; break;
|
||||
case Opcode::MoveU64: text += "MoveU64"; break;
|
||||
case Opcode::WqmB64: text += "WqmB64"; break;
|
||||
case Opcode::SaveexecB32: text += "SaveexecB32"; break;
|
||||
case Opcode::SaveexecB64: text += "SaveexecB64"; break;
|
||||
case Opcode::ReadFirstLaneU32: text += "ReadFirstLaneU32"; break;
|
||||
case Opcode::ReadLaneU32: text += "ReadLaneU32"; break;
|
||||
case Opcode::WriteLaneU32: text += "WriteLaneU32"; break;
|
||||
case Opcode::Permlane16B32: text += "Permlane16B32"; break;
|
||||
case Opcode::Permlanex16B32: text += "Permlanex16B32"; break;
|
||||
case Opcode::AbsI32: text += "AbsI32"; break;
|
||||
case Opcode::IAddU32: text += "IAddU32"; break;
|
||||
case Opcode::IAddCarryU32: text += "IAddCarryU32"; break;
|
||||
case Opcode::ISubBorrowU32: text += "ISubBorrowU32"; break;
|
||||
case Opcode::ScalarAddCarryU32: text += "ScalarAddCarryU32"; break;
|
||||
case Opcode::ScalarSubBorrowU32: text += "ScalarSubBorrowU32"; break;
|
||||
case Opcode::ScalarSubBorrowCarryU32: text += "ScalarSubBorrowCarryU32"; break;
|
||||
case Opcode::ScalarSignedAddOverflowI32: text += "ScalarSignedAddOverflowI32"; break;
|
||||
case Opcode::ScalarSignedSubOverflowI32: text += "ScalarSignedSubOverflowI32"; break;
|
||||
case Opcode::ScalarShiftLeftAddCarryU32: text += "ScalarShiftLeftAddCarryU32"; break;
|
||||
case Opcode::ISubU32: text += "ISubU32"; break;
|
||||
case Opcode::IMulU32: text += "IMulU32"; break;
|
||||
case Opcode::UMulHighU32: text += "UMulHighU32"; break;
|
||||
case Opcode::SMulHighI32: text += "SMulHighI32"; break;
|
||||
case Opcode::IMadI24U32: text += "IMadI24U32"; break;
|
||||
case Opcode::UMadU24U32: text += "UMadU24U32"; break;
|
||||
case Opcode::UMadU64U32: text += "UMadU64U32"; break;
|
||||
case Opcode::SadU32: text += "SadU32"; break;
|
||||
case Opcode::IAdd3U32: text += "IAdd3U32"; break;
|
||||
case Opcode::IMulI24U32: text += "IMulI24U32"; break;
|
||||
case Opcode::UMulU24U32: text += "UMulU24U32"; break;
|
||||
case Opcode::IMinI32: text += "IMinI32"; break;
|
||||
case Opcode::IMaxI32: text += "IMaxI32"; break;
|
||||
case Opcode::IMin3I32: text += "IMin3I32"; break;
|
||||
case Opcode::IMax3I32: text += "IMax3I32"; break;
|
||||
case Opcode::IMed3I32: text += "IMed3I32"; break;
|
||||
case Opcode::UMinU32: text += "UMinU32"; break;
|
||||
case Opcode::UMaxU32: text += "UMaxU32"; break;
|
||||
case Opcode::UMin3U32: text += "UMin3U32"; break;
|
||||
case Opcode::UMax3U32: text += "UMax3U32"; break;
|
||||
case Opcode::UMed3U32: text += "UMed3U32"; break;
|
||||
case Opcode::BitwiseAndU32: text += "BitwiseAndU32"; break;
|
||||
case Opcode::BitwiseAndU64: text += "BitwiseAndU64"; break;
|
||||
case Opcode::BitwiseAndNotU32: text += "BitwiseAndNotU32"; break;
|
||||
case Opcode::BitwiseAndNotU64: text += "BitwiseAndNotU64"; break;
|
||||
case Opcode::BitwiseOrU32: text += "BitwiseOrU32"; break;
|
||||
case Opcode::BitwiseOrU64: text += "BitwiseOrU64"; break;
|
||||
case Opcode::BitwiseOrNotU32: text += "BitwiseOrNotU32"; break;
|
||||
case Opcode::BitwiseOrNotU64: text += "BitwiseOrNotU64"; break;
|
||||
case Opcode::BitwiseAndOrU32: text += "BitwiseAndOrU32"; break;
|
||||
case Opcode::BitwiseOr3U32: text += "BitwiseOr3U32"; break;
|
||||
case Opcode::BitwiseXorU32: text += "BitwiseXorU32"; break;
|
||||
case Opcode::BitwiseXorU64: text += "BitwiseXorU64"; break;
|
||||
case Opcode::BitwiseXor3U32: text += "BitwiseXor3U32"; break;
|
||||
case Opcode::BitwiseNandU32: text += "BitwiseNandU32"; break;
|
||||
case Opcode::BitwiseNandU64: text += "BitwiseNandU64"; break;
|
||||
case Opcode::BitwiseNorU32: text += "BitwiseNorU32"; break;
|
||||
case Opcode::BitwiseNorU64: text += "BitwiseNorU64"; break;
|
||||
case Opcode::BitwiseXnorU32: text += "BitwiseXnorU32"; break;
|
||||
case Opcode::BitwiseXnorU64: text += "BitwiseXnorU64"; break;
|
||||
case Opcode::BitwiseNotU32: text += "BitwiseNotU32"; break;
|
||||
case Opcode::BitwiseNotU64: text += "BitwiseNotU64"; break;
|
||||
case Opcode::BitClearU32: text += "BitClearU32"; break;
|
||||
case Opcode::BitSetU32: text += "BitSetU32"; break;
|
||||
case Opcode::BitReverseU32: text += "BitReverseU32"; break;
|
||||
case Opcode::BitCountU32: text += "BitCountU32"; break;
|
||||
case Opcode::BitCountU64: text += "BitCountU64"; break;
|
||||
case Opcode::BitReplicateB64B32: text += "BitReplicateB64B32"; break;
|
||||
case Opcode::BitCountAddU32: text += "BitCountAddU32"; break;
|
||||
case Opcode::MaskedBitCountLowU32: text += "MaskedBitCountLowU32"; break;
|
||||
case Opcode::MaskedBitCountHighU32: text += "MaskedBitCountHighU32"; break;
|
||||
case Opcode::FindLsbU32: text += "FindLsbU32"; break;
|
||||
case Opcode::FindMsbFromHighU32: text += "FindMsbFromHighU32"; break;
|
||||
case Opcode::FindMsbFromHighU64: text += "FindMsbFromHighU64"; break;
|
||||
case Opcode::BitFieldMaskU32: text += "BitFieldMaskU32"; break;
|
||||
case Opcode::BitFieldMaskU64: text += "BitFieldMaskU64"; break;
|
||||
case Opcode::BitFieldExtractU32: text += "BitFieldExtractU32"; break;
|
||||
case Opcode::BitFieldExtractU64: text += "BitFieldExtractU64"; break;
|
||||
case Opcode::BitFieldExtract3U32: text += "BitFieldExtract3U32"; break;
|
||||
case Opcode::BitFieldExtract3I32: text += "BitFieldExtract3I32"; break;
|
||||
case Opcode::BitFieldInsertSelectU32: text += "BitFieldInsertSelectU32"; break;
|
||||
case Opcode::BitCompare0B32: text += "BitCompare0B32"; break;
|
||||
case Opcode::BitCompare1B32: text += "BitCompare1B32"; break;
|
||||
case Opcode::AlignBitU32: text += "AlignBitU32"; break;
|
||||
case Opcode::ShiftLeftAddU32: text += "ShiftLeftAddU32"; break;
|
||||
case Opcode::AddShiftLeftU32: text += "AddShiftLeftU32"; break;
|
||||
case Opcode::XorAddU32: text += "XorAddU32"; break;
|
||||
case Opcode::ShiftLeftOrU32: text += "ShiftLeftOrU32"; break;
|
||||
case Opcode::ShiftLeftLogicalU32: text += "ShiftLeftLogicalU32"; break;
|
||||
case Opcode::ShiftLeftLogicalU64: text += "ShiftLeftLogicalU64"; break;
|
||||
case Opcode::ShiftLeftLogicalU16: text += "ShiftLeftLogicalU16"; break;
|
||||
case Opcode::ShiftRightLogicalU32: text += "ShiftRightLogicalU32"; break;
|
||||
case Opcode::ShiftRightLogicalU64: text += "ShiftRightLogicalU64"; break;
|
||||
case Opcode::ShiftRightLogicalU16: text += "ShiftRightLogicalU16"; break;
|
||||
case Opcode::ShiftRightArithmeticI32: text += "ShiftRightArithmeticI32"; break;
|
||||
case Opcode::ShiftRightArithmeticI16: text += "ShiftRightArithmeticI16"; break;
|
||||
case Opcode::SelectU32: text += "SelectU32"; break;
|
||||
case Opcode::SelectMaskU32: text += "SelectMaskU32"; break;
|
||||
case Opcode::SelectF32Bits: text += "SelectF32Bits"; break;
|
||||
case Opcode::SelectMaskF32Bits: text += "SelectMaskF32Bits"; break;
|
||||
case Opcode::SelectU64: text += "SelectU64"; break;
|
||||
case Opcode::PackLowLowU16: text += "PackLowLowU16"; break;
|
||||
case Opcode::PackLowHighU16: text += "PackLowHighU16"; break;
|
||||
case Opcode::PackHighHighU16: text += "PackHighHighU16"; break;
|
||||
case Opcode::CompareFalse: text += "CompareFalse"; break;
|
||||
case Opcode::CompareTrue: text += "CompareTrue"; break;
|
||||
case Opcode::CompareEqU32: text += "CompareEqU32"; break;
|
||||
case Opcode::CompareNeU32: text += "CompareNeU32"; break;
|
||||
case Opcode::CompareGtU32: text += "CompareGtU32"; break;
|
||||
case Opcode::CompareGeU32: text += "CompareGeU32"; break;
|
||||
case Opcode::CompareLtU32: text += "CompareLtU32"; break;
|
||||
case Opcode::CompareLeU32: text += "CompareLeU32"; break;
|
||||
case Opcode::CompareNeU64: text += "CompareNeU64"; break;
|
||||
case Opcode::CompareMaskEqU32: text += "CompareMaskEqU32"; break;
|
||||
case Opcode::CompareMaskNeU32: text += "CompareMaskNeU32"; break;
|
||||
case Opcode::CompareMaskGtU32: text += "CompareMaskGtU32"; break;
|
||||
case Opcode::CompareMaskGeU32: text += "CompareMaskGeU32"; break;
|
||||
case Opcode::CompareMaskLtU32: text += "CompareMaskLtU32"; break;
|
||||
case Opcode::CompareMaskLeU32: text += "CompareMaskLeU32"; break;
|
||||
case Opcode::CompareEqI32: text += "CompareEqI32"; break;
|
||||
case Opcode::CompareNeI32: text += "CompareNeI32"; break;
|
||||
case Opcode::CompareGtI32: text += "CompareGtI32"; break;
|
||||
case Opcode::CompareGeI32: text += "CompareGeI32"; break;
|
||||
case Opcode::CompareLtI32: text += "CompareLtI32"; break;
|
||||
case Opcode::CompareLeI32: text += "CompareLeI32"; break;
|
||||
case Opcode::CompareEqI16: text += "CompareEqI16"; break;
|
||||
case Opcode::CompareNeI16: text += "CompareNeI16"; break;
|
||||
case Opcode::CompareGtI16: text += "CompareGtI16"; break;
|
||||
case Opcode::CompareGeI16: text += "CompareGeI16"; break;
|
||||
case Opcode::CompareLtI16: text += "CompareLtI16"; break;
|
||||
case Opcode::CompareLeI16: text += "CompareLeI16"; break;
|
||||
case Opcode::CompareMaskEqI32: text += "CompareMaskEqI32"; break;
|
||||
case Opcode::CompareMaskNeI32: text += "CompareMaskNeI32"; break;
|
||||
case Opcode::CompareMaskGtI32: text += "CompareMaskGtI32"; break;
|
||||
case Opcode::CompareMaskGeI32: text += "CompareMaskGeI32"; break;
|
||||
case Opcode::CompareMaskLtI32: text += "CompareMaskLtI32"; break;
|
||||
case Opcode::CompareMaskLeI32: text += "CompareMaskLeI32"; break;
|
||||
case Opcode::CompareEqU16: text += "CompareEqU16"; break;
|
||||
case Opcode::CompareNeU16: text += "CompareNeU16"; break;
|
||||
case Opcode::CompareGtU16: text += "CompareGtU16"; break;
|
||||
case Opcode::CompareGeU16: text += "CompareGeU16"; break;
|
||||
case Opcode::CompareLtU16: text += "CompareLtU16"; break;
|
||||
case Opcode::CompareLeU16: text += "CompareLeU16"; break;
|
||||
case Opcode::CompareEqF32: text += "CompareEqF32"; break;
|
||||
case Opcode::CompareNeF32: text += "CompareNeF32"; break;
|
||||
case Opcode::CompareGtF32: text += "CompareGtF32"; break;
|
||||
case Opcode::CompareGeF32: text += "CompareGeF32"; break;
|
||||
case Opcode::CompareLtF32: text += "CompareLtF32"; break;
|
||||
case Opcode::CompareLeF32: text += "CompareLeF32"; break;
|
||||
case Opcode::CompareOrderedF32: text += "CompareOrderedF32"; break;
|
||||
case Opcode::CompareUnorderedF32: text += "CompareUnorderedF32"; break;
|
||||
case Opcode::CompareUnordEqF32: text += "CompareUnordEqF32"; break;
|
||||
case Opcode::CompareUnordNeF32: text += "CompareUnordNeF32"; break;
|
||||
case Opcode::CompareUnordGtF32: text += "CompareUnordGtF32"; break;
|
||||
case Opcode::CompareUnordGeF32: text += "CompareUnordGeF32"; break;
|
||||
case Opcode::CompareUnordLtF32: text += "CompareUnordLtF32"; break;
|
||||
case Opcode::CompareUnordLeF32: text += "CompareUnordLeF32"; break;
|
||||
case Opcode::CompareClassF32: text += "CompareClassF32"; break;
|
||||
case Opcode::CompareEqF16: text += "CompareEqF16"; break;
|
||||
case Opcode::CompareNeF16: text += "CompareNeF16"; break;
|
||||
case Opcode::CompareGtF16: text += "CompareGtF16"; break;
|
||||
case Opcode::CompareGeF16: text += "CompareGeF16"; break;
|
||||
case Opcode::CompareLtF16: text += "CompareLtF16"; break;
|
||||
case Opcode::CompareLeF16: text += "CompareLeF16"; break;
|
||||
case Opcode::CompareUnordNeF16: text += "CompareUnordNeF16"; break;
|
||||
case Opcode::CompareMaskEqF16: text += "CompareMaskEqF16"; break;
|
||||
case Opcode::CompareMaskNeF16: text += "CompareMaskNeF16"; break;
|
||||
case Opcode::CompareMaskGtF16: text += "CompareMaskGtF16"; break;
|
||||
case Opcode::CompareMaskGeF16: text += "CompareMaskGeF16"; break;
|
||||
case Opcode::CompareMaskLtF16: text += "CompareMaskLtF16"; break;
|
||||
case Opcode::CompareMaskLeF16: text += "CompareMaskLeF16"; break;
|
||||
case Opcode::CompareMaskUnordNeF16: text += "CompareMaskUnordNeF16"; break;
|
||||
case Opcode::CompareMaskUnordGeF16: text += "CompareMaskUnordGeF16"; break;
|
||||
case Opcode::CompareMaskEqF32: text += "CompareMaskEqF32"; break;
|
||||
case Opcode::CompareMaskNeF32: text += "CompareMaskNeF32"; break;
|
||||
case Opcode::CompareMaskGtF32: text += "CompareMaskGtF32"; break;
|
||||
case Opcode::CompareMaskGeF32: text += "CompareMaskGeF32"; break;
|
||||
case Opcode::CompareMaskLtF32: text += "CompareMaskLtF32"; break;
|
||||
case Opcode::CompareMaskLeF32: text += "CompareMaskLeF32"; break;
|
||||
case Opcode::CompareMaskUnordEqF32: text += "CompareMaskUnordEqF32"; break;
|
||||
case Opcode::CompareMaskUnordNeF32: text += "CompareMaskUnordNeF32"; break;
|
||||
case Opcode::CompareMaskUnordGtF32: text += "CompareMaskUnordGtF32"; break;
|
||||
case Opcode::CompareMaskUnordGeF32: text += "CompareMaskUnordGeF32"; break;
|
||||
case Opcode::CompareMaskUnordLtF32: text += "CompareMaskUnordLtF32"; break;
|
||||
case Opcode::CompareMaskUnordLeF32: text += "CompareMaskUnordLeF32"; break;
|
||||
case Opcode::ConvertByteU32ToF32: text += "ConvertByteU32ToF32"; break;
|
||||
case Opcode::ConvertU32ToF32: text += "ConvertU32ToF32"; break;
|
||||
case Opcode::ConvertI32ToF32: text += "ConvertI32ToF32"; break;
|
||||
case Opcode::ConvertF32ToU32: text += "ConvertF32ToU32"; break;
|
||||
case Opcode::ConvertF32ToI32: text += "ConvertF32ToI32"; break;
|
||||
case Opcode::ConvertF32ToF16: text += "ConvertF32ToF16"; break;
|
||||
case Opcode::ConvertF16ToF32: text += "ConvertF16ToF32"; break;
|
||||
case Opcode::ConvertU16ToF16: text += "ConvertU16ToF16"; break;
|
||||
case Opcode::ConvertF16ToU16: text += "ConvertF16ToU16"; break;
|
||||
case Opcode::ConvertI16ToF16: text += "ConvertI16ToF16"; break;
|
||||
case Opcode::ConvertF16ToI16: text += "ConvertF16ToI16"; break;
|
||||
case Opcode::ConvertRoundPlusInfF32ToI32: text += "ConvertRoundPlusInfF32ToI32"; break;
|
||||
case Opcode::ConvertFloorF32ToI32: text += "ConvertFloorF32ToI32"; break;
|
||||
case Opcode::ConvertI4ToOffsetF32: text += "ConvertI4ToOffsetF32"; break;
|
||||
case Opcode::LdexpF32: text += "LdexpF32"; break;
|
||||
case Opcode::PackF32ToF16Rtz: text += "PackF32ToF16Rtz"; break;
|
||||
case Opcode::PackSnorm2x16F32: text += "PackSnorm2x16F32"; break;
|
||||
case Opcode::PackUnorm2x16F32: text += "PackUnorm2x16F32"; break;
|
||||
case Opcode::PackU16U32: text += "PackU16U32"; break;
|
||||
case Opcode::PackU8F32: text += "PackU8F32"; break;
|
||||
case Opcode::PackB32F16: text += "PackB32F16"; break;
|
||||
case Opcode::PackedMadI16: text += "PackedMadI16"; break;
|
||||
case Opcode::PackedMulLoU16: text += "PackedMulLoU16"; break;
|
||||
case Opcode::PackedAddI16: text += "PackedAddI16"; break;
|
||||
case Opcode::PackedSubI16: text += "PackedSubI16"; break;
|
||||
case Opcode::PackedLshlrevB16: text += "PackedLshlrevB16"; break;
|
||||
case Opcode::PackedLshrrevB16: text += "PackedLshrrevB16"; break;
|
||||
case Opcode::PackedAshrrevI16: text += "PackedAshrrevI16"; break;
|
||||
case Opcode::PackedMaxI16: text += "PackedMaxI16"; break;
|
||||
case Opcode::PackedMinI16: text += "PackedMinI16"; break;
|
||||
case Opcode::PackedMadU16: text += "PackedMadU16"; break;
|
||||
case Opcode::PackedAddU16: text += "PackedAddU16"; break;
|
||||
case Opcode::PackedSubU16: text += "PackedSubU16"; break;
|
||||
case Opcode::PackedMaxU16: text += "PackedMaxU16"; break;
|
||||
case Opcode::PackedMinU16: text += "PackedMinU16"; break;
|
||||
case Opcode::PackedAddF16: text += "PackedAddF16"; break;
|
||||
case Opcode::PackedMulF16: text += "PackedMulF16"; break;
|
||||
case Opcode::PackedMinF16: text += "PackedMinF16"; break;
|
||||
case Opcode::PackedMaxF16: text += "PackedMaxF16"; break;
|
||||
case Opcode::PackedFmaF16: text += "PackedFmaF16"; break;
|
||||
case Opcode::AddF16: text += "AddF16"; break;
|
||||
case Opcode::SubF16: text += "SubF16"; break;
|
||||
case Opcode::MulF16: text += "MulF16"; break;
|
||||
case Opcode::MinF16: text += "MinF16"; break;
|
||||
case Opcode::MaxF16: text += "MaxF16"; break;
|
||||
case Opcode::FmaF16: text += "FmaF16"; break;
|
||||
case Opcode::MadMixF16: text += "MadMixF16"; break;
|
||||
case Opcode::IAddU16: text += "IAddU16"; break;
|
||||
case Opcode::ISubI16: text += "ISubI16"; break;
|
||||
case Opcode::IMinI16: text += "IMinI16"; break;
|
||||
case Opcode::IMaxI16: text += "IMaxI16"; break;
|
||||
case Opcode::UMinU16: text += "UMinU16"; break;
|
||||
case Opcode::UMaxU16: text += "UMaxU16"; break;
|
||||
case Opcode::RcpF32: text += "RcpF32"; break;
|
||||
case Opcode::FractF32: text += "FractF32"; break;
|
||||
case Opcode::TruncF32: text += "TruncF32"; break;
|
||||
case Opcode::CeilF32: text += "CeilF32"; break;
|
||||
case Opcode::RoundEvenF32: text += "RoundEvenF32"; break;
|
||||
case Opcode::FloorF32: text += "FloorF32"; break;
|
||||
case Opcode::Exp2F32: text += "Exp2F32"; break;
|
||||
case Opcode::Log2F32: text += "Log2F32"; break;
|
||||
case Opcode::InverseSqrtF32: text += "InverseSqrtF32"; break;
|
||||
case Opcode::SqrtF32: text += "SqrtF32"; break;
|
||||
case Opcode::RcpF16: text += "RcpF16"; break;
|
||||
case Opcode::SqrtF16: text += "SqrtF16"; break;
|
||||
case Opcode::InverseSqrtF16: text += "InverseSqrtF16"; break;
|
||||
case Opcode::Log2F16: text += "Log2F16"; break;
|
||||
case Opcode::Exp2F16: text += "Exp2F16"; break;
|
||||
case Opcode::FloorF16: text += "FloorF16"; break;
|
||||
case Opcode::CeilF16: text += "CeilF16"; break;
|
||||
case Opcode::TruncF16: text += "TruncF16"; break;
|
||||
case Opcode::RoundEvenF16: text += "RoundEvenF16"; break;
|
||||
case Opcode::SinF32: text += "SinF32"; break;
|
||||
case Opcode::CosF32: text += "CosF32"; break;
|
||||
case Opcode::CubeIdF32: text += "CubeIdF32"; break;
|
||||
case Opcode::CubeScF32: text += "CubeScF32"; break;
|
||||
case Opcode::CubeTcF32: text += "CubeTcF32"; break;
|
||||
case Opcode::CubeMaF32: text += "CubeMaF32"; break;
|
||||
case Opcode::FAddF32: text += "FAddF32"; break;
|
||||
case Opcode::FSubF32: text += "FSubF32"; break;
|
||||
case Opcode::FMulF32: text += "FMulF32"; break;
|
||||
case Opcode::FMinF32: text += "FMinF32"; break;
|
||||
case Opcode::FMaxF32: text += "FMaxF32"; break;
|
||||
case Opcode::FMadF32: text += "FMadF32"; break;
|
||||
case Opcode::Dot2AccF32F16: text += "Dot2AccF32F16"; break;
|
||||
case Opcode::FMin3F32: text += "FMin3F32"; break;
|
||||
case Opcode::FMax3F32: text += "FMax3F32"; break;
|
||||
case Opcode::FMed3F32: text += "FMed3F32"; break;
|
||||
case Opcode::Min3F16: text += "Min3F16"; break;
|
||||
case Opcode::Max3F16: text += "Max3F16"; break;
|
||||
case Opcode::Med3F16: text += "Med3F16"; break;
|
||||
case Opcode::LoadSrtDword: text += "LoadSrtDword"; break;
|
||||
case Opcode::SLoadDword: text += "SLoadDword"; break;
|
||||
case Opcode::SBufferLoadDword: text += "SBufferLoadDword"; break;
|
||||
case Opcode::BufferLoadUbyte: text += "BufferLoadUbyte"; break;
|
||||
case Opcode::BufferLoadSbyte: text += "BufferLoadSbyte"; break;
|
||||
case Opcode::BufferLoadUshort: text += "BufferLoadUshort"; break;
|
||||
case Opcode::BufferLoadSshort: text += "BufferLoadSshort"; break;
|
||||
case Opcode::BufferLoadDword: text += "BufferLoadDword"; break;
|
||||
case Opcode::BufferStoreByte: text += "BufferStoreByte"; break;
|
||||
case Opcode::BufferStoreShort: text += "BufferStoreShort"; break;
|
||||
case Opcode::BufferStoreDword: text += "BufferStoreDword"; break;
|
||||
case Opcode::AtomicSwapU32: text += "AtomicSwapU32"; break;
|
||||
case Opcode::AtomicAddU32: text += "AtomicAddU32"; break;
|
||||
case Opcode::AtomicSubU32: text += "AtomicSubU32"; break;
|
||||
case Opcode::AtomicSMinI32: text += "AtomicSMinI32"; break;
|
||||
case Opcode::AtomicUMinU32: text += "AtomicUMinU32"; break;
|
||||
case Opcode::AtomicSMaxI32: text += "AtomicSMaxI32"; break;
|
||||
case Opcode::AtomicUMaxU32: text += "AtomicUMaxU32"; break;
|
||||
case Opcode::AtomicAndU32: text += "AtomicAndU32"; break;
|
||||
case Opcode::AtomicOrU32: text += "AtomicOrU32"; break;
|
||||
case Opcode::AtomicXorU32: text += "AtomicXorU32"; break;
|
||||
case Opcode::FlatLoadUbyte: text += "FlatLoadUbyte"; break;
|
||||
case Opcode::FlatLoadSbyte: text += "FlatLoadSbyte"; break;
|
||||
case Opcode::FlatLoadUshort: text += "FlatLoadUshort"; break;
|
||||
case Opcode::FlatLoadSshort: text += "FlatLoadSshort"; break;
|
||||
case Opcode::FlatLoadDword: text += "FlatLoadDword"; break;
|
||||
case Opcode::FlatStoreByte: text += "FlatStoreByte"; break;
|
||||
case Opcode::FlatStoreShort: text += "FlatStoreShort"; break;
|
||||
case Opcode::FlatStoreDword: text += "FlatStoreDword"; break;
|
||||
case Opcode::DsReadUbyte: text += "DsReadUbyte"; break;
|
||||
case Opcode::DsReadSbyte: text += "DsReadSbyte"; break;
|
||||
case Opcode::DsReadUshort: text += "DsReadUshort"; break;
|
||||
case Opcode::DsReadSshort: text += "DsReadSshort"; break;
|
||||
case Opcode::DsReadB32: text += "DsReadB32"; break;
|
||||
case Opcode::DsWriteByte: text += "DsWriteByte"; break;
|
||||
case Opcode::DsWriteShort: text += "DsWriteShort"; break;
|
||||
case Opcode::DsWriteB32: text += "DsWriteB32"; break;
|
||||
case Opcode::DsMinF32: text += "DsMinF32"; break;
|
||||
case Opcode::DsMaxF32: text += "DsMaxF32"; break;
|
||||
case Opcode::DsSwizzleB32: text += "DsSwizzleB32"; break;
|
||||
case Opcode::DsConsume: text += "DsConsume"; break;
|
||||
case Opcode::DsAppend: text += "DsAppend"; break;
|
||||
case Opcode::DsWriteAddtidB32: text += "DsWriteAddtidB32"; break;
|
||||
case Opcode::DsReadAddtidB32: text += "DsReadAddtidB32"; break;
|
||||
case Opcode::ImageGetResinfo: text += "ImageGetResinfo"; break;
|
||||
case Opcode::ImageGetLod: text += "ImageGetLod"; break;
|
||||
case Opcode::ImageLoad: text += "ImageLoad"; break;
|
||||
case Opcode::ImageStore: text += "ImageStore"; break;
|
||||
case Opcode::ImageSample: text += "ImageSample"; break;
|
||||
case Opcode::ImageGather4: text += "ImageGather4"; break;
|
||||
case Opcode::LoadInputF32: text += "LoadInputF32"; break;
|
||||
case Opcode::Export: text += "Export"; break;
|
||||
}
|
||||
text += " ";
|
||||
text += OperandToString(inst.dst);
|
||||
if (inst.dst2.kind != OperandKind::Null) {
|
||||
text += ", ";
|
||||
text += OperandToString(inst.dst2);
|
||||
}
|
||||
for (uint32_t i = 0; i < inst.src_count; i++) {
|
||||
text += i == 0 ? ", " : ", ";
|
||||
text += OperandToString(inst.src[i]);
|
||||
}
|
||||
text += MemoryInfoToString(inst.memory);
|
||||
text += InputInfoToString(inst.op, inst.input_info);
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string ExportTargetKindToString(ExportTargetKind kind) {
|
||||
switch (kind) {
|
||||
case ExportTargetKind::Null: return "null";
|
||||
case ExportTargetKind::Position: return "position";
|
||||
case ExportTargetKind::Primitive: return "primitive";
|
||||
case ExportTargetKind::Parameter: return "parameter";
|
||||
case ExportTargetKind::Mrt: return "mrt";
|
||||
case ExportTargetKind::MrtZ: return "mrtz";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
std::string ProgramToString(const Program& program) {
|
||||
std::string text;
|
||||
text += fmt::format("mode={} masks={} fallback={} reason={}\n",
|
||||
program.dispatcher_fallback ? "dispatcher" : "structured",
|
||||
program.lane_mask_mode == ShaderLaneMaskMode::NativeWave ? "native-wave"
|
||||
: "per-invocation",
|
||||
CFG::FailureKindToString(program.cfg_failure_kind).c_str(),
|
||||
program.fallback_reason.c_str());
|
||||
for (const auto& block: program.blocks) {
|
||||
text += fmt::format("block_{} pc=0x{:08x} end=0x{:08x} preds={} succs={} {}\n", block.id,
|
||||
block.start_pc, block.end_pc,
|
||||
static_cast<uint32_t>(block.predecessors.size()),
|
||||
static_cast<uint32_t>(block.successors.size()),
|
||||
TerminatorToString(block.terminator).c_str());
|
||||
for (const auto& inst: block.instructions) {
|
||||
text += " ";
|
||||
text += InstructionToString(inst);
|
||||
text += "\n";
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -0,0 +1,788 @@
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
|
||||
#include "graphics/shader/recompiler/shaderIR/ShaderIRInternal.h"
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
namespace {
|
||||
|
||||
bool LowerScalarBufferLoadDword(const Decoder::Instruction& decoded, BasicBlock* block, Opcode op,
|
||||
std::string* error) {
|
||||
for (uint32_t i = 0; i < decoded.data_dwords; i++) {
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = op;
|
||||
inst.src_count = 1;
|
||||
inst.memory = OffsetMemoryInfo(decoded, ResourceKind::ScalarBuffer, i);
|
||||
// Raw s_load uses a two-SGPR pointer base; s_buffer_load uses a four-SGPR
|
||||
// descriptor resource index.
|
||||
inst.memory.resource = op == Opcode::SLoadDword ? RawScalarLoadBase(decoded.src0)
|
||||
: ResourceIndexFromOperand(decoded.src0);
|
||||
if (!LowerRegisterOperand(OffsetDecodedRegister(decoded.dst, i), &inst.dst, error) ||
|
||||
!LowerSourceOperand(decoded.src1, &inst.src[0], error)) {
|
||||
return false;
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LowerBufferAddressSources(const Decoder::Instruction& decoded, Instruction* inst,
|
||||
uint32_t first_src, std::string* error) {
|
||||
uint32_t src = first_src;
|
||||
if (decoded.idxen) {
|
||||
if (!LowerSourceOperand(decoded.src0, &inst->src[src++], error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (decoded.offen) {
|
||||
const auto offset_source =
|
||||
decoded.idxen ? OffsetDecodedRegister(decoded.src0, 1) : decoded.src0;
|
||||
if (!LowerSourceOperand(offset_source, &inst->src[src++], error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!LowerSourceOperand(decoded.src2, &inst->src[src++], error)) {
|
||||
return false;
|
||||
}
|
||||
inst->src_count = src;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LowerBufferLoad(const Decoder::Instruction& decoded, BasicBlock* block, std::string* error) {
|
||||
const auto ir_op =
|
||||
decoded.data_bits == 8u
|
||||
? (decoded.data_signed ? Opcode::BufferLoadSbyte : Opcode::BufferLoadUbyte)
|
||||
: decoded.data_bits == 16u
|
||||
? (decoded.data_signed ? Opcode::BufferLoadSshort : Opcode::BufferLoadUshort)
|
||||
: Opcode::BufferLoadDword;
|
||||
const auto count = decoded.data_bits == 32u ? decoded.data_dwords : 1u;
|
||||
const auto typed_format_components = TypedBufferFormatComponentCount(decoded);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (i >= typed_format_components) {
|
||||
if (!LowerMoveImmediateU32(decoded.pc, OffsetDecodedRegister(decoded.dst, i), 0, block,
|
||||
error)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = ir_op;
|
||||
inst.memory = OffsetBufferMemoryInfo(decoded, i);
|
||||
if (!LowerRegisterOperand(OffsetDecodedRegister(decoded.dst, i), &inst.dst, error) ||
|
||||
!LowerBufferAddressSources(decoded, &inst, 0, error)) {
|
||||
return false;
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LowerBufferStore(const Decoder::Instruction& decoded, BasicBlock* block, std::string* error) {
|
||||
const auto ir_op = decoded.data_bits == 8u ? Opcode::BufferStoreByte
|
||||
: decoded.data_bits == 16u ? Opcode::BufferStoreShort
|
||||
: Opcode::BufferStoreDword;
|
||||
auto count = decoded.data_bits == 32u ? decoded.data_dwords : 1u;
|
||||
if (decoded.data_bits == 32u) {
|
||||
const auto typed_format_components = TypedBufferFormatComponentCount(decoded);
|
||||
if (typed_format_components < count) {
|
||||
count = typed_format_components;
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = ir_op;
|
||||
inst.memory = OffsetBufferMemoryInfo(decoded, i);
|
||||
inst.dst.kind = OperandKind::Null;
|
||||
if (!LowerSourceOperand(OffsetDecodedRegister(decoded.dst, i), &inst.src[0], error) ||
|
||||
!LowerBufferAddressSources(decoded, &inst, 1, error)) {
|
||||
return false;
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LowerBufferAtomicDword(const Decoder::Instruction& decoded, BasicBlock* block, Opcode op,
|
||||
std::string* error) {
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = op;
|
||||
inst.memory = MemoryInfoFromDecoded(decoded, ResourceKind::Buffer);
|
||||
inst.dst.kind = OperandKind::Null;
|
||||
if ((decoded.glc && !LowerRegisterOperand(decoded.dst, &inst.dst, error)) ||
|
||||
!LowerSourceOperand(decoded.dst, &inst.src[0], error) ||
|
||||
!LowerBufferAddressSources(decoded, &inst, 1, error)) {
|
||||
return false;
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
return true;
|
||||
}
|
||||
|
||||
ResourceKind DsMemoryKind(const Decoder::Instruction& decoded) {
|
||||
return decoded.gds ? ResourceKind::Gds : ResourceKind::Lds;
|
||||
}
|
||||
|
||||
bool LowerDsRead(const Decoder::Instruction& decoded, BasicBlock* block, std::string* error) {
|
||||
const auto ir_op = decoded.data_bits == 8u
|
||||
? (decoded.data_signed ? Opcode::DsReadSbyte : Opcode::DsReadUbyte)
|
||||
: decoded.data_bits == 16u
|
||||
? (decoded.data_signed ? Opcode::DsReadSshort : Opcode::DsReadUshort)
|
||||
: Opcode::DsReadB32;
|
||||
const auto count = decoded.data_bits == 32u ? decoded.data_dwords : 1u;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = ir_op;
|
||||
inst.src_count = 1;
|
||||
inst.memory = OffsetMemoryInfo(decoded, DsMemoryKind(decoded), i);
|
||||
if (!LowerRegisterOperand(OffsetDecodedRegister(decoded.dst, i), &inst.dst, error) ||
|
||||
!LowerSourceOperand(decoded.src0, &inst.src[0], error)) {
|
||||
return false;
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LowerDsRead2(const Decoder::Instruction& decoded, BasicBlock* block, uint32_t dwords_per_read,
|
||||
std::string* error) {
|
||||
const uint32_t offsets[] = {decoded.offset, decoded.secondary_offset};
|
||||
for (uint32_t read = 0; read < 2u; read++) {
|
||||
for (uint32_t dword = 0; dword < dwords_per_read; dword++) {
|
||||
const auto index = read * dwords_per_read + dword;
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = Opcode::DsReadB32;
|
||||
inst.src_count = 1;
|
||||
inst.memory =
|
||||
ByteOffsetMemoryInfo(decoded, DsMemoryKind(decoded), offsets[read] + dword * 4u);
|
||||
if (!LowerRegisterOperand(OffsetDecodedRegister(decoded.dst, index), &inst.dst,
|
||||
error) ||
|
||||
!LowerSourceOperand(decoded.src0, &inst.src[0], error)) {
|
||||
return false;
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LowerDsAtomicU32(const Decoder::Instruction& decoded, BasicBlock* block, Opcode op,
|
||||
bool return_old, std::string* error) {
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = op;
|
||||
inst.src_count = 2;
|
||||
inst.memory = MemoryInfoFromDecoded(decoded, DsMemoryKind(decoded));
|
||||
inst.dst.kind = return_old ? OperandKind::Register : OperandKind::Null;
|
||||
if ((return_old && !LowerRegisterOperand(decoded.dst, &inst.dst, error)) ||
|
||||
!LowerSourceOperand(decoded.src1, &inst.src[0], error) ||
|
||||
!LowerSourceOperand(decoded.src0, &inst.src[1], error)) {
|
||||
return false;
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LowerDsSwizzleB32(const Decoder::Instruction& decoded, BasicBlock* block, std::string* error) {
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = Opcode::DsSwizzleB32;
|
||||
inst.src_count = 2;
|
||||
inst.src[1].kind = OperandKind::ImmediateU32;
|
||||
inst.src[1].imm = decoded.offset & 0xffffu;
|
||||
if (!LowerRegisterOperand(decoded.dst, &inst.dst, error) ||
|
||||
!LowerSourceOperand(decoded.src0, &inst.src[0], error)) {
|
||||
return false;
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LowerDsFloatMinMaxF32(const Decoder::Instruction& decoded, BasicBlock* block, Opcode op,
|
||||
std::string* error) {
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = op;
|
||||
inst.src_count = 2;
|
||||
inst.memory = MemoryInfoFromDecoded(decoded, DsMemoryKind(decoded));
|
||||
inst.memory.resource = 0;
|
||||
inst.dst.kind = OperandKind::Null;
|
||||
if (!LowerSourceOperand(decoded.src1, &inst.src[0], error) ||
|
||||
!LowerSourceOperand(decoded.src0, &inst.src[1], error) ||
|
||||
!LowerSourceOperand(decoded.src2, &inst.src[2], error)) {
|
||||
return false;
|
||||
}
|
||||
inst.src_count = 3;
|
||||
block->instructions.push_back(inst);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LowerDsWriteAddtidB32(const Decoder::Instruction& decoded, BasicBlock* block,
|
||||
std::string* error) {
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = Opcode::DsWriteAddtidB32;
|
||||
inst.src_count = 2;
|
||||
inst.memory = MemoryInfoFromDecoded(decoded, DsMemoryKind(decoded));
|
||||
inst.memory.resource = 0;
|
||||
inst.dst.kind = OperandKind::Null;
|
||||
const auto m0 = M0Operand();
|
||||
if (!LowerSourceOperand(decoded.src1, &inst.src[0], error) ||
|
||||
!LowerSourceOperand(m0, &inst.src[1], error)) {
|
||||
return false;
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LowerDsReadAddtidB32(const Decoder::Instruction& decoded, BasicBlock* block,
|
||||
std::string* error) {
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = Opcode::DsReadAddtidB32;
|
||||
inst.src_count = 1;
|
||||
inst.memory = MemoryInfoFromDecoded(decoded, DsMemoryKind(decoded));
|
||||
inst.memory.resource = 0;
|
||||
const auto m0 = M0Operand();
|
||||
if (!LowerRegisterOperand(decoded.dst, &inst.dst, error) ||
|
||||
!LowerSourceOperand(m0, &inst.src[0], error)) {
|
||||
return false;
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LowerDsAppendConsume(const Decoder::Instruction& decoded, BasicBlock* block, Opcode op,
|
||||
std::string* error) {
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = op;
|
||||
inst.src_count = 1;
|
||||
inst.memory = MemoryInfoFromDecoded(decoded, DsMemoryKind(decoded));
|
||||
const auto m0 = M0Operand();
|
||||
if (!LowerRegisterOperand(decoded.dst, &inst.dst, error) ||
|
||||
!LowerSourceOperand(m0, &inst.src[0], error)) {
|
||||
return false;
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
return true;
|
||||
}
|
||||
|
||||
Opcode ImageAtomicIrOpcode(Decoder::Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Decoder::Opcode::ImageAtomicAdd: return Opcode::AtomicAddU32;
|
||||
case Decoder::Opcode::ImageAtomicUMin: return Opcode::AtomicUMinU32;
|
||||
case Decoder::Opcode::ImageAtomicUMax: return Opcode::AtomicUMaxU32;
|
||||
case Decoder::Opcode::ImageAtomicAnd: return Opcode::AtomicAndU32;
|
||||
case Decoder::Opcode::ImageAtomicOr: return Opcode::AtomicOrU32;
|
||||
case Decoder::Opcode::ImageAtomicXor: return Opcode::AtomicXorU32;
|
||||
default: return Opcode::AtomicAddU32;
|
||||
}
|
||||
}
|
||||
|
||||
bool LowerImageAtomicU32(const Decoder::Instruction& decoded, BasicBlock* block,
|
||||
std::string* error) {
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = ImageAtomicIrOpcode(decoded.opcode);
|
||||
inst.src_count = 2;
|
||||
inst.memory = MemoryInfoFromDecoded(decoded, ResourceKind::StorageImageUint);
|
||||
inst.memory.sampler = 0;
|
||||
inst.dst.kind = OperandKind::Null;
|
||||
if ((decoded.glc && !LowerRegisterOperand(decoded.dst, &inst.dst, error)) ||
|
||||
!LowerSourceOperand(decoded.dst, &inst.src[0], error) ||
|
||||
!LowerSourceOperand(decoded.src0, &inst.src[1], error)) {
|
||||
return false;
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LowerFlatLoad(const Decoder::Instruction& decoded, BasicBlock* block, std::string* error) {
|
||||
const auto kind = FlatSegmentResourceKind(decoded.memory_segment);
|
||||
const auto ir_op = decoded.data_bits == 8u
|
||||
? (decoded.data_signed ? Opcode::FlatLoadSbyte : Opcode::FlatLoadUbyte)
|
||||
: decoded.data_bits == 16u
|
||||
? (decoded.data_signed ? Opcode::FlatLoadSshort : Opcode::FlatLoadUshort)
|
||||
: Opcode::FlatLoadDword;
|
||||
const auto count = decoded.data_bits == 32u ? decoded.data_dwords : 1u;
|
||||
if (decoded.data_bits == 32u && count > 1u) {
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = ir_op;
|
||||
inst.src_count = decoded.src_count;
|
||||
inst.memory = MemoryInfoFromDecoded(decoded, kind);
|
||||
inst.memory.resource = 0;
|
||||
if (!LowerRegisterOperand(decoded.dst, &inst.dst, error)) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < decoded.src_count && i < 3u; i++) {
|
||||
if (!LowerSourceOperand(DecodedSourceAt(decoded, i), &inst.src[i], error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (uint32_t dword = 0; dword < count; dword++) {
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = ir_op;
|
||||
inst.src_count = decoded.src_count;
|
||||
inst.memory = OffsetMemoryInfo(decoded, kind, dword);
|
||||
inst.memory.resource = 0;
|
||||
if (!LowerRegisterOperand(OffsetDecodedRegister(decoded.dst, dword), &inst.dst, error)) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < decoded.src_count && i < 3u; i++) {
|
||||
if (!LowerSourceOperand(DecodedSourceAt(decoded, i), &inst.src[i], error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LowerFlatStore(const Decoder::Instruction& decoded, BasicBlock* block, std::string* error) {
|
||||
const auto kind = FlatSegmentResourceKind(decoded.memory_segment);
|
||||
const auto ir_op = decoded.data_bits == 8u ? Opcode::FlatStoreByte
|
||||
: decoded.data_bits == 16u ? Opcode::FlatStoreShort
|
||||
: Opcode::FlatStoreDword;
|
||||
const auto count = decoded.data_bits == 32u ? decoded.data_dwords : 1u;
|
||||
for (uint32_t dword = 0; dword < count; dword++) {
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = ir_op;
|
||||
inst.src_count = decoded.src_count + 1u;
|
||||
inst.memory = OffsetMemoryInfo(decoded, kind, dword);
|
||||
inst.memory.resource = 0;
|
||||
inst.dst.kind = OperandKind::Null;
|
||||
if (!LowerSourceOperand(OffsetDecodedRegister(decoded.dst, dword), &inst.src[0], error)) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < decoded.src_count && i + 1u < 3u; i++) {
|
||||
if (!LowerSourceOperand(DecodedSourceAt(decoded, i), &inst.src[i + 1u], error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LowerDsWrite(const Decoder::Instruction& decoded, BasicBlock* block, std::string* error) {
|
||||
const auto ir_op = decoded.data_bits == 8u ? Opcode::DsWriteByte
|
||||
: decoded.data_bits == 16u ? Opcode::DsWriteShort
|
||||
: Opcode::DsWriteB32;
|
||||
const auto count = decoded.data_bits == 32u ? decoded.data_dwords : 1u;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = ir_op;
|
||||
inst.src_count = 2;
|
||||
inst.memory = OffsetMemoryInfo(decoded, DsMemoryKind(decoded), i);
|
||||
inst.dst.kind = OperandKind::Null;
|
||||
if (!LowerSourceOperand(OffsetDecodedRegister(decoded.src1, i), &inst.src[0], error) ||
|
||||
!LowerSourceOperand(decoded.src0, &inst.src[1], error)) {
|
||||
return false;
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LowerDsWrite2(const Decoder::Instruction& decoded, BasicBlock* block,
|
||||
uint32_t dwords_per_write, std::string* error) {
|
||||
const Decoder::Operand* data[] = {&decoded.src1, &decoded.src2};
|
||||
const uint32_t offsets[] = {decoded.offset, decoded.secondary_offset};
|
||||
for (uint32_t write = 0; write < 2u; write++) {
|
||||
for (uint32_t dword = 0; dword < dwords_per_write; dword++) {
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
inst.op = Opcode::DsWriteB32;
|
||||
inst.src_count = 2;
|
||||
inst.memory =
|
||||
ByteOffsetMemoryInfo(decoded, DsMemoryKind(decoded), offsets[write] + dword * 4u);
|
||||
inst.dst.kind = OperandKind::Null;
|
||||
if (!LowerSourceOperand(OffsetDecodedRegister(*data[write], dword), &inst.src[0],
|
||||
error) ||
|
||||
!LowerSourceOperand(decoded.src0, &inst.src[1], error)) {
|
||||
return false;
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LowerImageOperation(const Decoder::Instruction& decoded, BasicBlock* block,
|
||||
std::string* error) {
|
||||
Instruction inst;
|
||||
inst.pc = decoded.pc;
|
||||
if (decoded.opcode == Decoder::Opcode::ImageStore ||
|
||||
decoded.opcode == Decoder::Opcode::ImageStoreMip) {
|
||||
inst.op = Opcode::ImageStore;
|
||||
inst.src_count = 2;
|
||||
inst.memory = MemoryInfoFromDecoded(decoded, ResourceKind::StorageImage);
|
||||
inst.memory.sampler = 0;
|
||||
inst.dst.kind = OperandKind::Null;
|
||||
if (!LowerSourceOperand(decoded.dst, &inst.src[0], error) ||
|
||||
!LowerSourceOperand(decoded.src0, &inst.src[1], error)) {
|
||||
return false;
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
return true;
|
||||
}
|
||||
|
||||
inst.op = decoded.opcode == Decoder::Opcode::ImageGetResinfo ? Opcode::ImageGetResinfo
|
||||
: decoded.opcode == Decoder::Opcode::ImageGetLod ? Opcode::ImageGetLod
|
||||
: decoded.opcode == Decoder::Opcode::ImageLoad ||
|
||||
decoded.opcode == Decoder::Opcode::ImageLoadMip
|
||||
? Opcode::ImageLoad
|
||||
: decoded.opcode == Decoder::Opcode::ImageGather4Lz ||
|
||||
decoded.opcode == Decoder::Opcode::ImageGather4C ||
|
||||
decoded.opcode == Decoder::Opcode::ImageGather4CLz ||
|
||||
decoded.opcode == Decoder::Opcode::ImageGather4LzO ||
|
||||
decoded.opcode == Decoder::Opcode::ImageGather4CO ||
|
||||
decoded.opcode == Decoder::Opcode::ImageGather4CLzO ||
|
||||
decoded.opcode == Decoder::Opcode::ImageGather4H
|
||||
? Opcode::ImageGather4
|
||||
: Opcode::ImageSample;
|
||||
inst.src_count = 1;
|
||||
inst.memory = MemoryInfoFromDecoded(decoded, ResourceKind::Image);
|
||||
if (!LowerRegisterOperand(decoded.dst, &inst.dst, error) ||
|
||||
!LowerSourceOperand(decoded.src0, &inst.src[0], error)) {
|
||||
return false;
|
||||
}
|
||||
block->instructions.push_back(inst);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
bool LowerMemoryInstruction(const Decoder::Instruction& decoded, BasicBlock* block,
|
||||
std::string* error) {
|
||||
switch (decoded.opcode) {
|
||||
case Decoder::Opcode::SLoadDword:
|
||||
case Decoder::Opcode::SLoadDwordx2:
|
||||
case Decoder::Opcode::SLoadDwordx4:
|
||||
case Decoder::Opcode::SLoadDwordx8:
|
||||
case Decoder::Opcode::SLoadDwordx16:
|
||||
return LowerScalarBufferLoadDword(decoded, block, Opcode::SLoadDword, error);
|
||||
case Decoder::Opcode::SBufferLoadDword:
|
||||
case Decoder::Opcode::SBufferLoadDwordx2:
|
||||
case Decoder::Opcode::SBufferLoadDwordx4:
|
||||
case Decoder::Opcode::SBufferLoadDwordx8:
|
||||
case Decoder::Opcode::SBufferLoadDwordx16:
|
||||
return LowerScalarBufferLoadDword(decoded, block, Opcode::SBufferLoadDword, error);
|
||||
case Decoder::Opcode::BufferLoadUbyte:
|
||||
case Decoder::Opcode::BufferLoadSbyte:
|
||||
case Decoder::Opcode::BufferLoadUshort:
|
||||
case Decoder::Opcode::BufferLoadSshort:
|
||||
case Decoder::Opcode::BufferLoadDword:
|
||||
case Decoder::Opcode::BufferLoadDwordx2:
|
||||
case Decoder::Opcode::BufferLoadDwordx3:
|
||||
case Decoder::Opcode::BufferLoadDwordx4:
|
||||
case Decoder::Opcode::BufferLoadFormatX:
|
||||
case Decoder::Opcode::BufferLoadFormatXy:
|
||||
case Decoder::Opcode::BufferLoadFormatXyz:
|
||||
case Decoder::Opcode::BufferLoadFormatXyzw:
|
||||
case Decoder::Opcode::TBufferLoadFormatX:
|
||||
case Decoder::Opcode::TBufferLoadFormatXy:
|
||||
case Decoder::Opcode::TBufferLoadFormatXyz:
|
||||
case Decoder::Opcode::TBufferLoadFormatXyzw: return LowerBufferLoad(decoded, block, error);
|
||||
case Decoder::Opcode::BufferStoreDword:
|
||||
case Decoder::Opcode::BufferStoreDwordx2:
|
||||
case Decoder::Opcode::BufferStoreDwordx3:
|
||||
case Decoder::Opcode::BufferStoreDwordx4:
|
||||
case Decoder::Opcode::BufferStoreByte:
|
||||
case Decoder::Opcode::BufferStoreShort:
|
||||
case Decoder::Opcode::BufferStoreFormatX:
|
||||
case Decoder::Opcode::BufferStoreFormatXy:
|
||||
case Decoder::Opcode::BufferStoreFormatXyz:
|
||||
case Decoder::Opcode::BufferStoreFormatXyzw:
|
||||
case Decoder::Opcode::TBufferStoreFormatX:
|
||||
case Decoder::Opcode::TBufferStoreFormatXy:
|
||||
case Decoder::Opcode::TBufferStoreFormatXyz:
|
||||
case Decoder::Opcode::TBufferStoreFormatXyzw:
|
||||
return LowerBufferStore(decoded, block, error);
|
||||
case Decoder::Opcode::BufferAtomicSwap:
|
||||
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicSwapU32, error);
|
||||
case Decoder::Opcode::BufferAtomicAdd:
|
||||
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicAddU32, error);
|
||||
case Decoder::Opcode::BufferAtomicSub:
|
||||
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicSubU32, error);
|
||||
case Decoder::Opcode::BufferAtomicSMin:
|
||||
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicSMinI32, error);
|
||||
case Decoder::Opcode::BufferAtomicUMin:
|
||||
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicUMinU32, error);
|
||||
case Decoder::Opcode::BufferAtomicSMax:
|
||||
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicSMaxI32, error);
|
||||
case Decoder::Opcode::BufferAtomicUMax:
|
||||
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicUMaxU32, error);
|
||||
case Decoder::Opcode::BufferAtomicAnd:
|
||||
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicAndU32, error);
|
||||
case Decoder::Opcode::BufferAtomicOr:
|
||||
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicOrU32, error);
|
||||
case Decoder::Opcode::BufferAtomicXor:
|
||||
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicXorU32, error);
|
||||
case Decoder::Opcode::FlatLoadUbyte:
|
||||
case Decoder::Opcode::FlatLoadSbyte:
|
||||
case Decoder::Opcode::FlatLoadUshort:
|
||||
case Decoder::Opcode::FlatLoadSshort:
|
||||
case Decoder::Opcode::FlatLoadDword:
|
||||
case Decoder::Opcode::FlatLoadDwordx2:
|
||||
case Decoder::Opcode::FlatLoadDwordx3:
|
||||
case Decoder::Opcode::FlatLoadDwordx4: return LowerFlatLoad(decoded, block, error);
|
||||
case Decoder::Opcode::FlatStoreByte:
|
||||
case Decoder::Opcode::FlatStoreShort:
|
||||
case Decoder::Opcode::FlatStoreDword:
|
||||
case Decoder::Opcode::FlatStoreDwordx2:
|
||||
case Decoder::Opcode::FlatStoreDwordx3:
|
||||
case Decoder::Opcode::FlatStoreDwordx4: return LowerFlatStore(decoded, block, error);
|
||||
case Decoder::Opcode::DsAddU32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicAddU32, false, error);
|
||||
case Decoder::Opcode::DsAddRtnU32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicAddU32, true, error);
|
||||
case Decoder::Opcode::DsSubU32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicSubU32, false, error);
|
||||
case Decoder::Opcode::DsSubRtnU32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicSubU32, true, error);
|
||||
case Decoder::Opcode::DsMinI32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicSMinI32, false, error);
|
||||
case Decoder::Opcode::DsMinRtnI32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicSMinI32, true, error);
|
||||
case Decoder::Opcode::DsMaxI32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicSMaxI32, false, error);
|
||||
case Decoder::Opcode::DsMaxRtnI32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicSMaxI32, true, error);
|
||||
case Decoder::Opcode::DsMinU32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicUMinU32, false, error);
|
||||
case Decoder::Opcode::DsMinRtnU32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicUMinU32, true, error);
|
||||
case Decoder::Opcode::DsMaxU32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicUMaxU32, false, error);
|
||||
case Decoder::Opcode::DsMaxRtnU32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicUMaxU32, true, error);
|
||||
case Decoder::Opcode::DsAndB32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicAndU32, false, error);
|
||||
case Decoder::Opcode::DsAndRtnB32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicAndU32, true, error);
|
||||
case Decoder::Opcode::DsOrB32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicOrU32, false, error);
|
||||
case Decoder::Opcode::DsOrRtnB32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicOrU32, true, error);
|
||||
case Decoder::Opcode::DsXorB32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicXorU32, false, error);
|
||||
case Decoder::Opcode::DsXorRtnB32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicXorU32, true, error);
|
||||
case Decoder::Opcode::DsWrxchgRtnB32:
|
||||
return LowerDsAtomicU32(decoded, block, Opcode::AtomicSwapU32, true, error);
|
||||
case Decoder::Opcode::DsMinF32:
|
||||
return LowerDsFloatMinMaxF32(decoded, block, Opcode::DsMinF32, error);
|
||||
case Decoder::Opcode::DsMaxF32:
|
||||
return LowerDsFloatMinMaxF32(decoded, block, Opcode::DsMaxF32, error);
|
||||
case Decoder::Opcode::DsSwizzleB32: return LowerDsSwizzleB32(decoded, block, error);
|
||||
case Decoder::Opcode::DsConsume:
|
||||
return LowerDsAppendConsume(decoded, block, Opcode::DsConsume, error);
|
||||
case Decoder::Opcode::DsAppend:
|
||||
return LowerDsAppendConsume(decoded, block, Opcode::DsAppend, error);
|
||||
case Decoder::Opcode::DsWriteAddtidB32: return LowerDsWriteAddtidB32(decoded, block, error);
|
||||
case Decoder::Opcode::DsReadAddtidB32: return LowerDsReadAddtidB32(decoded, block, error);
|
||||
case Decoder::Opcode::DsRead2B32: return LowerDsRead2(decoded, block, 1, error);
|
||||
case Decoder::Opcode::DsRead2B64:
|
||||
case Decoder::Opcode::DsRead2St64B64: return LowerDsRead2(decoded, block, 2, error);
|
||||
case Decoder::Opcode::DsReadSbyte:
|
||||
case Decoder::Opcode::DsReadUbyte:
|
||||
case Decoder::Opcode::DsReadSshort:
|
||||
case Decoder::Opcode::DsReadUshort:
|
||||
case Decoder::Opcode::DsReadB32:
|
||||
case Decoder::Opcode::DsReadB64:
|
||||
case Decoder::Opcode::DsReadB96:
|
||||
case Decoder::Opcode::DsReadB128: return LowerDsRead(decoded, block, error);
|
||||
case Decoder::Opcode::DsWrite2B32:
|
||||
case Decoder::Opcode::DsWrite2St64B32: return LowerDsWrite2(decoded, block, 1, error);
|
||||
case Decoder::Opcode::DsWrite2B64:
|
||||
case Decoder::Opcode::DsWrite2St64B64: return LowerDsWrite2(decoded, block, 2, error);
|
||||
case Decoder::Opcode::DsWriteByte:
|
||||
case Decoder::Opcode::DsWriteShort:
|
||||
case Decoder::Opcode::DsWriteB32:
|
||||
case Decoder::Opcode::DsWriteB64:
|
||||
case Decoder::Opcode::DsWriteB96:
|
||||
case Decoder::Opcode::DsWriteB128: return LowerDsWrite(decoded, block, error);
|
||||
case Decoder::Opcode::ImageGetResinfo:
|
||||
case Decoder::Opcode::ImageGetLod:
|
||||
case Decoder::Opcode::ImageLoad:
|
||||
case Decoder::Opcode::ImageLoadMip:
|
||||
case Decoder::Opcode::ImageStore:
|
||||
case Decoder::Opcode::ImageStoreMip:
|
||||
case Decoder::Opcode::ImageGather4Lz:
|
||||
case Decoder::Opcode::ImageGather4C:
|
||||
case Decoder::Opcode::ImageGather4CLz:
|
||||
case Decoder::Opcode::ImageGather4LzO:
|
||||
case Decoder::Opcode::ImageGather4CO:
|
||||
case Decoder::Opcode::ImageGather4CLzO:
|
||||
case Decoder::Opcode::ImageGather4H:
|
||||
case Decoder::Opcode::ImageSample: return LowerImageOperation(decoded, block, error);
|
||||
case Decoder::Opcode::ImageAtomicAdd:
|
||||
case Decoder::Opcode::ImageAtomicUMin:
|
||||
case Decoder::Opcode::ImageAtomicUMax:
|
||||
case Decoder::Opcode::ImageAtomicAnd:
|
||||
case Decoder::Opcode::ImageAtomicOr:
|
||||
case Decoder::Opcode::ImageAtomicXor: return LowerImageAtomicU32(decoded, block, error);
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool IsMemoryOpcode(Decoder::Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Decoder::Opcode::SLoadDword:
|
||||
case Decoder::Opcode::SLoadDwordx2:
|
||||
case Decoder::Opcode::SLoadDwordx4:
|
||||
case Decoder::Opcode::SLoadDwordx8:
|
||||
case Decoder::Opcode::SLoadDwordx16:
|
||||
case Decoder::Opcode::SBufferLoadDword:
|
||||
case Decoder::Opcode::SBufferLoadDwordx2:
|
||||
case Decoder::Opcode::SBufferLoadDwordx4:
|
||||
case Decoder::Opcode::SBufferLoadDwordx8:
|
||||
case Decoder::Opcode::SBufferLoadDwordx16:
|
||||
case Decoder::Opcode::BufferLoadUbyte:
|
||||
case Decoder::Opcode::BufferLoadSbyte:
|
||||
case Decoder::Opcode::BufferLoadUshort:
|
||||
case Decoder::Opcode::BufferLoadSshort:
|
||||
case Decoder::Opcode::BufferLoadDword:
|
||||
case Decoder::Opcode::BufferLoadDwordx2:
|
||||
case Decoder::Opcode::BufferLoadDwordx3:
|
||||
case Decoder::Opcode::BufferLoadDwordx4:
|
||||
case Decoder::Opcode::BufferLoadFormatX:
|
||||
case Decoder::Opcode::BufferLoadFormatXy:
|
||||
case Decoder::Opcode::BufferLoadFormatXyz:
|
||||
case Decoder::Opcode::BufferLoadFormatXyzw:
|
||||
case Decoder::Opcode::TBufferLoadFormatX:
|
||||
case Decoder::Opcode::TBufferLoadFormatXy:
|
||||
case Decoder::Opcode::TBufferLoadFormatXyz:
|
||||
case Decoder::Opcode::TBufferLoadFormatXyzw:
|
||||
case Decoder::Opcode::BufferStoreDword:
|
||||
case Decoder::Opcode::BufferStoreDwordx2:
|
||||
case Decoder::Opcode::BufferStoreDwordx3:
|
||||
case Decoder::Opcode::BufferStoreDwordx4:
|
||||
case Decoder::Opcode::BufferStoreByte:
|
||||
case Decoder::Opcode::BufferStoreShort:
|
||||
case Decoder::Opcode::BufferStoreFormatX:
|
||||
case Decoder::Opcode::BufferStoreFormatXy:
|
||||
case Decoder::Opcode::BufferStoreFormatXyz:
|
||||
case Decoder::Opcode::BufferStoreFormatXyzw:
|
||||
case Decoder::Opcode::TBufferStoreFormatX:
|
||||
case Decoder::Opcode::TBufferStoreFormatXy:
|
||||
case Decoder::Opcode::TBufferStoreFormatXyz:
|
||||
case Decoder::Opcode::TBufferStoreFormatXyzw:
|
||||
case Decoder::Opcode::BufferAtomicSwap:
|
||||
case Decoder::Opcode::BufferAtomicAdd:
|
||||
case Decoder::Opcode::BufferAtomicSub:
|
||||
case Decoder::Opcode::BufferAtomicSMin:
|
||||
case Decoder::Opcode::BufferAtomicUMin:
|
||||
case Decoder::Opcode::BufferAtomicSMax:
|
||||
case Decoder::Opcode::BufferAtomicUMax:
|
||||
case Decoder::Opcode::BufferAtomicAnd:
|
||||
case Decoder::Opcode::BufferAtomicOr:
|
||||
case Decoder::Opcode::BufferAtomicXor:
|
||||
case Decoder::Opcode::FlatLoadUbyte:
|
||||
case Decoder::Opcode::FlatLoadSbyte:
|
||||
case Decoder::Opcode::FlatLoadUshort:
|
||||
case Decoder::Opcode::FlatLoadSshort:
|
||||
case Decoder::Opcode::FlatLoadDword:
|
||||
case Decoder::Opcode::FlatLoadDwordx2:
|
||||
case Decoder::Opcode::FlatLoadDwordx3:
|
||||
case Decoder::Opcode::FlatLoadDwordx4:
|
||||
case Decoder::Opcode::FlatStoreByte:
|
||||
case Decoder::Opcode::FlatStoreShort:
|
||||
case Decoder::Opcode::FlatStoreDword:
|
||||
case Decoder::Opcode::FlatStoreDwordx2:
|
||||
case Decoder::Opcode::FlatStoreDwordx3:
|
||||
case Decoder::Opcode::FlatStoreDwordx4:
|
||||
case Decoder::Opcode::DsAddU32:
|
||||
case Decoder::Opcode::DsAddRtnU32:
|
||||
case Decoder::Opcode::DsSubU32:
|
||||
case Decoder::Opcode::DsSubRtnU32:
|
||||
case Decoder::Opcode::DsMinI32:
|
||||
case Decoder::Opcode::DsMinRtnI32:
|
||||
case Decoder::Opcode::DsMaxI32:
|
||||
case Decoder::Opcode::DsMaxRtnI32:
|
||||
case Decoder::Opcode::DsMinU32:
|
||||
case Decoder::Opcode::DsMinRtnU32:
|
||||
case Decoder::Opcode::DsMaxU32:
|
||||
case Decoder::Opcode::DsMaxRtnU32:
|
||||
case Decoder::Opcode::DsAndB32:
|
||||
case Decoder::Opcode::DsAndRtnB32:
|
||||
case Decoder::Opcode::DsOrB32:
|
||||
case Decoder::Opcode::DsOrRtnB32:
|
||||
case Decoder::Opcode::DsXorB32:
|
||||
case Decoder::Opcode::DsXorRtnB32:
|
||||
case Decoder::Opcode::DsWrxchgRtnB32:
|
||||
case Decoder::Opcode::DsMinF32:
|
||||
case Decoder::Opcode::DsMaxF32:
|
||||
case Decoder::Opcode::DsSwizzleB32:
|
||||
case Decoder::Opcode::DsConsume:
|
||||
case Decoder::Opcode::DsAppend:
|
||||
case Decoder::Opcode::DsReadSbyte:
|
||||
case Decoder::Opcode::DsReadUbyte:
|
||||
case Decoder::Opcode::DsReadSshort:
|
||||
case Decoder::Opcode::DsReadUshort:
|
||||
case Decoder::Opcode::DsRead2B32:
|
||||
case Decoder::Opcode::DsReadB32:
|
||||
case Decoder::Opcode::DsReadB64:
|
||||
case Decoder::Opcode::DsRead2B64:
|
||||
case Decoder::Opcode::DsRead2St64B64:
|
||||
case Decoder::Opcode::DsReadB96:
|
||||
case Decoder::Opcode::DsReadB128:
|
||||
case Decoder::Opcode::DsWriteByte:
|
||||
case Decoder::Opcode::DsWriteShort:
|
||||
case Decoder::Opcode::DsWrite2B32:
|
||||
case Decoder::Opcode::DsWrite2St64B32:
|
||||
case Decoder::Opcode::DsWrite2B64:
|
||||
case Decoder::Opcode::DsWrite2St64B64:
|
||||
case Decoder::Opcode::DsWriteB32:
|
||||
case Decoder::Opcode::DsWriteB64:
|
||||
case Decoder::Opcode::DsWriteB96:
|
||||
case Decoder::Opcode::DsWriteB128:
|
||||
case Decoder::Opcode::DsWriteAddtidB32:
|
||||
case Decoder::Opcode::DsReadAddtidB32:
|
||||
case Decoder::Opcode::ImageGetResinfo:
|
||||
case Decoder::Opcode::ImageGetLod:
|
||||
case Decoder::Opcode::ImageLoad:
|
||||
case Decoder::Opcode::ImageLoadMip:
|
||||
case Decoder::Opcode::ImageStore:
|
||||
case Decoder::Opcode::ImageStoreMip:
|
||||
case Decoder::Opcode::ImageAtomicAdd:
|
||||
case Decoder::Opcode::ImageAtomicUMin:
|
||||
case Decoder::Opcode::ImageAtomicUMax:
|
||||
case Decoder::Opcode::ImageAtomicAnd:
|
||||
case Decoder::Opcode::ImageAtomicOr:
|
||||
case Decoder::Opcode::ImageAtomicXor:
|
||||
case Decoder::Opcode::ImageGather4Lz:
|
||||
case Decoder::Opcode::ImageGather4C:
|
||||
case Decoder::Opcode::ImageGather4CLz:
|
||||
case Decoder::Opcode::ImageGather4LzO:
|
||||
case Decoder::Opcode::ImageGather4CO:
|
||||
case Decoder::Opcode::ImageGather4CLzO:
|
||||
case Decoder::Opcode::ImageGather4H:
|
||||
case Decoder::Opcode::ImageSample: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -0,0 +1,534 @@
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
|
||||
#include "graphics/shader/recompiler/shaderIR/ShaderIRInternal.h"
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
namespace {
|
||||
|
||||
struct LowerMap {
|
||||
Decoder::Opcode decoded = Decoder::Opcode::Unknown;
|
||||
Opcode ir = Opcode::MoveU32;
|
||||
};
|
||||
|
||||
constexpr LowerMap LOWER_OPS[] = {
|
||||
{Decoder::Opcode::SMovB32, Opcode::MoveU32},
|
||||
{Decoder::Opcode::SMovB64, Opcode::MoveU64},
|
||||
{Decoder::Opcode::SWqmB64, Opcode::WqmB64},
|
||||
{Decoder::Opcode::SMovkI32, Opcode::MoveU32},
|
||||
{Decoder::Opcode::SAbsI32, Opcode::AbsI32},
|
||||
{Decoder::Opcode::SBrevB32, Opcode::BitReverseU32},
|
||||
{Decoder::Opcode::SBcnt1I32B32, Opcode::BitCountU32},
|
||||
{Decoder::Opcode::SBcnt1I32B64, Opcode::BitCountU64},
|
||||
{Decoder::Opcode::SFf1I32B32, Opcode::FindLsbU32},
|
||||
{Decoder::Opcode::SFlbitI32B64, Opcode::FindMsbFromHighU64},
|
||||
{Decoder::Opcode::SBitreplicateB64B32, Opcode::BitReplicateB64B32},
|
||||
{Decoder::Opcode::SNotB32, Opcode::BitwiseNotU32},
|
||||
{Decoder::Opcode::SNotB64, Opcode::BitwiseNotU64},
|
||||
{Decoder::Opcode::SAddU32, Opcode::IAddU32},
|
||||
{Decoder::Opcode::SSubU32, Opcode::ISubU32},
|
||||
{Decoder::Opcode::SAddI32, Opcode::IAddU32},
|
||||
{Decoder::Opcode::SSubI32, Opcode::ISubU32},
|
||||
{Decoder::Opcode::SMinI32, Opcode::IMinI32},
|
||||
{Decoder::Opcode::SMaxI32, Opcode::IMaxI32},
|
||||
{Decoder::Opcode::SMinU32, Opcode::UMinU32},
|
||||
{Decoder::Opcode::SMaxU32, Opcode::UMaxU32},
|
||||
{Decoder::Opcode::SAndB32, Opcode::BitwiseAndU32},
|
||||
{Decoder::Opcode::SAndB64, Opcode::BitwiseAndU64},
|
||||
{Decoder::Opcode::SAndn2B32, Opcode::BitwiseAndNotU32},
|
||||
{Decoder::Opcode::SAndn2B64, Opcode::BitwiseAndNotU64},
|
||||
{Decoder::Opcode::SOrB32, Opcode::BitwiseOrU32},
|
||||
{Decoder::Opcode::SOrB64, Opcode::BitwiseOrU64},
|
||||
{Decoder::Opcode::SOrn2B32, Opcode::BitwiseOrNotU32},
|
||||
{Decoder::Opcode::SOrn2B64, Opcode::BitwiseOrNotU64},
|
||||
{Decoder::Opcode::SXorB32, Opcode::BitwiseXorU32},
|
||||
{Decoder::Opcode::SXorB64, Opcode::BitwiseXorU64},
|
||||
{Decoder::Opcode::SNandB32, Opcode::BitwiseNandU32},
|
||||
{Decoder::Opcode::SNandB64, Opcode::BitwiseNandU64},
|
||||
{Decoder::Opcode::SNorB32, Opcode::BitwiseNorU32},
|
||||
{Decoder::Opcode::SNorB64, Opcode::BitwiseNorU64},
|
||||
{Decoder::Opcode::SXnorB32, Opcode::BitwiseXnorU32},
|
||||
{Decoder::Opcode::SXnorB64, Opcode::BitwiseXnorU64},
|
||||
{Decoder::Opcode::SLshlB32, Opcode::ShiftLeftLogicalU32},
|
||||
{Decoder::Opcode::SLshlB64, Opcode::ShiftLeftLogicalU64},
|
||||
{Decoder::Opcode::SLshrB32, Opcode::ShiftRightLogicalU32},
|
||||
{Decoder::Opcode::SLshrB64, Opcode::ShiftRightLogicalU64},
|
||||
{Decoder::Opcode::SAshrI32, Opcode::ShiftRightArithmeticI32},
|
||||
{Decoder::Opcode::SMulI32, Opcode::IMulU32},
|
||||
{Decoder::Opcode::SMulHiU32, Opcode::UMulHighU32},
|
||||
{Decoder::Opcode::SMulkI32, Opcode::IMulU32},
|
||||
{Decoder::Opcode::SBfeU32, Opcode::BitFieldExtractU32},
|
||||
{Decoder::Opcode::SBfeU64, Opcode::BitFieldExtractU64},
|
||||
{Decoder::Opcode::SBfmB32, Opcode::BitFieldMaskU32},
|
||||
{Decoder::Opcode::SBfmB64, Opcode::BitFieldMaskU64},
|
||||
{Decoder::Opcode::SBitcmp0B32, Opcode::BitCompare0B32},
|
||||
{Decoder::Opcode::SBitcmp1B32, Opcode::BitCompare1B32},
|
||||
{Decoder::Opcode::SPackLlB32B16, Opcode::PackLowLowU16},
|
||||
{Decoder::Opcode::SPackLhB32B16, Opcode::PackLowHighU16},
|
||||
{Decoder::Opcode::SPackHhB32B16, Opcode::PackHighHighU16},
|
||||
{Decoder::Opcode::SCmpEqI32, Opcode::CompareEqI32},
|
||||
{Decoder::Opcode::SCmpLgI32, Opcode::CompareNeI32},
|
||||
{Decoder::Opcode::SCmpGtI32, Opcode::CompareGtI32},
|
||||
{Decoder::Opcode::SCmpGeI32, Opcode::CompareGeI32},
|
||||
{Decoder::Opcode::SCmpLtI32, Opcode::CompareLtI32},
|
||||
{Decoder::Opcode::SCmpLeI32, Opcode::CompareLeI32},
|
||||
{Decoder::Opcode::SCmpEqU32, Opcode::CompareEqU32},
|
||||
{Decoder::Opcode::SCmpLgU32, Opcode::CompareNeU32},
|
||||
{Decoder::Opcode::SCmpGtU32, Opcode::CompareGtU32},
|
||||
{Decoder::Opcode::SCmpGeU32, Opcode::CompareGeU32},
|
||||
{Decoder::Opcode::SCmpLtU32, Opcode::CompareLtU32},
|
||||
{Decoder::Opcode::SCmpLeU32, Opcode::CompareLeU32},
|
||||
{Decoder::Opcode::SCmpLgU64, Opcode::CompareNeU64},
|
||||
{Decoder::Opcode::VReadfirstlaneB32, Opcode::ReadFirstLaneU32},
|
||||
{Decoder::Opcode::VReadlaneB32, Opcode::ReadLaneU32},
|
||||
{Decoder::Opcode::VWritelaneB32, Opcode::WriteLaneU32},
|
||||
{Decoder::Opcode::VPermlane16B32, Opcode::Permlane16B32},
|
||||
{Decoder::Opcode::VPermlanex16B32, Opcode::Permlanex16B32},
|
||||
{Decoder::Opcode::VCvtF32I32, Opcode::ConvertI32ToF32},
|
||||
{Decoder::Opcode::VCvtF32U32, Opcode::ConvertU32ToF32},
|
||||
{Decoder::Opcode::VCvtU32F32, Opcode::ConvertF32ToU32},
|
||||
{Decoder::Opcode::VCvtI32F32, Opcode::ConvertF32ToI32},
|
||||
{Decoder::Opcode::VCvtF16F32, Opcode::ConvertF32ToF16},
|
||||
{Decoder::Opcode::VCvtF32F16, Opcode::ConvertF16ToF32},
|
||||
{Decoder::Opcode::VCvtF16U16, Opcode::ConvertU16ToF16},
|
||||
{Decoder::Opcode::VCvtU16F16, Opcode::ConvertF16ToU16},
|
||||
{Decoder::Opcode::VCvtF16I16, Opcode::ConvertI16ToF16},
|
||||
{Decoder::Opcode::VCvtI16F16, Opcode::ConvertF16ToI16},
|
||||
{Decoder::Opcode::VCvtRpiI32F32, Opcode::ConvertRoundPlusInfF32ToI32},
|
||||
{Decoder::Opcode::VCvtFlrI32F32, Opcode::ConvertFloorF32ToI32},
|
||||
{Decoder::Opcode::VCvtOffF32I4, Opcode::ConvertI4ToOffsetF32},
|
||||
{Decoder::Opcode::VRcpF32, Opcode::RcpF32},
|
||||
{Decoder::Opcode::VFractF32, Opcode::FractF32},
|
||||
{Decoder::Opcode::VTruncF32, Opcode::TruncF32},
|
||||
{Decoder::Opcode::VCeilF32, Opcode::CeilF32},
|
||||
{Decoder::Opcode::VRndneF32, Opcode::RoundEvenF32},
|
||||
{Decoder::Opcode::VFloorF32, Opcode::FloorF32},
|
||||
{Decoder::Opcode::VExpF32, Opcode::Exp2F32},
|
||||
{Decoder::Opcode::VLogF32, Opcode::Log2F32},
|
||||
{Decoder::Opcode::VRsqF32, Opcode::InverseSqrtF32},
|
||||
{Decoder::Opcode::VSqrtF32, Opcode::SqrtF32},
|
||||
{Decoder::Opcode::VRcpF16, Opcode::RcpF16},
|
||||
{Decoder::Opcode::VSqrtF16, Opcode::SqrtF16},
|
||||
{Decoder::Opcode::VRsqF16, Opcode::InverseSqrtF16},
|
||||
{Decoder::Opcode::VLogF16, Opcode::Log2F16},
|
||||
{Decoder::Opcode::VExpF16, Opcode::Exp2F16},
|
||||
{Decoder::Opcode::VFloorF16, Opcode::FloorF16},
|
||||
{Decoder::Opcode::VCeilF16, Opcode::CeilF16},
|
||||
{Decoder::Opcode::VTruncF16, Opcode::TruncF16},
|
||||
{Decoder::Opcode::VRndneF16, Opcode::RoundEvenF16},
|
||||
{Decoder::Opcode::VSinF32, Opcode::SinF32},
|
||||
{Decoder::Opcode::VCosF32, Opcode::CosF32},
|
||||
{Decoder::Opcode::VNotB32, Opcode::BitwiseNotU32},
|
||||
{Decoder::Opcode::VBfrevB32, Opcode::BitReverseU32},
|
||||
{Decoder::Opcode::VFfblB32, Opcode::FindLsbU32},
|
||||
{Decoder::Opcode::VFfbhU32, Opcode::FindMsbFromHighU32},
|
||||
{Decoder::Opcode::VAddF32, Opcode::FAddF32},
|
||||
{Decoder::Opcode::VSubF32, Opcode::FSubF32},
|
||||
{Decoder::Opcode::VSubrevF32, Opcode::FSubF32},
|
||||
{Decoder::Opcode::VMulF32, Opcode::FMulF32},
|
||||
{Decoder::Opcode::VMacF32, Opcode::FMadF32},
|
||||
{Decoder::Opcode::VMadmkF32, Opcode::FMadF32},
|
||||
{Decoder::Opcode::VMadakF32, Opcode::FMadF32},
|
||||
{Decoder::Opcode::VMinF32, Opcode::FMinF32},
|
||||
{Decoder::Opcode::VMaxF32, Opcode::FMaxF32},
|
||||
{Decoder::Opcode::VMinI32, Opcode::IMinI32},
|
||||
{Decoder::Opcode::VMaxI32, Opcode::IMaxI32},
|
||||
{Decoder::Opcode::VMadF32, Opcode::FMadF32},
|
||||
{Decoder::Opcode::VMadI32I24, Opcode::IMadI24U32},
|
||||
{Decoder::Opcode::VMadU32U24, Opcode::UMadU24U32},
|
||||
{Decoder::Opcode::VCubeidF32, Opcode::CubeIdF32},
|
||||
{Decoder::Opcode::VCubescF32, Opcode::CubeScF32},
|
||||
{Decoder::Opcode::VCubetcF32, Opcode::CubeTcF32},
|
||||
{Decoder::Opcode::VCubemaF32, Opcode::CubeMaF32},
|
||||
{Decoder::Opcode::VFmaF32, Opcode::FMadF32},
|
||||
{Decoder::Opcode::VBfeU32, Opcode::BitFieldExtract3U32},
|
||||
{Decoder::Opcode::VBfeI32, Opcode::BitFieldExtract3I32},
|
||||
{Decoder::Opcode::VBfiB32, Opcode::BitFieldInsertSelectU32},
|
||||
{Decoder::Opcode::VAlignbitB32, Opcode::AlignBitU32},
|
||||
{Decoder::Opcode::VMin3F32, Opcode::FMin3F32},
|
||||
{Decoder::Opcode::VMin3I32, Opcode::IMin3I32},
|
||||
{Decoder::Opcode::VMin3U32, Opcode::UMin3U32},
|
||||
{Decoder::Opcode::VMin3F16, Opcode::Min3F16},
|
||||
{Decoder::Opcode::VMax3F32, Opcode::FMax3F32},
|
||||
{Decoder::Opcode::VMax3I32, Opcode::IMax3I32},
|
||||
{Decoder::Opcode::VMax3U32, Opcode::UMax3U32},
|
||||
{Decoder::Opcode::VMax3F16, Opcode::Max3F16},
|
||||
{Decoder::Opcode::VMed3F32, Opcode::FMed3F32},
|
||||
{Decoder::Opcode::VMed3I32, Opcode::IMed3I32},
|
||||
{Decoder::Opcode::VMed3U32, Opcode::UMed3U32},
|
||||
{Decoder::Opcode::VMed3F16, Opcode::Med3F16},
|
||||
{Decoder::Opcode::VSadU32, Opcode::SadU32},
|
||||
{Decoder::Opcode::VAdd3U32, Opcode::IAdd3U32},
|
||||
{Decoder::Opcode::VLshlAddU32, Opcode::ShiftLeftAddU32},
|
||||
{Decoder::Opcode::VAddLshlU32, Opcode::AddShiftLeftU32},
|
||||
{Decoder::Opcode::VXadU32, Opcode::XorAddU32},
|
||||
{Decoder::Opcode::VLshlOrB32, Opcode::ShiftLeftOrU32},
|
||||
{Decoder::Opcode::VAndOrB32, Opcode::BitwiseAndOrU32},
|
||||
{Decoder::Opcode::VOr3B32, Opcode::BitwiseOr3U32},
|
||||
{Decoder::Opcode::VXor3B32, Opcode::BitwiseXor3U32},
|
||||
{Decoder::Opcode::VAddNcU32, Opcode::IAddU32},
|
||||
{Decoder::Opcode::VSubNcU32, Opcode::ISubU32},
|
||||
{Decoder::Opcode::VSubrevNcU32, Opcode::ISubU32},
|
||||
{Decoder::Opcode::VAddNcU16, Opcode::IAddU16},
|
||||
{Decoder::Opcode::VSubNcU16, Opcode::ISubI16},
|
||||
{Decoder::Opcode::VMaxU16, Opcode::UMaxU16},
|
||||
{Decoder::Opcode::VMaxI16, Opcode::IMaxI16},
|
||||
{Decoder::Opcode::VMinU16, Opcode::UMinU16},
|
||||
{Decoder::Opcode::VMinI16, Opcode::IMinI16},
|
||||
{Decoder::Opcode::VAddNcI16, Opcode::IAddU16},
|
||||
{Decoder::Opcode::VSubNcI16, Opcode::ISubI16},
|
||||
{Decoder::Opcode::VMulI32I24, Opcode::IMulI24U32},
|
||||
{Decoder::Opcode::VMulU32U24, Opcode::UMulU24U32},
|
||||
{Decoder::Opcode::VMulLoU32, Opcode::IMulU32},
|
||||
{Decoder::Opcode::VMulHiU32, Opcode::UMulHighU32},
|
||||
{Decoder::Opcode::VMulLoI32, Opcode::IMulU32},
|
||||
{Decoder::Opcode::VMulHiI32, Opcode::SMulHighI32},
|
||||
{Decoder::Opcode::VAddI32, Opcode::IAddU32},
|
||||
{Decoder::Opcode::VSubI32, Opcode::ISubU32},
|
||||
{Decoder::Opcode::VSubrevI32, Opcode::ISubU32},
|
||||
{Decoder::Opcode::VBfmB32, Opcode::BitFieldMaskU32},
|
||||
{Decoder::Opcode::VCvtPkrtzF16F32, Opcode::PackF32ToF16Rtz},
|
||||
{Decoder::Opcode::VLdexpF32, Opcode::LdexpF32},
|
||||
{Decoder::Opcode::VCvtPknormI16F32, Opcode::PackSnorm2x16F32},
|
||||
{Decoder::Opcode::VCvtPknormU16F32, Opcode::PackUnorm2x16F32},
|
||||
{Decoder::Opcode::VCvtPkU16U32, Opcode::PackU16U32},
|
||||
{Decoder::Opcode::VCvtPkU8F32, Opcode::PackU8F32},
|
||||
{Decoder::Opcode::VPackB32F16, Opcode::PackB32F16},
|
||||
{Decoder::Opcode::VPkMadI16, Opcode::PackedMadI16},
|
||||
{Decoder::Opcode::VPkMulLoU16, Opcode::PackedMulLoU16},
|
||||
{Decoder::Opcode::VPkAddI16, Opcode::PackedAddI16},
|
||||
{Decoder::Opcode::VPkSubI16, Opcode::PackedSubI16},
|
||||
{Decoder::Opcode::VPkLshlrevB16, Opcode::PackedLshlrevB16},
|
||||
{Decoder::Opcode::VPkLshrrevB16, Opcode::PackedLshrrevB16},
|
||||
{Decoder::Opcode::VPkAshrrevI16, Opcode::PackedAshrrevI16},
|
||||
{Decoder::Opcode::VPkMaxI16, Opcode::PackedMaxI16},
|
||||
{Decoder::Opcode::VPkMinI16, Opcode::PackedMinI16},
|
||||
{Decoder::Opcode::VPkMadU16, Opcode::PackedMadU16},
|
||||
{Decoder::Opcode::VPkAddU16, Opcode::PackedAddU16},
|
||||
{Decoder::Opcode::VPkSubU16, Opcode::PackedSubU16},
|
||||
{Decoder::Opcode::VPkMaxU16, Opcode::PackedMaxU16},
|
||||
{Decoder::Opcode::VPkMinU16, Opcode::PackedMinU16},
|
||||
{Decoder::Opcode::VPkAddF16, Opcode::PackedAddF16},
|
||||
{Decoder::Opcode::VPkMulF16, Opcode::PackedMulF16},
|
||||
{Decoder::Opcode::VPkMinF16, Opcode::PackedMinF16},
|
||||
{Decoder::Opcode::VPkMaxF16, Opcode::PackedMaxF16},
|
||||
{Decoder::Opcode::VPkFmaF16, Opcode::PackedFmaF16},
|
||||
{Decoder::Opcode::VPkFmacF16, Opcode::PackedFmaF16},
|
||||
{Decoder::Opcode::VAddF16, Opcode::AddF16},
|
||||
{Decoder::Opcode::VSubF16, Opcode::SubF16},
|
||||
{Decoder::Opcode::VSubrevF16, Opcode::SubF16},
|
||||
{Decoder::Opcode::VMulF16, Opcode::MulF16},
|
||||
{Decoder::Opcode::VFmacF16, Opcode::FmaF16},
|
||||
{Decoder::Opcode::VFmamkF16, Opcode::FmaF16},
|
||||
{Decoder::Opcode::VFmaakF16, Opcode::FmaF16},
|
||||
{Decoder::Opcode::VFmaF16, Opcode::FmaF16},
|
||||
{Decoder::Opcode::VMadMixloF16, Opcode::MadMixF16},
|
||||
{Decoder::Opcode::VMadMixhiF16, Opcode::MadMixF16},
|
||||
{Decoder::Opcode::VAndB32, Opcode::BitwiseAndU32},
|
||||
{Decoder::Opcode::VOrB32, Opcode::BitwiseOrU32},
|
||||
{Decoder::Opcode::VXorB32, Opcode::BitwiseXorU32},
|
||||
{Decoder::Opcode::VXnorB32, Opcode::BitwiseXnorU32},
|
||||
{Decoder::Opcode::VBcntU32B32, Opcode::BitCountAddU32},
|
||||
{Decoder::Opcode::VMbcntLoU32B32, Opcode::MaskedBitCountLowU32},
|
||||
{Decoder::Opcode::VMbcntHiU32B32, Opcode::MaskedBitCountHighU32},
|
||||
{Decoder::Opcode::VLshlB32, Opcode::ShiftLeftLogicalU32},
|
||||
{Decoder::Opcode::VLshlrevB32, Opcode::ShiftLeftLogicalU32},
|
||||
{Decoder::Opcode::VLshlrevB16, Opcode::ShiftLeftLogicalU16},
|
||||
{Decoder::Opcode::VLshrB32, Opcode::ShiftRightLogicalU32},
|
||||
{Decoder::Opcode::VLshrrevB32, Opcode::ShiftRightLogicalU32},
|
||||
{Decoder::Opcode::VLshrrevB16, Opcode::ShiftRightLogicalU16},
|
||||
{Decoder::Opcode::VAshrI32, Opcode::ShiftRightArithmeticI32},
|
||||
{Decoder::Opcode::VAshrrevI32, Opcode::ShiftRightArithmeticI32},
|
||||
{Decoder::Opcode::VAshrrevI16, Opcode::ShiftRightArithmeticI16},
|
||||
{Decoder::Opcode::VMinU32, Opcode::UMinU32},
|
||||
{Decoder::Opcode::VMaxU32, Opcode::UMaxU32},
|
||||
{Decoder::Opcode::VMaxF16, Opcode::MaxF16},
|
||||
{Decoder::Opcode::VMinF16, Opcode::MinF16},
|
||||
{Decoder::Opcode::VCmpFF32, Opcode::CompareFalse},
|
||||
{Decoder::Opcode::VCmpLtF32, Opcode::CompareLtF32},
|
||||
{Decoder::Opcode::VCmpEqF32, Opcode::CompareEqF32},
|
||||
{Decoder::Opcode::VCmpLeF32, Opcode::CompareLeF32},
|
||||
{Decoder::Opcode::VCmpGtF32, Opcode::CompareGtF32},
|
||||
{Decoder::Opcode::VCmpLgF32, Opcode::CompareNeF32},
|
||||
{Decoder::Opcode::VCmpGeF32, Opcode::CompareGeF32},
|
||||
{Decoder::Opcode::VCmpOF32, Opcode::CompareOrderedF32},
|
||||
{Decoder::Opcode::VCmpUF32, Opcode::CompareUnorderedF32},
|
||||
{Decoder::Opcode::VCmpNgeF32, Opcode::CompareUnordLtF32},
|
||||
{Decoder::Opcode::VCmpNlgF32, Opcode::CompareUnordEqF32},
|
||||
{Decoder::Opcode::VCmpNgtF32, Opcode::CompareUnordLeF32},
|
||||
{Decoder::Opcode::VCmpNleF32, Opcode::CompareUnordGtF32},
|
||||
{Decoder::Opcode::VCmpNeqF32, Opcode::CompareUnordNeF32},
|
||||
{Decoder::Opcode::VCmpNltF32, Opcode::CompareUnordGeF32},
|
||||
{Decoder::Opcode::VCmpTruF32, Opcode::CompareTrue},
|
||||
{Decoder::Opcode::VCmpClassF32, Opcode::CompareClassF32},
|
||||
{Decoder::Opcode::VCmpLtF16, Opcode::CompareLtF16},
|
||||
{Decoder::Opcode::VCmpEqF16, Opcode::CompareEqF16},
|
||||
{Decoder::Opcode::VCmpLeF16, Opcode::CompareLeF16},
|
||||
{Decoder::Opcode::VCmpGtF16, Opcode::CompareGtF16},
|
||||
{Decoder::Opcode::VCmpLgF16, Opcode::CompareNeF16},
|
||||
{Decoder::Opcode::VCmpGeF16, Opcode::CompareGeF16},
|
||||
{Decoder::Opcode::VCmpNeqF16, Opcode::CompareUnordNeF16},
|
||||
{Decoder::Opcode::VCmpxLtF16, Opcode::CompareMaskLtF16},
|
||||
{Decoder::Opcode::VCmpxEqF16, Opcode::CompareMaskEqF16},
|
||||
{Decoder::Opcode::VCmpxLeF16, Opcode::CompareMaskLeF16},
|
||||
{Decoder::Opcode::VCmpxGtF16, Opcode::CompareMaskGtF16},
|
||||
{Decoder::Opcode::VCmpxGeF16, Opcode::CompareMaskGeF16},
|
||||
{Decoder::Opcode::VCmpxNeqF16, Opcode::CompareMaskUnordNeF16},
|
||||
{Decoder::Opcode::VCmpxNltF16, Opcode::CompareMaskUnordGeF16},
|
||||
{Decoder::Opcode::VCmpxLtF32, Opcode::CompareMaskLtF32},
|
||||
{Decoder::Opcode::VCmpxEqF32, Opcode::CompareMaskEqF32},
|
||||
{Decoder::Opcode::VCmpxLeF32, Opcode::CompareMaskLeF32},
|
||||
{Decoder::Opcode::VCmpxGtF32, Opcode::CompareMaskGtF32},
|
||||
{Decoder::Opcode::VCmpxLgF32, Opcode::CompareMaskNeF32},
|
||||
{Decoder::Opcode::VCmpxGeF32, Opcode::CompareMaskGeF32},
|
||||
{Decoder::Opcode::VCmpxNgeF32, Opcode::CompareMaskUnordLtF32},
|
||||
{Decoder::Opcode::VCmpxNlgF32, Opcode::CompareMaskUnordEqF32},
|
||||
{Decoder::Opcode::VCmpxNgtF32, Opcode::CompareMaskUnordLeF32},
|
||||
{Decoder::Opcode::VCmpxNleF32, Opcode::CompareMaskUnordGtF32},
|
||||
{Decoder::Opcode::VCmpxNeqF32, Opcode::CompareMaskUnordNeF32},
|
||||
{Decoder::Opcode::VCmpxNltF32, Opcode::CompareMaskUnordGeF32},
|
||||
{Decoder::Opcode::VCmpFI32, Opcode::CompareFalse},
|
||||
{Decoder::Opcode::VCmpLtI32, Opcode::CompareLtI32},
|
||||
{Decoder::Opcode::VCmpEqI32, Opcode::CompareEqI32},
|
||||
{Decoder::Opcode::VCmpLeI32, Opcode::CompareLeI32},
|
||||
{Decoder::Opcode::VCmpGtI32, Opcode::CompareGtI32},
|
||||
{Decoder::Opcode::VCmpNeI32, Opcode::CompareNeI32},
|
||||
{Decoder::Opcode::VCmpGeI32, Opcode::CompareGeI32},
|
||||
{Decoder::Opcode::VCmpTI32, Opcode::CompareTrue},
|
||||
{Decoder::Opcode::VCmpLtI16, Opcode::CompareLtI16},
|
||||
{Decoder::Opcode::VCmpEqI16, Opcode::CompareEqI16},
|
||||
{Decoder::Opcode::VCmpLeI16, Opcode::CompareLeI16},
|
||||
{Decoder::Opcode::VCmpGtI16, Opcode::CompareGtI16},
|
||||
{Decoder::Opcode::VCmpNeI16, Opcode::CompareNeI16},
|
||||
{Decoder::Opcode::VCmpGeI16, Opcode::CompareGeI16},
|
||||
{Decoder::Opcode::VCmpxLtI32, Opcode::CompareMaskLtI32},
|
||||
{Decoder::Opcode::VCmpxEqI32, Opcode::CompareMaskEqI32},
|
||||
{Decoder::Opcode::VCmpxLeI32, Opcode::CompareMaskLeI32},
|
||||
{Decoder::Opcode::VCmpxGtI32, Opcode::CompareMaskGtI32},
|
||||
{Decoder::Opcode::VCmpxNeI32, Opcode::CompareMaskNeI32},
|
||||
{Decoder::Opcode::VCmpxGeI32, Opcode::CompareMaskGeI32},
|
||||
{Decoder::Opcode::VCmpLtU16, Opcode::CompareLtU16},
|
||||
{Decoder::Opcode::VCmpEqU16, Opcode::CompareEqU16},
|
||||
{Decoder::Opcode::VCmpLeU16, Opcode::CompareLeU16},
|
||||
{Decoder::Opcode::VCmpGtU16, Opcode::CompareGtU16},
|
||||
{Decoder::Opcode::VCmpNeU16, Opcode::CompareNeU16},
|
||||
{Decoder::Opcode::VCmpGeU16, Opcode::CompareGeU16},
|
||||
{Decoder::Opcode::VCmpFU32, Opcode::CompareFalse},
|
||||
{Decoder::Opcode::VCmpLtU32, Opcode::CompareLtU32},
|
||||
{Decoder::Opcode::VCmpEqU32, Opcode::CompareEqU32},
|
||||
{Decoder::Opcode::VCmpLeU32, Opcode::CompareLeU32},
|
||||
{Decoder::Opcode::VCmpGtU32, Opcode::CompareGtU32},
|
||||
{Decoder::Opcode::VCmpNeU32, Opcode::CompareNeU32},
|
||||
{Decoder::Opcode::VCmpGeU32, Opcode::CompareGeU32},
|
||||
{Decoder::Opcode::VCmpTU32, Opcode::CompareTrue},
|
||||
{Decoder::Opcode::VCmpNeU64, Opcode::CompareNeU64},
|
||||
{Decoder::Opcode::VCmpxLtU32, Opcode::CompareMaskLtU32},
|
||||
{Decoder::Opcode::VCmpxEqU32, Opcode::CompareMaskEqU32},
|
||||
{Decoder::Opcode::VCmpxLeU32, Opcode::CompareMaskLeU32},
|
||||
{Decoder::Opcode::VCmpxGtU32, Opcode::CompareMaskGtU32},
|
||||
{Decoder::Opcode::VCmpxNeU32, Opcode::CompareMaskNeU32},
|
||||
{Decoder::Opcode::VCmpxGeU32, Opcode::CompareMaskGeU32},
|
||||
{Decoder::Opcode::SLoadDword, Opcode::SLoadDword},
|
||||
{Decoder::Opcode::SBufferLoadDword, Opcode::SBufferLoadDword},
|
||||
{Decoder::Opcode::BufferLoadDword, Opcode::BufferLoadDword},
|
||||
{Decoder::Opcode::BufferLoadSbyte, Opcode::BufferLoadSbyte},
|
||||
{Decoder::Opcode::BufferLoadSshort, Opcode::BufferLoadSshort},
|
||||
{Decoder::Opcode::BufferStoreByte, Opcode::BufferStoreByte},
|
||||
{Decoder::Opcode::BufferStoreShort, Opcode::BufferStoreShort},
|
||||
{Decoder::Opcode::BufferStoreDword, Opcode::BufferStoreDword},
|
||||
{Decoder::Opcode::BufferAtomicSwap, Opcode::AtomicSwapU32},
|
||||
{Decoder::Opcode::BufferAtomicAdd, Opcode::AtomicAddU32},
|
||||
{Decoder::Opcode::BufferAtomicSub, Opcode::AtomicSubU32},
|
||||
{Decoder::Opcode::BufferAtomicSMin, Opcode::AtomicSMinI32},
|
||||
{Decoder::Opcode::BufferAtomicUMin, Opcode::AtomicUMinU32},
|
||||
{Decoder::Opcode::BufferAtomicSMax, Opcode::AtomicSMaxI32},
|
||||
{Decoder::Opcode::BufferAtomicUMax, Opcode::AtomicUMaxU32},
|
||||
{Decoder::Opcode::BufferAtomicAnd, Opcode::AtomicAndU32},
|
||||
{Decoder::Opcode::BufferAtomicOr, Opcode::AtomicOrU32},
|
||||
{Decoder::Opcode::BufferAtomicXor, Opcode::AtomicXorU32},
|
||||
{Decoder::Opcode::FlatLoadUbyte, Opcode::FlatLoadUbyte},
|
||||
{Decoder::Opcode::FlatLoadSbyte, Opcode::FlatLoadSbyte},
|
||||
{Decoder::Opcode::FlatLoadSshort, Opcode::FlatLoadSshort},
|
||||
{Decoder::Opcode::FlatStoreByte, Opcode::FlatStoreByte},
|
||||
{Decoder::Opcode::FlatStoreShort, Opcode::FlatStoreShort},
|
||||
{Decoder::Opcode::FlatStoreDword, Opcode::FlatStoreDword},
|
||||
{Decoder::Opcode::DsAddU32, Opcode::AtomicAddU32},
|
||||
{Decoder::Opcode::DsAddRtnU32, Opcode::AtomicAddU32},
|
||||
{Decoder::Opcode::DsSubU32, Opcode::AtomicSubU32},
|
||||
{Decoder::Opcode::DsSubRtnU32, Opcode::AtomicSubU32},
|
||||
{Decoder::Opcode::DsMinI32, Opcode::AtomicSMinI32},
|
||||
{Decoder::Opcode::DsMinRtnI32, Opcode::AtomicSMinI32},
|
||||
{Decoder::Opcode::DsMaxI32, Opcode::AtomicSMaxI32},
|
||||
{Decoder::Opcode::DsMaxRtnI32, Opcode::AtomicSMaxI32},
|
||||
{Decoder::Opcode::DsMinU32, Opcode::AtomicUMinU32},
|
||||
{Decoder::Opcode::DsMinRtnU32, Opcode::AtomicUMinU32},
|
||||
{Decoder::Opcode::DsMaxU32, Opcode::AtomicUMaxU32},
|
||||
{Decoder::Opcode::DsMaxRtnU32, Opcode::AtomicUMaxU32},
|
||||
{Decoder::Opcode::DsAndB32, Opcode::AtomicAndU32},
|
||||
{Decoder::Opcode::DsAndRtnB32, Opcode::AtomicAndU32},
|
||||
{Decoder::Opcode::DsOrB32, Opcode::AtomicOrU32},
|
||||
{Decoder::Opcode::DsOrRtnB32, Opcode::AtomicOrU32},
|
||||
{Decoder::Opcode::DsXorB32, Opcode::AtomicXorU32},
|
||||
{Decoder::Opcode::DsXorRtnB32, Opcode::AtomicXorU32},
|
||||
{Decoder::Opcode::DsWrxchgRtnB32, Opcode::AtomicSwapU32},
|
||||
{Decoder::Opcode::DsReadSbyte, Opcode::DsReadSbyte},
|
||||
{Decoder::Opcode::DsReadUbyte, Opcode::DsReadUbyte},
|
||||
{Decoder::Opcode::DsReadSshort, Opcode::DsReadSshort},
|
||||
{Decoder::Opcode::DsReadUshort, Opcode::DsReadUshort},
|
||||
{Decoder::Opcode::DsRead2B32, Opcode::DsReadB32},
|
||||
{Decoder::Opcode::DsReadB32, Opcode::DsReadB32},
|
||||
{Decoder::Opcode::DsReadB64, Opcode::DsReadB32},
|
||||
{Decoder::Opcode::DsRead2B64, Opcode::DsReadB32},
|
||||
{Decoder::Opcode::DsRead2St64B64, Opcode::DsReadB32},
|
||||
{Decoder::Opcode::DsReadB96, Opcode::DsReadB32},
|
||||
{Decoder::Opcode::DsReadB128, Opcode::DsReadB32},
|
||||
{Decoder::Opcode::DsWriteByte, Opcode::DsWriteByte},
|
||||
{Decoder::Opcode::DsWriteShort, Opcode::DsWriteShort},
|
||||
{Decoder::Opcode::DsWrite2B32, Opcode::DsWriteB32},
|
||||
{Decoder::Opcode::DsWrite2St64B32, Opcode::DsWriteB32},
|
||||
{Decoder::Opcode::DsWrite2B64, Opcode::DsWriteB32},
|
||||
{Decoder::Opcode::DsWrite2St64B64, Opcode::DsWriteB32},
|
||||
{Decoder::Opcode::DsWriteB32, Opcode::DsWriteB32},
|
||||
{Decoder::Opcode::DsWriteB64, Opcode::DsWriteB32},
|
||||
{Decoder::Opcode::DsWriteB96, Opcode::DsWriteB32},
|
||||
{Decoder::Opcode::DsWriteB128, Opcode::DsWriteB32},
|
||||
{Decoder::Opcode::DsMinF32, Opcode::DsMinF32},
|
||||
{Decoder::Opcode::DsMaxF32, Opcode::DsMaxF32},
|
||||
{Decoder::Opcode::DsConsume, Opcode::DsConsume},
|
||||
{Decoder::Opcode::DsAppend, Opcode::DsAppend},
|
||||
{Decoder::Opcode::DsWriteAddtidB32, Opcode::DsWriteAddtidB32},
|
||||
{Decoder::Opcode::DsReadAddtidB32, Opcode::DsReadAddtidB32},
|
||||
{Decoder::Opcode::ImageGetResinfo, Opcode::ImageGetResinfo},
|
||||
{Decoder::Opcode::ImageGetLod, Opcode::ImageGetLod},
|
||||
{Decoder::Opcode::ImageLoad, Opcode::ImageLoad},
|
||||
{Decoder::Opcode::ImageLoadMip, Opcode::ImageLoad},
|
||||
{Decoder::Opcode::ImageStore, Opcode::ImageStore},
|
||||
{Decoder::Opcode::ImageStoreMip, Opcode::ImageStore},
|
||||
{Decoder::Opcode::ImageAtomicAdd, Opcode::AtomicAddU32},
|
||||
{Decoder::Opcode::ImageAtomicUMin, Opcode::AtomicUMinU32},
|
||||
{Decoder::Opcode::ImageAtomicUMax, Opcode::AtomicUMaxU32},
|
||||
{Decoder::Opcode::ImageAtomicAnd, Opcode::AtomicAndU32},
|
||||
{Decoder::Opcode::ImageAtomicOr, Opcode::AtomicOrU32},
|
||||
{Decoder::Opcode::ImageAtomicXor, Opcode::AtomicXorU32},
|
||||
{Decoder::Opcode::ImageSample, Opcode::ImageSample},
|
||||
{Decoder::Opcode::ImageGather4Lz, Opcode::ImageGather4},
|
||||
{Decoder::Opcode::ImageGather4C, Opcode::ImageGather4},
|
||||
{Decoder::Opcode::ImageGather4CLz, Opcode::ImageGather4},
|
||||
{Decoder::Opcode::ImageGather4LzO, Opcode::ImageGather4},
|
||||
{Decoder::Opcode::ImageGather4CO, Opcode::ImageGather4},
|
||||
{Decoder::Opcode::ImageGather4CLzO, Opcode::ImageGather4},
|
||||
{Decoder::Opcode::ImageGather4H, Opcode::ImageGather4},
|
||||
};
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
void SetError(std::string* error, const char* message) {
|
||||
if (error != nullptr) {
|
||||
*error = message;
|
||||
}
|
||||
}
|
||||
|
||||
Opcode LookupIrOpcode(Decoder::Opcode opcode) {
|
||||
for (const auto& op: LOWER_OPS) {
|
||||
if (op.decoded == opcode) {
|
||||
return op.ir;
|
||||
}
|
||||
}
|
||||
return Opcode::MoveU32;
|
||||
}
|
||||
|
||||
bool IsImplemented(Decoder::Opcode opcode) {
|
||||
for (const auto& op: LOWER_OPS) {
|
||||
if (op.decoded == opcode) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsReversedBinary(Decoder::Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Decoder::Opcode::VSubrevF32:
|
||||
case Decoder::Opcode::VSubrevF16:
|
||||
case Decoder::Opcode::VSubrevNcU32:
|
||||
case Decoder::Opcode::VLshlrevB32:
|
||||
case Decoder::Opcode::VLshrrevB32:
|
||||
case Decoder::Opcode::VAshrrevI32:
|
||||
case Decoder::Opcode::VLshlrevB16:
|
||||
case Decoder::Opcode::VLshrrevB16:
|
||||
case Decoder::Opcode::VAshrrevI16:
|
||||
case Decoder::Opcode::VSubrevI32: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsVectorCarryOutOpcode(Decoder::Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Decoder::Opcode::VAddI32:
|
||||
case Decoder::Opcode::VSubI32:
|
||||
case Decoder::Opcode::VSubrevI32: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ScalarResultWritesSccNonZero(Decoder::Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Decoder::Opcode::SAbsI32:
|
||||
case Decoder::Opcode::SBcnt1I32B32:
|
||||
case Decoder::Opcode::SBcnt1I32B64:
|
||||
case Decoder::Opcode::SAndB32:
|
||||
case Decoder::Opcode::SAndn2B32:
|
||||
case Decoder::Opcode::SOrB32:
|
||||
case Decoder::Opcode::SOrn2B32:
|
||||
case Decoder::Opcode::SXorB32:
|
||||
case Decoder::Opcode::SNandB32:
|
||||
case Decoder::Opcode::SNorB32:
|
||||
case Decoder::Opcode::SXnorB32:
|
||||
case Decoder::Opcode::SNotB32:
|
||||
case Decoder::Opcode::SLshlB32:
|
||||
case Decoder::Opcode::SLshrB32:
|
||||
case Decoder::Opcode::SAshrI32:
|
||||
case Decoder::Opcode::SBfeU32:
|
||||
case Decoder::Opcode::SAndB64:
|
||||
case Decoder::Opcode::SAndn2B64:
|
||||
case Decoder::Opcode::SNotB64:
|
||||
case Decoder::Opcode::SOrB64:
|
||||
case Decoder::Opcode::SOrn2B64:
|
||||
case Decoder::Opcode::SXorB64:
|
||||
case Decoder::Opcode::SNandB64:
|
||||
case Decoder::Opcode::SNorB64:
|
||||
case Decoder::Opcode::SXnorB64:
|
||||
case Decoder::Opcode::SLshlB64:
|
||||
case Decoder::Opcode::SLshrB64:
|
||||
case Decoder::Opcode::SBfeU64:
|
||||
case Decoder::Opcode::SWqmB64: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ScalarResultIs64Bit(Decoder::Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Decoder::Opcode::SAndB64:
|
||||
case Decoder::Opcode::SAndn2B64:
|
||||
case Decoder::Opcode::SNotB64:
|
||||
case Decoder::Opcode::SOrB64:
|
||||
case Decoder::Opcode::SOrn2B64:
|
||||
case Decoder::Opcode::SXorB64:
|
||||
case Decoder::Opcode::SNandB64:
|
||||
case Decoder::Opcode::SNorB64:
|
||||
case Decoder::Opcode::SXnorB64:
|
||||
case Decoder::Opcode::SLshlB64:
|
||||
case Decoder::Opcode::SLshrB64:
|
||||
case Decoder::Opcode::SBfeU64:
|
||||
case Decoder::Opcode::SWqmB64: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,752 @@
|
||||
#include "common/assert.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/shader/recompiler/SpirvEmitter.h"
|
||||
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
uint32_t PixelParameterMappedLocation(const EmitterState& state, uint32_t attr) {
|
||||
const auto* ps = state.pixel_input_info;
|
||||
if (state.stage != ShaderType::Pixel || ps == nullptr || attr >= ps->input_num) {
|
||||
return attr;
|
||||
}
|
||||
// VINTRP ATTR selects the PS input slot. SPI_PS_INPUT_CNTL maps that slot to a
|
||||
// VS parameter export, which is the SPIR-V location we must link against.
|
||||
return ps->interpolator_settings[attr] & PsInputOffsetMask;
|
||||
}
|
||||
|
||||
uint32_t PixelParameterLocation(const EmitterState& state, uint32_t attr) {
|
||||
bool used_locations[32] = {};
|
||||
|
||||
for (const auto& input: state.inputs) {
|
||||
if (input.kind != IR::StageInputKind::Parameter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto location = PixelParameterMappedLocation(state, input.location);
|
||||
if (location < std::size(used_locations) && used_locations[location]) {
|
||||
auto fallback_location = input.location;
|
||||
while (fallback_location < std::size(used_locations) &&
|
||||
used_locations[fallback_location]) {
|
||||
fallback_location++;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(fallback_location >= std::size(used_locations));
|
||||
location = fallback_location;
|
||||
}
|
||||
|
||||
if (input.location == attr) {
|
||||
return location;
|
||||
}
|
||||
|
||||
if (location < std::size(used_locations)) {
|
||||
used_locations[location] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return PixelParameterMappedLocation(state, attr);
|
||||
}
|
||||
|
||||
bool PixelParameterIsFlat(const EmitterState& state, uint32_t attr) {
|
||||
const auto* ps = state.pixel_input_info;
|
||||
if (state.stage != ShaderType::Pixel || ps == nullptr || attr >= ps->input_num) {
|
||||
return false;
|
||||
}
|
||||
return (ps->interpolator_settings[attr] & PsInputFlatShade) != 0;
|
||||
}
|
||||
|
||||
void SetError(std::string* error, const char* message) {
|
||||
if (error != nullptr) {
|
||||
*error = message;
|
||||
}
|
||||
}
|
||||
|
||||
void CollectRegister(std::vector<RegisterBinding>* registers, IR::Register reg) {
|
||||
if (std::any_of(registers->begin(), registers->end(),
|
||||
[reg](const RegisterBinding& binding) { return binding.reg == reg; })) {
|
||||
return;
|
||||
}
|
||||
registers->push_back({reg, 0});
|
||||
}
|
||||
|
||||
IR::Operand MakeRegisterOperand(IR::RegisterFile file, uint32_t index) {
|
||||
IR::Operand operand;
|
||||
operand.kind = IR::OperandKind::Register;
|
||||
operand.reg = {file, index};
|
||||
return operand;
|
||||
}
|
||||
|
||||
IR::Register SccRegister() {
|
||||
return {IR::RegisterFile::Scc, static_cast<uint32_t>(Decoder::OperandKind::Scc)};
|
||||
}
|
||||
|
||||
IR::Operand SccOperand() {
|
||||
return MakeRegisterOperand(IR::RegisterFile::Scc,
|
||||
static_cast<uint32_t>(Decoder::OperandKind::Scc));
|
||||
}
|
||||
|
||||
bool IsInactiveWave32ExecHigh(const EmitterState& state, IR::Register reg) {
|
||||
return state.wave_size == 32u && reg.file == IR::RegisterFile::Exec && reg.index == 1u;
|
||||
}
|
||||
|
||||
bool IsMaskRegisterFile(IR::RegisterFile file) {
|
||||
return file == IR::RegisterFile::Exec || file == IR::RegisterFile::Vcc;
|
||||
}
|
||||
|
||||
bool IsSccOperand(const IR::Operand& operand) {
|
||||
return operand.kind == IR::OperandKind::Register && operand.reg.file == IR::RegisterFile::Scc;
|
||||
}
|
||||
|
||||
bool IsCompareOpcode(IR::Opcode op);
|
||||
|
||||
void CollectMaskStateRegisters(std::vector<RegisterBinding>* registers) {
|
||||
CollectRegister(registers, {IR::RegisterFile::Exec, 0});
|
||||
CollectRegister(registers, {IR::RegisterFile::Exec, 1});
|
||||
CollectRegister(registers, {IR::RegisterFile::Vcc, 0});
|
||||
CollectRegister(registers, {IR::RegisterFile::Vcc, 1});
|
||||
CollectRegister(registers, SccRegister());
|
||||
}
|
||||
|
||||
void CollectSequentialRegisters(std::vector<RegisterBinding>* registers, const IR::Operand& base,
|
||||
uint32_t count) {
|
||||
if (base.kind != IR::OperandKind::Register) {
|
||||
return;
|
||||
}
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
auto reg = base.reg;
|
||||
reg.index += i;
|
||||
CollectRegister(registers, reg);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t MaxCollectedVectorRegisterEnd(const std::vector<RegisterBinding>& registers) {
|
||||
uint32_t max_end = 0;
|
||||
for (const auto& binding: registers) {
|
||||
if (binding.reg.file == IR::RegisterFile::Vector) {
|
||||
max_end = std::max(max_end, binding.reg.index + 1u);
|
||||
}
|
||||
}
|
||||
return std::min(max_end, 256u);
|
||||
}
|
||||
|
||||
void CollectMoveRelSourceRegisters(const IR::Program& program,
|
||||
std::vector<RegisterBinding>* registers) {
|
||||
const auto max_vector_end = MaxCollectedVectorRegisterEnd(*registers);
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (inst.op != IR::Opcode::MoveRelSourceU32 ||
|
||||
inst.src[0].kind != IR::OperandKind::Register ||
|
||||
inst.src[0].reg.file != IR::RegisterFile::Vector) {
|
||||
continue;
|
||||
}
|
||||
const auto base_index = inst.src[0].reg.index;
|
||||
const auto limit = std::min(std::max(max_vector_end, base_index + 1u), 256u);
|
||||
CollectSequentialRegisters(registers, inst.src[0], limit - base_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsPairDwordOpcode(IR::Opcode op) {
|
||||
switch (op) {
|
||||
case IR::Opcode::MoveU64:
|
||||
case IR::Opcode::WqmB64:
|
||||
case IR::Opcode::SaveexecB64:
|
||||
case IR::Opcode::BitwiseAndU64:
|
||||
case IR::Opcode::BitwiseAndNotU64:
|
||||
case IR::Opcode::BitwiseOrU64:
|
||||
case IR::Opcode::BitwiseOrNotU64:
|
||||
case IR::Opcode::BitwiseXorU64:
|
||||
case IR::Opcode::BitwiseNandU64:
|
||||
case IR::Opcode::BitwiseNorU64:
|
||||
case IR::Opcode::BitwiseXnorU64:
|
||||
case IR::Opcode::BitwiseNotU64:
|
||||
case IR::Opcode::BitFieldMaskU64:
|
||||
case IR::Opcode::BitFieldExtractU64:
|
||||
case IR::Opcode::BitReplicateB64B32:
|
||||
case IR::Opcode::ShiftLeftLogicalU64:
|
||||
case IR::Opcode::ShiftRightLogicalU64:
|
||||
case IR::Opcode::SelectU64: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t PairDwordSourceCount(IR::Opcode op, uint32_t src_count) {
|
||||
switch (op) {
|
||||
case IR::Opcode::BitFieldMaskU64: return 0;
|
||||
case IR::Opcode::BitReplicateB64B32: return 0;
|
||||
case IR::Opcode::BitFieldExtractU64: return 1;
|
||||
case IR::Opcode::ShiftLeftLogicalU64:
|
||||
case IR::Opcode::ShiftRightLogicalU64:
|
||||
case IR::Opcode::BitwiseNotU64:
|
||||
case IR::Opcode::SaveexecB64:
|
||||
case IR::Opcode::WqmB64:
|
||||
case IR::Opcode::MoveU64: return std::min<uint32_t>(src_count, 1u);
|
||||
default: return src_count;
|
||||
}
|
||||
}
|
||||
|
||||
void CollectRegisters(const IR::Program& program, std::vector<RegisterBinding>* registers) {
|
||||
CollectMaskStateRegisters(registers);
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (inst.dst.kind == IR::OperandKind::Register) {
|
||||
CollectRegister(registers, inst.dst.reg);
|
||||
}
|
||||
if (inst.dst2.kind == IR::OperandKind::Register) {
|
||||
CollectRegister(registers, inst.dst2.reg);
|
||||
}
|
||||
for (uint32_t i = 0; i < inst.src_count; i++) {
|
||||
if (inst.src[i].kind == IR::OperandKind::Register) {
|
||||
CollectRegister(registers, inst.src[i].reg);
|
||||
}
|
||||
}
|
||||
if (IsPairDwordOpcode(inst.op)) {
|
||||
CollectSequentialRegisters(registers, inst.dst, 2);
|
||||
const uint32_t first_pair_src = inst.op == IR::Opcode::SelectU64 ? 1u : 0u;
|
||||
const uint32_t pair_src_count = PairDwordSourceCount(inst.op, inst.src_count);
|
||||
for (uint32_t i = first_pair_src; i < pair_src_count; i++) {
|
||||
CollectSequentialRegisters(registers, inst.src[i], 2);
|
||||
}
|
||||
}
|
||||
if (inst.op == IR::Opcode::BitCountU64 || inst.op == IR::Opcode::FindMsbFromHighU64) {
|
||||
CollectSequentialRegisters(registers, inst.src[0], 2);
|
||||
}
|
||||
if (inst.op == IR::Opcode::CompareNeU64) {
|
||||
CollectSequentialRegisters(registers, inst.src[0], 2);
|
||||
CollectSequentialRegisters(registers, inst.src[1], 2);
|
||||
}
|
||||
if (IsCompareOpcode(inst.op) && inst.dst.kind == IR::OperandKind::Register &&
|
||||
inst.dst.reg.file != IR::RegisterFile::Scc) {
|
||||
CollectSequentialRegisters(registers, inst.dst, 2);
|
||||
}
|
||||
if (inst.op == IR::Opcode::IAddCarryU32 || inst.op == IR::Opcode::ISubBorrowU32) {
|
||||
CollectSequentialRegisters(registers, inst.dst2, 2);
|
||||
}
|
||||
if (inst.op == IR::Opcode::UMadU64U32) {
|
||||
CollectSequentialRegisters(registers, inst.dst, 2);
|
||||
CollectSequentialRegisters(registers, inst.dst2, 2);
|
||||
CollectSequentialRegisters(registers, inst.src[2], 2);
|
||||
}
|
||||
if (inst.op == IR::Opcode::SBufferLoadDword &&
|
||||
inst.memory.kind == IR::ResourceKind::ScalarBuffer) {
|
||||
CollectRegister(registers, {IR::RegisterFile::Scalar, inst.memory.resource * 4u});
|
||||
}
|
||||
if ((inst.op == IR::Opcode::ImageSample || inst.op == IR::Opcode::ImageGather4) &&
|
||||
inst.memory.image_address_components > 1u) {
|
||||
CollectSequentialRegisters(registers, inst.dst, inst.memory.data_dwords);
|
||||
CollectSequentialRegisters(registers, inst.src[0],
|
||||
inst.memory.image_address_components);
|
||||
}
|
||||
if (inst.op == IR::Opcode::ImageGetLod) {
|
||||
CollectSequentialRegisters(registers, inst.dst, inst.memory.data_dwords);
|
||||
CollectSequentialRegisters(registers, inst.src[0],
|
||||
inst.memory.image_address_components);
|
||||
}
|
||||
if (inst.op == IR::Opcode::ImageLoad) {
|
||||
CollectSequentialRegisters(registers, inst.dst, inst.memory.data_dwords);
|
||||
CollectSequentialRegisters(registers, inst.src[0],
|
||||
inst.memory.image_address_components);
|
||||
}
|
||||
if (inst.op == IR::Opcode::ImageStore) {
|
||||
CollectSequentialRegisters(registers, inst.src[0], inst.memory.data_dwords);
|
||||
CollectSequentialRegisters(registers, inst.src[1],
|
||||
inst.memory.image_address_components);
|
||||
}
|
||||
if (inst.memory.kind == IR::ResourceKind::StorageImageUint) {
|
||||
CollectSequentialRegisters(registers, inst.src[1],
|
||||
inst.memory.image_address_components);
|
||||
}
|
||||
}
|
||||
}
|
||||
CollectMoveRelSourceRegisters(program, registers);
|
||||
}
|
||||
|
||||
bool HasOutput(const std::vector<OutputBinding>& outputs, IR::StageOutputKind kind,
|
||||
uint32_t index) {
|
||||
return std::any_of(outputs.begin(), outputs.end(), [kind, index](const OutputBinding& binding) {
|
||||
return binding.kind == kind && binding.index == index;
|
||||
});
|
||||
}
|
||||
|
||||
void CopyProgramInputsAndOutputs(EmitterState* state, const IR::Program& program) {
|
||||
for (const auto& input: program.info.inputs) {
|
||||
state->inputs.push_back(
|
||||
{input.kind, input.location, input.component_count, 0, input.debug_name});
|
||||
}
|
||||
for (const auto& output: program.info.outputs) {
|
||||
if (HasOutput(state->outputs, output.kind, output.index)) {
|
||||
continue;
|
||||
}
|
||||
state->outputs.push_back(
|
||||
{output.kind, output.index, output.location, 0, output.debug_name});
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp) {
|
||||
if (exp.kind == IR::ExportTargetKind::Position) {
|
||||
return state.per_vertex_variable;
|
||||
}
|
||||
if (exp.kind == IR::ExportTargetKind::MrtZ) {
|
||||
return state.depth_variable;
|
||||
}
|
||||
for (const auto& binding: state.outputs) {
|
||||
const auto expected_kind = exp.kind == IR::ExportTargetKind::Mrt
|
||||
? IR::StageOutputKind::Mrt
|
||||
: IR::StageOutputKind::Parameter;
|
||||
if (binding.kind == expected_kind && binding.index == exp.index) {
|
||||
return binding.variable_id;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ProgramNeedsComputeDerivatives(const IR::Program& program) {
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (inst.op == IR::Opcode::ImageGetLod) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProgramNeedsImageGatherExtended(const IR::Program& program) {
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (inst.op == IR::Opcode::ImageGather4) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsLdsOpcode(IR::Opcode op) {
|
||||
switch (op) {
|
||||
case IR::Opcode::DsReadUbyte:
|
||||
case IR::Opcode::DsReadSbyte:
|
||||
case IR::Opcode::DsReadUshort:
|
||||
case IR::Opcode::DsReadSshort:
|
||||
case IR::Opcode::DsReadB32:
|
||||
case IR::Opcode::DsWriteByte:
|
||||
case IR::Opcode::DsWriteShort:
|
||||
case IR::Opcode::DsWriteB32:
|
||||
case IR::Opcode::DsMinF32:
|
||||
case IR::Opcode::DsMaxF32:
|
||||
case IR::Opcode::DsSwizzleB32:
|
||||
case IR::Opcode::DsWriteAddtidB32:
|
||||
case IR::Opcode::DsReadAddtidB32: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ProgramNeedsFunctionLds(const IR::Program& program) {
|
||||
if (program.stage == ShaderType::Compute) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (IsLdsOpcode(inst.op)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProgramNeedsPixelValidMask(const IR::Program& program) {
|
||||
if (program.stage != ShaderType::Pixel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (inst.op == IR::Opcode::Export && inst.export_info.vm) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InstructionHasDppSource(const IR::Instruction& inst) {
|
||||
for (uint32_t i = 0; i < inst.src_count && i < 3u; i++) {
|
||||
if (inst.src[i].dpp) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProgramNeedsSubgroupBallot(const IR::Program& program) {
|
||||
if (program.lane_mask_mode == ShaderLaneMaskMode::PerInvocation) {
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (inst.op == IR::Opcode::WqmB64) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ProgramRequiresExactSubgroupSize(program);
|
||||
}
|
||||
|
||||
bool ProgramNeedsSubgroupShuffle(const IR::Program& program) {
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (inst.op == IR::Opcode::ReadFirstLaneU32 || inst.op == IR::Opcode::ReadLaneU32 ||
|
||||
inst.op == IR::Opcode::Permlane16B32 || inst.op == IR::Opcode::Permlanex16B32 ||
|
||||
inst.op == IR::Opcode::DsSwizzleB32 || inst.op == IR::Opcode::DsConsume ||
|
||||
inst.op == IR::Opcode::DsAppend || InstructionHasDppSource(inst)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsCompareOpcode(IR::Opcode op) {
|
||||
switch (op) {
|
||||
case IR::Opcode::CompareFalse:
|
||||
case IR::Opcode::CompareTrue:
|
||||
case IR::Opcode::CompareEqU32:
|
||||
case IR::Opcode::CompareNeU32:
|
||||
case IR::Opcode::CompareGtU32:
|
||||
case IR::Opcode::CompareGeU32:
|
||||
case IR::Opcode::CompareLtU32:
|
||||
case IR::Opcode::CompareLeU32:
|
||||
case IR::Opcode::CompareNeU64:
|
||||
case IR::Opcode::CompareMaskEqU32:
|
||||
case IR::Opcode::CompareMaskNeU32:
|
||||
case IR::Opcode::CompareMaskGtU32:
|
||||
case IR::Opcode::CompareMaskGeU32:
|
||||
case IR::Opcode::CompareMaskLtU32:
|
||||
case IR::Opcode::CompareMaskLeU32:
|
||||
case IR::Opcode::CompareEqI32:
|
||||
case IR::Opcode::CompareNeI32:
|
||||
case IR::Opcode::CompareGtI32:
|
||||
case IR::Opcode::CompareGeI32:
|
||||
case IR::Opcode::CompareLtI32:
|
||||
case IR::Opcode::CompareLeI32:
|
||||
case IR::Opcode::CompareEqI16:
|
||||
case IR::Opcode::CompareNeI16:
|
||||
case IR::Opcode::CompareGtI16:
|
||||
case IR::Opcode::CompareGeI16:
|
||||
case IR::Opcode::CompareLtI16:
|
||||
case IR::Opcode::CompareLeI16:
|
||||
case IR::Opcode::CompareMaskEqI32:
|
||||
case IR::Opcode::CompareMaskNeI32:
|
||||
case IR::Opcode::CompareMaskGtI32:
|
||||
case IR::Opcode::CompareMaskGeI32:
|
||||
case IR::Opcode::CompareMaskLtI32:
|
||||
case IR::Opcode::CompareMaskLeI32:
|
||||
case IR::Opcode::CompareEqU16:
|
||||
case IR::Opcode::CompareNeU16:
|
||||
case IR::Opcode::CompareGtU16:
|
||||
case IR::Opcode::CompareGeU16:
|
||||
case IR::Opcode::CompareLtU16:
|
||||
case IR::Opcode::CompareLeU16:
|
||||
case IR::Opcode::CompareEqF32:
|
||||
case IR::Opcode::CompareNeF32:
|
||||
case IR::Opcode::CompareGtF32:
|
||||
case IR::Opcode::CompareGeF32:
|
||||
case IR::Opcode::CompareLtF32:
|
||||
case IR::Opcode::CompareLeF32:
|
||||
case IR::Opcode::CompareOrderedF32:
|
||||
case IR::Opcode::CompareUnorderedF32:
|
||||
case IR::Opcode::CompareUnordEqF32:
|
||||
case IR::Opcode::CompareUnordNeF32:
|
||||
case IR::Opcode::CompareUnordGtF32:
|
||||
case IR::Opcode::CompareUnordGeF32:
|
||||
case IR::Opcode::CompareUnordLtF32:
|
||||
case IR::Opcode::CompareUnordLeF32:
|
||||
case IR::Opcode::CompareClassF32:
|
||||
case IR::Opcode::CompareEqF16:
|
||||
case IR::Opcode::CompareNeF16:
|
||||
case IR::Opcode::CompareGtF16:
|
||||
case IR::Opcode::CompareGeF16:
|
||||
case IR::Opcode::CompareLtF16:
|
||||
case IR::Opcode::CompareLeF16:
|
||||
case IR::Opcode::CompareUnordNeF16:
|
||||
case IR::Opcode::CompareMaskEqF16:
|
||||
case IR::Opcode::CompareMaskNeF16:
|
||||
case IR::Opcode::CompareMaskGtF16:
|
||||
case IR::Opcode::CompareMaskGeF16:
|
||||
case IR::Opcode::CompareMaskLtF16:
|
||||
case IR::Opcode::CompareMaskLeF16:
|
||||
case IR::Opcode::CompareMaskUnordNeF16:
|
||||
case IR::Opcode::CompareMaskUnordGeF16:
|
||||
case IR::Opcode::CompareMaskEqF32:
|
||||
case IR::Opcode::CompareMaskNeF32:
|
||||
case IR::Opcode::CompareMaskGtF32:
|
||||
case IR::Opcode::CompareMaskGeF32:
|
||||
case IR::Opcode::CompareMaskLtF32:
|
||||
case IR::Opcode::CompareMaskLeF32:
|
||||
case IR::Opcode::CompareMaskUnordEqF32:
|
||||
case IR::Opcode::CompareMaskUnordNeF32:
|
||||
case IR::Opcode::CompareMaskUnordGtF32:
|
||||
case IR::Opcode::CompareMaskUnordGeF32:
|
||||
case IR::Opcode::CompareMaskUnordLtF32:
|
||||
case IR::Opcode::CompareMaskUnordLeF32: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ProgramNeedsSubgroupLocalInvocationId(const IR::Program& program) {
|
||||
for (const auto& block: program.blocks) {
|
||||
for (const auto& inst: block.instructions) {
|
||||
if (inst.op == IR::Opcode::WqmB64 || inst.op == IR::Opcode::DsSwizzleB32 ||
|
||||
inst.op == IR::Opcode::DsConsume || inst.op == IR::Opcode::DsAppend ||
|
||||
inst.op == IR::Opcode::WriteLaneU32 || inst.op == IR::Opcode::Permlane16B32 ||
|
||||
inst.op == IR::Opcode::Permlanex16B32 || inst.op == IR::Opcode::Export ||
|
||||
InstructionHasDppSource(inst)) {
|
||||
return true;
|
||||
}
|
||||
if (IsCompareOpcode(inst.op)) {
|
||||
return true;
|
||||
}
|
||||
if (inst.dst.kind == IR::OperandKind::Register &&
|
||||
inst.dst.reg.file == IR::RegisterFile::Vector) {
|
||||
return true;
|
||||
}
|
||||
if (inst.dst2.kind == IR::OperandKind::Register &&
|
||||
inst.dst2.reg.file == IR::RegisterFile::Vector) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t PointerForRegister(const EmitterState& state, IR::Register reg) {
|
||||
for (const auto& binding: state.registers) {
|
||||
if (binding.reg == reg) {
|
||||
return binding.pointer_id;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t ConstantU32(EmitterState* state, uint32_t value);
|
||||
void EmitStoreU32(EmitterState* state, const IR::Operand& dst, uint32_t value);
|
||||
uint32_t EmitSubgroupLocalInvocationId(EmitterState* state);
|
||||
uint32_t EmitLaneIndexActiveBool(EmitterState* state, uint32_t lane);
|
||||
[[noreturn]] void ExitDescriptorBindingFailure(const EmitterState& state,
|
||||
IR::DescriptorBindingKind kind, uint32_t resource,
|
||||
const char* reason) {
|
||||
EXIT("shader binding resolution failed during SPIR-V emit: hash=0x%016" PRIx64
|
||||
" stage=%u resource=%" PRIu32 " binding_kind=%u reason=%s\n",
|
||||
state.program->shader_hash, static_cast<unsigned>(state.stage), resource,
|
||||
static_cast<unsigned>(kind), reason);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
DescriptorResourceBinding ResourceForDescriptor(const EmitterState& state,
|
||||
IR::DescriptorBindingKind kind, uint32_t resource) {
|
||||
const auto* descriptor = IR::FindBinding(state.program->bindings, kind);
|
||||
if (descriptor == nullptr) {
|
||||
ExitDescriptorBindingFailure(state, kind, resource, "descriptor group was not allocated");
|
||||
}
|
||||
const auto found =
|
||||
std::find(descriptor->resources.begin(), descriptor->resources.end(), resource);
|
||||
if (found == descriptor->resources.end()) {
|
||||
ExitDescriptorBindingFailure(state, kind, resource,
|
||||
"resource is absent from descriptor group");
|
||||
}
|
||||
return {descriptor, static_cast<uint32_t>(found - descriptor->resources.begin())};
|
||||
}
|
||||
|
||||
uint32_t DescriptorElementPointer(EmitterState* state, uint32_t result_ptr_type,
|
||||
uint32_t variable_id, uint32_t array_index,
|
||||
IR::DescriptorBindingKind kind, uint32_t resource,
|
||||
const char* variable_name) {
|
||||
if (variable_id == 0) {
|
||||
ExitDescriptorBindingFailure(*state, kind, resource, variable_name);
|
||||
}
|
||||
const auto pointer = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpAccessChain, result_ptr_type, pointer, variable_id, ConstantU32(state, array_index)});
|
||||
return pointer;
|
||||
}
|
||||
|
||||
ImageViewKind ImageViewKindFromDimension(Decoder::ImageDimension dimension) {
|
||||
switch (dimension) {
|
||||
case Decoder::ImageDimension::Dim2DArray: return ImageViewKind::Dim2DArray;
|
||||
case Decoder::ImageDimension::Dim3D: return ImageViewKind::Dim3D;
|
||||
default: return ImageViewKind::Dim2D;
|
||||
}
|
||||
}
|
||||
|
||||
ImageViewKind SampledImageViewKind(const EmitterState& state, const IR::MemoryInfo& mem,
|
||||
uint32_t use_pc) {
|
||||
(void)state;
|
||||
(void)use_pc;
|
||||
return ImageViewKindFromDimension(mem.image_dimension);
|
||||
}
|
||||
|
||||
ImageViewKind StorageImageViewKind(const EmitterState& state, const IR::MemoryInfo& mem,
|
||||
bool uint_image, uint32_t use_pc) {
|
||||
(void)state;
|
||||
(void)uint_image;
|
||||
(void)use_pc;
|
||||
return ImageViewKindFromDimension(mem.image_dimension);
|
||||
}
|
||||
|
||||
uint32_t ImageViewCoordinateComponents(ImageViewKind view) {
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim2DArray:
|
||||
case ImageViewKind::Dim3D: return 3u;
|
||||
default: return 2u;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ImageViewImageType(const EmitterState& state, ImageViewKind view, bool integer) {
|
||||
return state.sampled_images[SampledImageIndex(integer, view)].image_type;
|
||||
}
|
||||
|
||||
uint32_t ImageViewSampledImageType(const EmitterState& state, ImageViewKind view, bool integer) {
|
||||
return state.sampled_images[SampledImageIndex(integer, view)].sampled_image_type;
|
||||
}
|
||||
|
||||
uint32_t ImageViewSizeType(const EmitterState& state, ImageViewKind view) {
|
||||
return ImageViewCoordinateComponents(view) == 3u ? state.vec3_uint_type : state.vec2_uint_type;
|
||||
}
|
||||
|
||||
uint32_t StorageImageType(const EmitterState& state, bool uint_image, ImageViewKind view) {
|
||||
if (uint_image) {
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim2DArray: return state.storage_image_uint_2d_array_type;
|
||||
case ImageViewKind::Dim3D: return state.storage_image_uint_3d_type;
|
||||
default: return state.storage_image_uint_type;
|
||||
}
|
||||
}
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim2DArray: return state.storage_image_2d_array_type;
|
||||
case ImageViewKind::Dim3D: return state.storage_image_3d_type;
|
||||
default: return state.storage_image_type;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t StorageImagePointerType(const EmitterState& state, bool uint_image, ImageViewKind view) {
|
||||
if (uint_image) {
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim2DArray: return state.ptr_uniform_storage_image_uint_2d_array;
|
||||
case ImageViewKind::Dim3D: return state.ptr_uniform_storage_image_uint_3d;
|
||||
default: return state.ptr_uniform_storage_image_uint;
|
||||
}
|
||||
}
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim2DArray: return state.ptr_uniform_storage_image_2d_array;
|
||||
case ImageViewKind::Dim3D: return state.ptr_uniform_storage_image_3d;
|
||||
default: return state.ptr_uniform_storage_image;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t StorageImageVariable(const EmitterState& state, bool uint_image, ImageViewKind view) {
|
||||
if (uint_image) {
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim2DArray: return state.storage_image_uint_2d_array_variable;
|
||||
case ImageViewKind::Dim3D: return state.storage_image_uint_3d_variable;
|
||||
default: return state.storage_image_uint_variable;
|
||||
}
|
||||
}
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim2DArray: return state.storage_image_2d_array_variable;
|
||||
case ImageViewKind::Dim3D: return state.storage_image_3d_variable;
|
||||
default: return state.storage_image_variable;
|
||||
}
|
||||
}
|
||||
|
||||
IR::DescriptorBindingKind StorageBindingKind(bool uint_image, ImageViewKind view) {
|
||||
if (uint_image) {
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim2DArray: return IR::DescriptorBindingKind::StorageUint2DArray;
|
||||
case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::StorageUint3D;
|
||||
default: return IR::DescriptorBindingKind::StorageUint2D;
|
||||
}
|
||||
}
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim2DArray: return IR::DescriptorBindingKind::Storage2DArray;
|
||||
case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::Storage3D;
|
||||
default: return IR::DescriptorBindingKind::Storage2D;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t LoadSampledImageDescriptor(EmitterState* state, const IR::MemoryInfo& mem, uint32_t use_pc,
|
||||
ImageViewKind view) {
|
||||
(void)use_pc;
|
||||
const bool integer = mem.kind == IR::ResourceKind::ImageUint;
|
||||
const auto kind = SampledBindingKind(integer, view);
|
||||
const auto binding = ResourceForDescriptor(*state, kind, mem.resource);
|
||||
const auto& descriptors = state->sampled_images[SampledImageIndex(integer, view)];
|
||||
const auto pointer = DescriptorElementPointer(
|
||||
state, descriptors.pointer_type, descriptors.variable, binding.array_index, kind,
|
||||
mem.resource, "sampled image descriptor array was not emitted");
|
||||
const auto image = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpLoad, ImageViewImageType(*state, view, integer), image, pointer});
|
||||
return image;
|
||||
}
|
||||
|
||||
uint32_t LoadSamplerDescriptor(EmitterState* state, uint32_t sampler, uint32_t use_pc) {
|
||||
(void)use_pc;
|
||||
const auto binding =
|
||||
ResourceForDescriptor(*state, IR::DescriptorBindingKind::Samplers, sampler);
|
||||
const auto pointer = DescriptorElementPointer(
|
||||
state, state->ptr_uniform_sampler, state->sampler_variable, binding.array_index,
|
||||
IR::DescriptorBindingKind::Samplers, sampler, "sampler descriptor array was not emitted");
|
||||
const auto sampler_id = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpLoad, state->sampler_type, sampler_id, pointer});
|
||||
return sampler_id;
|
||||
}
|
||||
|
||||
uint32_t MakeSampledImage(EmitterState* state, const IR::MemoryInfo& mem, uint32_t use_pc,
|
||||
ImageViewKind view) {
|
||||
const auto image = LoadSampledImageDescriptor(state, mem, use_pc, view);
|
||||
const auto sampler = LoadSamplerDescriptor(state, mem.sampler, use_pc);
|
||||
if (image == 0 || sampler == 0) {
|
||||
ExitDescriptorBindingFailure(
|
||||
*state, SampledBindingKind(mem.kind == IR::ResourceKind::ImageUint, view), mem.resource,
|
||||
"sampled image or sampler descriptor load failed");
|
||||
}
|
||||
const auto sampled_image = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpSampledImage,
|
||||
ImageViewSampledImageType(*state, view, mem.kind == IR::ResourceKind::ImageUint),
|
||||
sampled_image, image, sampler});
|
||||
return sampled_image;
|
||||
}
|
||||
|
||||
uint32_t StorageImageDescriptorPointer(EmitterState* state, uint32_t resource, bool uint_image,
|
||||
uint32_t use_pc, ImageViewKind view) {
|
||||
(void)use_pc;
|
||||
const auto kind = StorageBindingKind(uint_image, view);
|
||||
const auto binding = ResourceForDescriptor(*state, kind, resource);
|
||||
const auto ptr_type = StorageImagePointerType(*state, uint_image, view);
|
||||
const auto variable = StorageImageVariable(*state, uint_image, view);
|
||||
return DescriptorElementPointer(state, ptr_type, variable, binding.array_index, kind, resource,
|
||||
"storage image descriptor array was not emitted");
|
||||
}
|
||||
|
||||
uint32_t LoadStorageImageDescriptor(EmitterState* state, uint32_t resource, bool uint_image,
|
||||
uint32_t use_pc, ImageViewKind view) {
|
||||
const auto pointer = StorageImageDescriptorPointer(state, resource, uint_image, use_pc, view);
|
||||
if (pointer == 0) {
|
||||
ExitDescriptorBindingFailure(*state, StorageBindingKind(uint_image, view), resource,
|
||||
"storage image descriptor pointer creation failed");
|
||||
}
|
||||
const auto type = StorageImageType(*state, uint_image, view);
|
||||
const auto image = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpLoad, type, image, pointer});
|
||||
return image;
|
||||
}
|
||||
|
||||
uint32_t ExecutionModelForStage(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Vertex: return ExecutionModelVertex;
|
||||
case ShaderType::Pixel: return ExecutionModelFragment;
|
||||
default: return ExecutionModelGLCompute;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,154 @@
|
||||
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
uint32_t EmitExportComponentF32(EmitterState* state, const IR::Instruction& inst,
|
||||
uint32_t component) {
|
||||
const bool enabled = ((inst.export_info.en >> component) & 1u) != 0;
|
||||
if (!enabled || component >= inst.src_count || component >= 4u) {
|
||||
return ConstantF32(state, component == 3u ? 0x3f800000u : 0u);
|
||||
}
|
||||
return EmitFloatLoad(state, inst.src[component]);
|
||||
}
|
||||
|
||||
uint32_t EmitExportVec4F32(EmitterState* state, const IR::Instruction& inst) {
|
||||
if (inst.export_info.compr) {
|
||||
uint32_t components[4] = {
|
||||
ConstantF32(state, 0u),
|
||||
ConstantF32(state, 0u),
|
||||
ConstantF32(state, 0u),
|
||||
ConstantF32(state, 0x3f800000u),
|
||||
};
|
||||
for (uint32_t pair_index = 0; pair_index < 2u && pair_index < inst.src_count;
|
||||
pair_index++) {
|
||||
const auto raw = EmitValueLoad(state, inst.src[pair_index]);
|
||||
const auto unpacked = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpExtInst, state->vec2_float_type, unpacked,
|
||||
state->glsl_std450, GlslUnpackHalf2x16, raw});
|
||||
for (uint32_t lane = 0; lane < 2u; lane++) {
|
||||
const auto component = pair_index * 2u + lane;
|
||||
if (((inst.export_info.en >> component) & 1u) == 0) {
|
||||
continue;
|
||||
}
|
||||
components[component] = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpCompositeExtract, state->float_type, components[component], unpacked, lane});
|
||||
}
|
||||
}
|
||||
const auto vec = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpCompositeConstruct, state->vec4_float_type, vec,
|
||||
components[0], components[1], components[2], components[3]});
|
||||
return vec;
|
||||
}
|
||||
|
||||
const auto x = EmitExportComponentF32(state, inst, 0);
|
||||
const auto y = EmitExportComponentF32(state, inst, 1);
|
||||
const auto z = EmitExportComponentF32(state, inst, 2);
|
||||
const auto w = EmitExportComponentF32(state, inst, 3);
|
||||
const auto vec = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpCompositeConstruct, state->vec4_float_type, vec, x, y, z, w});
|
||||
return vec;
|
||||
}
|
||||
|
||||
bool ExportWritesData(const IR::Instruction& inst) {
|
||||
switch (inst.export_info.kind) {
|
||||
case IR::ExportTargetKind::Null:
|
||||
case IR::ExportTargetKind::Primitive: return false;
|
||||
case IR::ExportTargetKind::MrtZ: return (inst.export_info.en & 0x5u) != 0;
|
||||
default: return inst.export_info.en != 0;
|
||||
}
|
||||
}
|
||||
|
||||
void EmitMrtZExport(EmitterState* state, const IR::Instruction& inst) {
|
||||
if ((inst.export_info.en & 0x1u) != 0 && state->depth_variable != 0) {
|
||||
state->builder.AddFunction(
|
||||
{OpStore, state->depth_variable, EmitExportComponentF32(state, inst, 0)});
|
||||
}
|
||||
|
||||
if ((inst.export_info.en & 0x4u) != 0 && state->sample_mask_variable != 0) {
|
||||
const auto raw =
|
||||
inst.src_count > 2u ? EmitValueLoad(state, inst.src[2]) : ConstantU32(state, 0);
|
||||
const auto mask = state->builder.AllocateId();
|
||||
const auto ptr = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitcast, state->int_type, mask, raw});
|
||||
state->builder.AddFunction({OpAccessChain, state->ptr_output_int, ptr,
|
||||
state->sample_mask_variable, ConstantU32(state, 0)});
|
||||
state->builder.AddFunction({OpStore, ptr, mask});
|
||||
}
|
||||
}
|
||||
|
||||
void EmitExport(EmitterState* state, const IR::Instruction& inst) {
|
||||
if (inst.export_info.kind == IR::ExportTargetKind::Null ||
|
||||
inst.export_info.kind == IR::ExportTargetKind::Primitive) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ExportWritesData(inst)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (inst.export_info.kind == IR::ExportTargetKind::MrtZ) {
|
||||
EmitMrtZExport(state, inst);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto variable = OutputVariableForExport(*state, inst.export_info);
|
||||
if (variable == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto value = EmitExportVec4F32(state, inst);
|
||||
if (inst.export_info.kind == IR::ExportTargetKind::Position) {
|
||||
const auto pointer = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpAccessChain, state->ptr_output_vec4_float, pointer, variable,
|
||||
ConstantU32(state, 0)});
|
||||
state->builder.AddFunction({OpStore, pointer, value});
|
||||
return;
|
||||
}
|
||||
|
||||
state->builder.AddFunction({OpStore, variable, value});
|
||||
}
|
||||
|
||||
bool ExportUsesPixelValidMask(const EmitterState& state, const IR::Instruction& inst) {
|
||||
return state.stage == ShaderType::Pixel && inst.export_info.vm && state.needs_pixel_valid_mask;
|
||||
}
|
||||
|
||||
void EmitKillIfBoolFalse(EmitterState* state, uint32_t active) {
|
||||
const auto kill_label = state->builder.AllocateId();
|
||||
const auto merge_label = state->builder.AllocateId();
|
||||
const auto inactive = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpLogicalNot, state->bool_type, inactive, active});
|
||||
state->builder.AddFunction({OpSelectionMerge, merge_label, SelectionControlNone});
|
||||
state->builder.AddFunction({OpBranchConditional, inactive, kill_label, merge_label});
|
||||
state->builder.AddFunction({OpLabel, kill_label});
|
||||
state->builder.AddFunction({OpKill});
|
||||
state->builder.AddFunction({OpLabel, merge_label});
|
||||
}
|
||||
|
||||
void EmitUpdatePixelValidMask(EmitterState* state) {
|
||||
if (state->pixel_valid_mask_variable == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto active = EmitExecActiveBool(state);
|
||||
const auto value = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpSelect, state->uint_type, value, active, ConstantU32(state, 1), ConstantU32(state, 0)});
|
||||
state->builder.AddFunction({OpStore, state->pixel_valid_mask_variable, value});
|
||||
}
|
||||
|
||||
void EmitKillIfPixelValidMaskInactive(EmitterState* state) {
|
||||
if (state->pixel_valid_mask_variable == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto mask_value = state->builder.AllocateId();
|
||||
const auto active = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpLoad, state->uint_type, mask_value, state->pixel_valid_mask_variable});
|
||||
state->builder.AddFunction(
|
||||
{OpINotEqual, state->bool_type, active, mask_value, ConstantU32(state, 0)});
|
||||
EmitKillIfBoolFalse(state, active);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
|
||||
@@ -0,0 +1,381 @@
|
||||
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
void EmitMoveU32(EmitterState* state, const IR::Instruction& inst) {
|
||||
const auto value = EmitValueLoad(state, inst.src[0]);
|
||||
EmitStoreU32(state, inst.dst, value);
|
||||
}
|
||||
|
||||
void EmitMoveF32Bits(EmitterState* state, const IR::Instruction& inst) {
|
||||
const auto value = EmitFloatLoad(state, inst.src[0]);
|
||||
const auto bits = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitcast, state->uint_type, bits, value});
|
||||
EmitStoreU32(state, inst.dst, bits);
|
||||
}
|
||||
|
||||
uint32_t MoveRelRegisterLimit(const EmitterState& state, IR::Register base) {
|
||||
uint32_t limit = base.index + 1u;
|
||||
for (const auto& binding: state.registers) {
|
||||
if (binding.reg.file == IR::RegisterFile::Vector) {
|
||||
limit = std::max(limit, binding.reg.index + 1u);
|
||||
}
|
||||
}
|
||||
return std::min(limit, 256u);
|
||||
}
|
||||
|
||||
void EmitMoveRelSourceU32(EmitterState* state, const IR::Instruction& inst) {
|
||||
if (inst.src[0].kind != IR::OperandKind::Register ||
|
||||
inst.src[0].reg.file != IR::RegisterFile::Vector) {
|
||||
EmitMoveU32(state, inst);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto base_reg = inst.src[0].reg;
|
||||
const auto m0 = inst.src_count > 1u ? EmitValueLoad(state, inst.src[1]) : ConstantU32(state, 0);
|
||||
auto selected = EmitRegisterLoad(state, base_reg);
|
||||
const auto limit = MoveRelRegisterLimit(*state, base_reg);
|
||||
for (uint32_t reg = base_reg.index + 1u; reg < limit; reg++) {
|
||||
const auto candidate = EmitRegisterLoad(state, {IR::RegisterFile::Vector, reg});
|
||||
const auto match = state->builder.AllocateId();
|
||||
const auto next = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpIEqual, state->bool_type, match, m0, ConstantU32(state, reg - base_reg.index)});
|
||||
state->builder.AddFunction({OpSelect, state->uint_type, next, match, candidate, selected});
|
||||
selected = next;
|
||||
}
|
||||
EmitStoreU32(state, inst.dst, selected);
|
||||
}
|
||||
|
||||
void EmitMoveRelDestU32(EmitterState* state, const IR::Instruction& inst) {
|
||||
if (inst.dst.kind != IR::OperandKind::Register ||
|
||||
inst.dst.reg.file != IR::RegisterFile::Vector) {
|
||||
EmitMoveU32(state, inst);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto base_reg = inst.dst.reg;
|
||||
const auto value = EmitValueLoad(state, inst.src[0]);
|
||||
const auto m0 = inst.src_count > 1u ? EmitValueLoad(state, inst.src[1]) : ConstantU32(state, 0);
|
||||
const auto exec_active = EmitExecActiveBool(state);
|
||||
const auto limit = MoveRelRegisterLimit(*state, base_reg);
|
||||
for (uint32_t reg = base_reg.index; reg < limit; reg++) {
|
||||
const auto pointer = PointerForRegister(*state, {IR::RegisterFile::Vector, reg});
|
||||
if (pointer == 0) {
|
||||
continue;
|
||||
}
|
||||
const auto old_value = state->builder.AllocateId();
|
||||
const auto match = state->builder.AllocateId();
|
||||
const auto write = state->builder.AllocateId();
|
||||
const auto merged = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpLoad, state->uint_type, old_value, pointer});
|
||||
state->builder.AddFunction(
|
||||
{OpIEqual, state->bool_type, match, m0, ConstantU32(state, reg - base_reg.index)});
|
||||
state->builder.AddFunction({OpLogicalAnd, state->bool_type, write, exec_active, match});
|
||||
state->builder.AddFunction({OpSelect, state->uint_type, merged, write, value, old_value});
|
||||
state->builder.AddFunction({OpStore, pointer, merged});
|
||||
}
|
||||
}
|
||||
|
||||
void EmitMoveU64(EmitterState* state, const IR::Instruction& inst) {
|
||||
if (state->per_invocation_masks && inst.dst.kind == IR::OperandKind::Register &&
|
||||
IsMaskRegisterFile(inst.dst.reg.file)) {
|
||||
EmitPerInvocationMask(state, inst.dst, EmitLaneMaskOperandActiveBool(state, inst.src[0]));
|
||||
return;
|
||||
}
|
||||
const auto low = EmitSequentialValueLoad(state, inst.src[0], 0);
|
||||
const auto high = EmitSequentialValueLoad(state, inst.src[0], 1);
|
||||
EmitStoreU32(state, inst.dst, low);
|
||||
EmitStoreU32(state, OffsetRegisterOperand(inst.dst, 1), high);
|
||||
}
|
||||
|
||||
uint32_t EmitWqmLaneU32(EmitterState* state, uint32_t src) {
|
||||
uint32_t ret = ConstantU32(state, 0);
|
||||
for (uint32_t i = 0; i < 8u; i++) {
|
||||
const auto mask = 0x0fu << (i * 4u);
|
||||
const auto masked = state->builder.AllocateId();
|
||||
const auto non_zero = state->builder.AllocateId();
|
||||
const auto expanded = state->builder.AllocateId();
|
||||
const auto combined = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, masked, src, ConstantU32(state, mask)});
|
||||
state->builder.AddFunction(
|
||||
{OpINotEqual, state->bool_type, non_zero, masked, ConstantU32(state, 0)});
|
||||
state->builder.AddFunction({OpSelect, state->uint_type, expanded, non_zero,
|
||||
ConstantU32(state, mask), ConstantU32(state, 0)});
|
||||
state->builder.AddFunction({OpBitwiseOr, state->uint_type, combined, ret, expanded});
|
||||
ret = combined;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void EmitWqmB64(EmitterState* state, const IR::Instruction& inst) {
|
||||
if (!state->per_invocation_masks && inst.dst.kind == IR::OperandKind::Register &&
|
||||
inst.dst.reg.file == IR::RegisterFile::Scalar) {
|
||||
const auto low = EmitSequentialValueLoad(state, inst.src[0], 0);
|
||||
const auto high = EmitSequentialValueLoad(state, inst.src[0], 1);
|
||||
const auto ret_low = EmitWqmLaneU32(state, low);
|
||||
const auto ret_high = EmitWqmLaneU32(state, high);
|
||||
EmitStoreU32(state, inst.dst, ret_low);
|
||||
EmitStoreU32(state, OffsetRegisterOperand(inst.dst, 1), ret_high);
|
||||
return;
|
||||
}
|
||||
const auto ballot = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpGroupNonUniformBallot, state->vec4_uint_type, ballot,
|
||||
ConstantU32(state, ScopeSubgroup),
|
||||
EmitLaneMaskOperandActiveBool(state, inst.src[0])});
|
||||
const auto low = state->builder.AllocateId();
|
||||
const auto high = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpCompositeExtract, state->uint_type, low, ballot, 0});
|
||||
state->builder.AddFunction({OpCompositeExtract, state->uint_type, high, ballot, 1});
|
||||
const auto wqm_low = EmitWqmLaneU32(state, low);
|
||||
const auto wqm_high = EmitWqmLaneU32(state, high);
|
||||
const auto lane = EmitSubgroupLocalInvocationId(state);
|
||||
const auto high_half = state->builder.AllocateId();
|
||||
const auto mask = state->builder.AllocateId();
|
||||
const auto bit_index = state->builder.AllocateId();
|
||||
const auto bit = state->builder.AllocateId();
|
||||
const auto hit = state->builder.AllocateId();
|
||||
const auto active = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpUGreaterThanEqual, state->bool_type, high_half, lane, ConstantU32(state, 32)});
|
||||
state->builder.AddFunction({OpSelect, state->uint_type, mask, high_half, wqm_high, wqm_low});
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, bit_index, lane, ConstantU32(state, 31)});
|
||||
state->builder.AddFunction(
|
||||
{OpShiftLeftLogical, state->uint_type, bit, ConstantU32(state, 1), bit_index});
|
||||
state->builder.AddFunction({OpBitwiseAnd, state->uint_type, hit, mask, bit});
|
||||
state->builder.AddFunction({OpINotEqual, state->bool_type, active, hit, ConstantU32(state, 0)});
|
||||
if (state->per_invocation_masks) {
|
||||
EmitPerInvocationMask(state, inst.dst, active);
|
||||
} else {
|
||||
const auto result = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpSelect, state->uint_type, result, active,
|
||||
ConstantU32(state, 1), ConstantU32(state, 0)});
|
||||
EmitStoreU32(state, inst.dst, result);
|
||||
EmitStoreU32(state, OffsetRegisterOperand(inst.dst, 1), ConstantU32(state, 0));
|
||||
}
|
||||
}
|
||||
|
||||
void EmitSaveexecPerInvocation(EmitterState* state, const IR::Instruction& inst) {
|
||||
const auto old_exec = EmitExecActiveBool(state);
|
||||
const auto src = EmitLaneMaskOperandActiveBool(state, inst.src[0]);
|
||||
EmitPerInvocationMask(state, inst.dst, old_exec);
|
||||
|
||||
uint32_t lhs = old_exec;
|
||||
uint32_t rhs = src;
|
||||
bool use_or = false;
|
||||
switch (inst.saveexec_mode) {
|
||||
case IR::SaveexecMode::And: break;
|
||||
case IR::SaveexecMode::Andn1: rhs = EmitLogicalNotBool(state, src); break;
|
||||
case IR::SaveexecMode::Orn2:
|
||||
lhs = EmitLogicalNotBool(state, old_exec);
|
||||
use_or = true;
|
||||
break;
|
||||
}
|
||||
const auto result =
|
||||
use_or ? EmitLogicalOrBool(state, lhs, rhs) : EmitLogicalAndBool(state, lhs, rhs);
|
||||
EmitPerInvocationMask(state, MakeRegisterOperand(IR::RegisterFile::Exec, 0), result);
|
||||
EmitStoreSccBool(state, result);
|
||||
}
|
||||
|
||||
void EmitSaveexecB32(EmitterState* state, const IR::Instruction& inst) {
|
||||
if (state->per_invocation_masks) {
|
||||
EmitSaveexecPerInvocation(state, inst);
|
||||
return;
|
||||
}
|
||||
const auto old_low = EmitRegisterLoad(state, {IR::RegisterFile::Exec, 0});
|
||||
const auto src_low = EmitValueLoad(state, inst.src[0]);
|
||||
|
||||
EmitStoreU32(state, inst.dst, old_low);
|
||||
|
||||
uint32_t rhs_low = src_low;
|
||||
if (inst.saveexec_mode == IR::SaveexecMode::Andn1) {
|
||||
rhs_low = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpNot, state->uint_type, rhs_low, src_low});
|
||||
}
|
||||
|
||||
const auto new_low = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitwiseAnd, state->uint_type, new_low, rhs_low, old_low});
|
||||
EmitStoreU32(state, MakeRegisterOperand(IR::RegisterFile::Exec, 0), new_low);
|
||||
|
||||
if (state->wave_size == 32u) {
|
||||
EmitStoreU32(state, MakeRegisterOperand(IR::RegisterFile::Exec, 1), ConstantU32(state, 0));
|
||||
}
|
||||
|
||||
const auto cond = state->builder.AllocateId();
|
||||
const auto scc = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpINotEqual, state->bool_type, cond, new_low, ConstantU32(state, 0)});
|
||||
state->builder.AddFunction(
|
||||
{OpSelect, state->uint_type, scc, cond, ConstantU32(state, 1), ConstantU32(state, 0)});
|
||||
EmitStoreU32(state, SccOperand(), scc);
|
||||
}
|
||||
|
||||
void EmitSaveexecB64(EmitterState* state, const IR::Instruction& inst) {
|
||||
if (state->per_invocation_masks) {
|
||||
EmitSaveexecPerInvocation(state, inst);
|
||||
return;
|
||||
}
|
||||
const auto old_low = EmitRegisterLoad(state, {IR::RegisterFile::Exec, 0});
|
||||
const auto old_high = EmitRegisterLoad(state, {IR::RegisterFile::Exec, 1});
|
||||
const auto src_low = EmitSequentialValueLoad(state, inst.src[0], 0);
|
||||
const auto src_high = EmitSequentialValueLoad(state, inst.src[0], 1);
|
||||
|
||||
EmitStoreU32(state, inst.dst, old_low);
|
||||
EmitStoreU32(state, OffsetRegisterOperand(inst.dst, 1), old_high);
|
||||
|
||||
uint32_t lhs_low = old_low;
|
||||
uint32_t lhs_high = old_high;
|
||||
uint32_t rhs_low = src_low;
|
||||
uint32_t rhs_high = src_high;
|
||||
uint32_t opcode = OpBitwiseAnd;
|
||||
if (inst.saveexec_mode == IR::SaveexecMode::Andn1) {
|
||||
rhs_low = state->builder.AllocateId();
|
||||
rhs_high = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpNot, state->uint_type, rhs_low, src_low});
|
||||
state->builder.AddFunction({OpNot, state->uint_type, rhs_high, src_high});
|
||||
}
|
||||
if (inst.saveexec_mode == IR::SaveexecMode::Orn2) {
|
||||
lhs_low = state->builder.AllocateId();
|
||||
lhs_high = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpNot, state->uint_type, lhs_low, old_low});
|
||||
state->builder.AddFunction({OpNot, state->uint_type, lhs_high, old_high});
|
||||
opcode = OpBitwiseOr;
|
||||
}
|
||||
|
||||
const auto new_low = state->builder.AllocateId();
|
||||
const auto new_high = state->builder.AllocateId();
|
||||
state->builder.AddFunction({opcode, state->uint_type, new_low, lhs_low, rhs_low});
|
||||
state->builder.AddFunction({opcode, state->uint_type, new_high, lhs_high, rhs_high});
|
||||
EmitStoreU32(state, MakeRegisterOperand(IR::RegisterFile::Exec, 0), new_low);
|
||||
EmitStoreU32(state, MakeRegisterOperand(IR::RegisterFile::Exec, 1), new_high);
|
||||
|
||||
const auto active_new_high = state->wave_size == 32u ? ConstantU32(state, 0) : new_high;
|
||||
const auto mask = state->builder.AllocateId();
|
||||
const auto cond = state->builder.AllocateId();
|
||||
const auto scc = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitwiseOr, state->uint_type, mask, new_low, active_new_high});
|
||||
state->builder.AddFunction({OpINotEqual, state->bool_type, cond, mask, ConstantU32(state, 0)});
|
||||
state->builder.AddFunction(
|
||||
{OpSelect, state->uint_type, scc, cond, ConstantU32(state, 1), ConstantU32(state, 0)});
|
||||
EmitStoreU32(state, SccOperand(), scc);
|
||||
}
|
||||
|
||||
void EmitReadFirstLaneU32(EmitterState* state, const IR::Instruction& inst) {
|
||||
const auto src = EmitValueLoad(state, inst.src[0]);
|
||||
const auto active = EmitExecActiveBool(state);
|
||||
const auto ballot = state->builder.AllocateId();
|
||||
const auto first_lane = state->builder.AllocateId();
|
||||
const auto first_value = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpGroupNonUniformBallot, state->vec4_uint_type, ballot,
|
||||
ConstantU32(state, ScopeSubgroup), active});
|
||||
state->builder.AddFunction({OpGroupNonUniformBallotFindLSB, state->uint_type, first_lane,
|
||||
ConstantU32(state, ScopeSubgroup), ballot});
|
||||
state->builder.AddFunction({OpGroupNonUniformShuffle, state->uint_type, first_value,
|
||||
ConstantU32(state, ScopeSubgroup), src, first_lane});
|
||||
EmitStoreU32(state, inst.dst, first_value);
|
||||
}
|
||||
|
||||
uint32_t EmitLaneIndex(EmitterState* state, const IR::Operand& operand) {
|
||||
const auto lane = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitwiseAnd, state->uint_type, lane, EmitValueLoad(state, operand),
|
||||
ConstantU32(state, 63)});
|
||||
return lane;
|
||||
}
|
||||
|
||||
void EmitReadLaneU32(EmitterState* state, const IR::Instruction& inst) {
|
||||
const auto src = EmitValueLoad(state, inst.src[0]);
|
||||
const auto lane = EmitLaneIndex(state, inst.src[1]);
|
||||
const auto value = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpGroupNonUniformShuffle, state->uint_type, value,
|
||||
ConstantU32(state, ScopeSubgroup), src, lane});
|
||||
EmitStoreU32(state, inst.dst, value);
|
||||
}
|
||||
|
||||
void EmitWriteLaneU32(EmitterState* state, const IR::Instruction& inst) {
|
||||
const auto value = EmitValueLoad(state, inst.src[0]);
|
||||
const auto lane = EmitLaneIndex(state, inst.src[1]);
|
||||
const auto subid = EmitSubgroupLocalInvocationId(state);
|
||||
const auto hit = state->builder.AllocateId();
|
||||
const auto pointer =
|
||||
inst.dst.kind == IR::OperandKind::Register ? PointerForRegister(*state, inst.dst.reg) : 0;
|
||||
const auto old = pointer != 0 ? state->builder.AllocateId() : ConstantU32(state, 0);
|
||||
const auto merged = state->builder.AllocateId();
|
||||
if (pointer != 0) {
|
||||
state->builder.AddFunction({OpLoad, state->uint_type, old, pointer});
|
||||
}
|
||||
state->builder.AddFunction({OpIEqual, state->bool_type, hit, subid, lane});
|
||||
state->builder.AddFunction({OpSelect, state->uint_type, merged, hit, value, old});
|
||||
if (pointer != 0) {
|
||||
// RDNA2 V_WRITELANE_B32 ignores EXEC, unlike ordinary VGPR destination writes.
|
||||
state->builder.AddFunction({OpStore, pointer, merged});
|
||||
}
|
||||
}
|
||||
|
||||
void EmitPermlaneB32(EmitterState* state, const IR::Instruction& inst, bool x16) {
|
||||
const auto subid = EmitSubgroupLocalInvocationId(state);
|
||||
const auto row = state->builder.AllocateId();
|
||||
const auto lane = state->builder.AllocateId();
|
||||
const auto lane8 = state->builder.AllocateId();
|
||||
const auto shift = state->builder.AllocateId();
|
||||
const auto upper = state->builder.AllocateId();
|
||||
const auto selected = state->builder.AllocateId();
|
||||
const auto index0 = state->builder.AllocateId();
|
||||
const auto index1 = state->builder.AllocateId();
|
||||
const auto target = state->builder.AllocateId();
|
||||
const auto shuffled = state->builder.AllocateId();
|
||||
const auto value = EmitValueLoad(state, inst.src[0]);
|
||||
const auto src1 = EmitValueLoad(state, inst.src[1]);
|
||||
const auto src2 = EmitValueLoad(state, inst.src[2]);
|
||||
uint32_t row_value = row;
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, row, subid, ConstantU32(state, 0xfffffff0u)});
|
||||
if (x16) {
|
||||
row_value = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseXor, state->uint_type, row_value, row, ConstantU32(state, 16)});
|
||||
}
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, lane, subid, ConstantU32(state, 15)});
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, lane8, lane, ConstantU32(state, 7)});
|
||||
state->builder.AddFunction(
|
||||
{OpShiftLeftLogical, state->uint_type, shift, lane8, ConstantU32(state, 2)});
|
||||
state->builder.AddFunction(
|
||||
{OpUGreaterThanEqual, state->bool_type, upper, lane, ConstantU32(state, 8)});
|
||||
state->builder.AddFunction({OpSelect, state->uint_type, selected, upper, src2, src1});
|
||||
state->builder.AddFunction({OpShiftRightLogical, state->uint_type, index0, selected, shift});
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, index1, index0, ConstantU32(state, 15)});
|
||||
state->builder.AddFunction({OpBitwiseOr, state->uint_type, target, row_value, index1});
|
||||
state->builder.AddFunction({OpGroupNonUniformShuffle, state->uint_type, shuffled,
|
||||
ConstantU32(state, ScopeSubgroup), value, target});
|
||||
uint32_t ret = shuffled;
|
||||
if (!inst.dst.op_sel) {
|
||||
const auto source_active = EmitLaneIndexActiveBool(state, target);
|
||||
ret = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpSelect, state->uint_type, ret, source_active, shuffled, ConstantU32(state, 0)});
|
||||
}
|
||||
EmitStoreU32(state, inst.dst, ret);
|
||||
}
|
||||
|
||||
void EmitControlNop(EmitterState* state, const IR::Instruction& inst) {
|
||||
(void)state;
|
||||
(void)inst;
|
||||
}
|
||||
|
||||
void EmitWaitcnt(EmitterState* state, const IR::Instruction& inst) {
|
||||
(void)state;
|
||||
(void)inst;
|
||||
}
|
||||
|
||||
void EmitBarrier(EmitterState* state, const IR::Instruction& inst) {
|
||||
(void)inst;
|
||||
const auto semantics = MemorySemanticsAcquireRelease | MemorySemanticsWorkgroupMemory;
|
||||
state->builder.AddFunction({OpControlBarrier, ConstantU32(state, ScopeWorkgroup),
|
||||
ConstantU32(state, ScopeWorkgroup), ConstantU32(state, semantics)});
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
|
||||
@@ -0,0 +1,314 @@
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
bool HasImageSampleFlag(const IR::Instruction& inst, uint32_t flag) {
|
||||
return (inst.memory.image_sample_flags & flag) != 0;
|
||||
}
|
||||
|
||||
ImageSampleLayout MakeImageSampleLayout(const IR::Instruction& inst, ImageViewKind view) {
|
||||
ImageSampleLayout layout;
|
||||
uint32_t cursor = 0;
|
||||
if (HasImageSampleFlag(inst, Decoder::ImageSampleFlagOffset)) {
|
||||
layout.offset = cursor++;
|
||||
}
|
||||
if (HasImageSampleFlag(inst, Decoder::ImageSampleFlagBias)) {
|
||||
layout.bias = cursor++;
|
||||
}
|
||||
if (HasImageSampleFlag(inst, Decoder::ImageSampleFlagCompare)) {
|
||||
layout.dref = cursor++;
|
||||
}
|
||||
if (HasImageSampleFlag(inst, Decoder::ImageSampleFlagDerivative)) {
|
||||
layout.grad_x = cursor;
|
||||
cursor += 2u;
|
||||
layout.grad_y = cursor;
|
||||
cursor += 2u;
|
||||
}
|
||||
layout.coord = cursor;
|
||||
cursor += ImageViewCoordinateComponents(view);
|
||||
if (HasImageSampleFlag(inst, Decoder::ImageSampleFlagLod)) {
|
||||
layout.lod = cursor++;
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
uint32_t EmitImageCoordF32(EmitterState* state, const IR::Instruction& inst,
|
||||
const ImageSampleLayout& layout, ImageViewKind view) {
|
||||
const auto x = EmitImageAddressFloatLoad(state, inst, inst.src[0], layout.coord);
|
||||
const auto y = inst.memory.image_address_components > layout.coord + 1u
|
||||
? EmitImageAddressFloatLoad(state, inst, inst.src[0], layout.coord + 1u)
|
||||
: EmitZeroF32(state);
|
||||
const auto coord = state->builder.AllocateId();
|
||||
if (ImageViewCoordinateComponents(view) == 3u) {
|
||||
const auto z = inst.memory.image_address_components > layout.coord + 2u
|
||||
? EmitImageAddressFloatLoad(state, inst, inst.src[0], layout.coord + 2u)
|
||||
: EmitZeroF32(state);
|
||||
state->builder.AddFunction({OpCompositeConstruct, state->vec3_float_type, coord, x, y, z});
|
||||
} else {
|
||||
state->builder.AddFunction({OpCompositeConstruct, state->vec2_float_type, coord, x, y});
|
||||
}
|
||||
return coord;
|
||||
}
|
||||
|
||||
uint32_t EmitImageLodF32(EmitterState* state, const IR::Instruction& inst,
|
||||
const ImageSampleLayout& layout) {
|
||||
if (HasImageSampleFlag(inst, Decoder::ImageSampleFlagLevelZero) ||
|
||||
layout.lod == NoImageComponent || inst.memory.image_address_components <= layout.lod) {
|
||||
return EmitZeroF32(state);
|
||||
}
|
||||
return EmitImageAddressFloatLoad(state, inst, inst.src[0], layout.lod);
|
||||
}
|
||||
|
||||
uint32_t EmitImageDrefF32(EmitterState* state, const IR::Instruction& inst,
|
||||
const ImageSampleLayout& layout) {
|
||||
if (layout.dref == NoImageComponent || inst.memory.image_address_components <= layout.dref) {
|
||||
return EmitZeroF32(state);
|
||||
}
|
||||
return EmitImageAddressFloatLoad(state, inst, inst.src[0], layout.dref);
|
||||
}
|
||||
|
||||
uint32_t EmitImageBiasF32(EmitterState* state, const IR::Instruction& inst,
|
||||
const ImageSampleLayout& layout) {
|
||||
if (layout.bias == NoImageComponent || inst.memory.image_address_components <= layout.bias) {
|
||||
return EmitZeroF32(state);
|
||||
}
|
||||
return EmitImageAddressFloatLoad(state, inst, inst.src[0], layout.bias);
|
||||
}
|
||||
|
||||
uint32_t EmitImageGradientF32(EmitterState* state, const IR::Instruction& inst,
|
||||
uint32_t first_component) {
|
||||
const auto x = inst.memory.image_address_components > first_component
|
||||
? EmitImageAddressFloatLoad(state, inst, inst.src[0], first_component)
|
||||
: EmitZeroF32(state);
|
||||
const auto y = inst.memory.image_address_components > first_component + 1u
|
||||
? EmitImageAddressFloatLoad(state, inst, inst.src[0], first_component + 1u)
|
||||
: EmitZeroF32(state);
|
||||
const auto grad = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpCompositeConstruct, state->vec2_float_type, grad, x, y});
|
||||
return grad;
|
||||
}
|
||||
|
||||
uint32_t EmitImagePackedOffset2I32(EmitterState* state, const IR::Instruction& inst,
|
||||
const ImageSampleLayout& layout) {
|
||||
if (layout.offset == NoImageComponent ||
|
||||
inst.memory.image_address_components <= layout.offset) {
|
||||
const auto zero = ConstantI32(state, 0);
|
||||
const auto ret = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpCompositeConstruct, state->vec2_int_type, ret, zero, zero});
|
||||
return ret;
|
||||
}
|
||||
|
||||
const auto packed_bits = EmitImageAddressValueLoad(state, inst, inst.src[0], layout.offset);
|
||||
const auto packed_i32 = state->builder.AllocateId();
|
||||
const auto offset_x = state->builder.AllocateId();
|
||||
const auto offset_y = state->builder.AllocateId();
|
||||
const auto offset = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitcast, state->int_type, packed_i32, packed_bits});
|
||||
state->builder.AddFunction({OpBitFieldSExtract, state->int_type, offset_x, packed_i32,
|
||||
ConstantI32(state, 0), ConstantI32(state, 6)});
|
||||
state->builder.AddFunction({OpBitFieldSExtract, state->int_type, offset_y, packed_i32,
|
||||
ConstantI32(state, 8), ConstantI32(state, 6)});
|
||||
state->builder.AddFunction(
|
||||
{OpCompositeConstruct, state->vec2_int_type, offset, offset_x, offset_y});
|
||||
return offset;
|
||||
}
|
||||
|
||||
uint32_t EmitImageOffsetCoordF32(EmitterState* state, const IR::Instruction& inst,
|
||||
const ImageSampleLayout& layout, uint32_t sampled_image,
|
||||
uint32_t coord, ImageViewKind view) {
|
||||
if (!HasImageSampleFlag(inst, Decoder::ImageSampleFlagOffset)) {
|
||||
return coord;
|
||||
}
|
||||
|
||||
const auto offset_i32 = EmitImagePackedOffset2I32(state, inst, layout);
|
||||
const auto offset_f32 = state->builder.AllocateId();
|
||||
const auto image = state->builder.AllocateId();
|
||||
const auto size_u32 = state->builder.AllocateId();
|
||||
const auto size_xy_u32 =
|
||||
ImageViewCoordinateComponents(view) == 3u ? state->builder.AllocateId() : size_u32;
|
||||
const auto size_f32 = state->builder.AllocateId();
|
||||
const auto normalized = state->builder.AllocateId();
|
||||
const auto adjusted = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpConvertSToF, state->vec2_float_type, offset_f32, offset_i32});
|
||||
state->builder.AddFunction(
|
||||
{OpImage, ImageViewImageType(*state, view, inst.memory.kind == IR::ResourceKind::ImageUint),
|
||||
image, sampled_image});
|
||||
state->builder.AddFunction({OpImageQuerySizeLod, ImageViewSizeType(*state, view), size_u32,
|
||||
image, ConstantU32(state, 0)});
|
||||
if (ImageViewCoordinateComponents(view) == 3u) {
|
||||
const auto width = state->builder.AllocateId();
|
||||
const auto height = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpCompositeExtract, state->uint_type, width, size_u32, 0});
|
||||
state->builder.AddFunction({OpCompositeExtract, state->uint_type, height, size_u32, 1});
|
||||
state->builder.AddFunction(
|
||||
{OpCompositeConstruct, state->vec2_uint_type, size_xy_u32, width, height});
|
||||
}
|
||||
state->builder.AddFunction({OpConvertUToF, state->vec2_float_type, size_f32, size_xy_u32});
|
||||
state->builder.AddFunction({OpFDiv, state->vec2_float_type, normalized, offset_f32, size_f32});
|
||||
if (ImageViewCoordinateComponents(view) == 2u) {
|
||||
state->builder.AddFunction({OpFAdd, state->vec2_float_type, adjusted, coord, normalized});
|
||||
return adjusted;
|
||||
}
|
||||
|
||||
const auto x = state->builder.AllocateId();
|
||||
const auto y = state->builder.AllocateId();
|
||||
const auto z = state->builder.AllocateId();
|
||||
const auto normalized_x = state->builder.AllocateId();
|
||||
const auto normalized_y = state->builder.AllocateId();
|
||||
const auto adjusted_x = state->builder.AllocateId();
|
||||
const auto adjusted_y = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpCompositeExtract, state->float_type, x, coord, 0});
|
||||
state->builder.AddFunction({OpCompositeExtract, state->float_type, y, coord, 1});
|
||||
state->builder.AddFunction({OpCompositeExtract, state->float_type, z, coord, 2});
|
||||
state->builder.AddFunction(
|
||||
{OpCompositeExtract, state->float_type, normalized_x, normalized, 0});
|
||||
state->builder.AddFunction(
|
||||
{OpCompositeExtract, state->float_type, normalized_y, normalized, 1});
|
||||
state->builder.AddFunction({OpFAdd, state->float_type, adjusted_x, x, normalized_x});
|
||||
state->builder.AddFunction({OpFAdd, state->float_type, adjusted_y, y, normalized_y});
|
||||
state->builder.AddFunction(
|
||||
{OpCompositeConstruct, state->vec3_float_type, adjusted, adjusted_x, adjusted_y, z});
|
||||
return adjusted;
|
||||
}
|
||||
|
||||
uint32_t EmitImageCoordU32(EmitterState* state, const IR::Instruction& inst, ImageViewKind view) {
|
||||
const auto x = EmitImageAddressValueLoad(state, inst, inst.src[1], 0);
|
||||
const auto y = inst.memory.image_address_components > 1u
|
||||
? EmitImageAddressValueLoad(state, inst, inst.src[1], 1)
|
||||
: ConstantU32(state, 0);
|
||||
const auto coord = state->builder.AllocateId();
|
||||
if (ImageViewCoordinateComponents(view) == 3u) {
|
||||
const auto z = inst.memory.image_address_components > 2u
|
||||
? EmitImageAddressValueLoad(state, inst, inst.src[1], 2)
|
||||
: ConstantU32(state, 0);
|
||||
state->builder.AddFunction({OpCompositeConstruct, state->vec3_uint_type, coord, x, y, z});
|
||||
} else {
|
||||
state->builder.AddFunction({OpCompositeConstruct, state->vec2_uint_type, coord, x, y});
|
||||
}
|
||||
return coord;
|
||||
}
|
||||
|
||||
uint32_t EmitImageLoadCoordU32(EmitterState* state, const IR::Instruction& inst,
|
||||
ImageViewKind view) {
|
||||
const auto x = EmitImageAddressValueLoad(state, inst, inst.src[0], 0);
|
||||
const auto y = inst.memory.image_address_components > 1u
|
||||
? EmitImageAddressValueLoad(state, inst, inst.src[0], 1)
|
||||
: ConstantU32(state, 0);
|
||||
const auto coord = state->builder.AllocateId();
|
||||
if (ImageViewCoordinateComponents(view) == 3u) {
|
||||
const auto z = inst.memory.image_address_components > 2u
|
||||
? EmitImageAddressValueLoad(state, inst, inst.src[0], 2)
|
||||
: ConstantU32(state, 0);
|
||||
state->builder.AddFunction({OpCompositeConstruct, state->vec3_uint_type, coord, x, y, z});
|
||||
} else {
|
||||
state->builder.AddFunction({OpCompositeConstruct, state->vec2_uint_type, coord, x, y});
|
||||
}
|
||||
return coord;
|
||||
}
|
||||
|
||||
uint32_t EmitImageMipLodU32(EmitterState* state, const IR::Instruction& inst,
|
||||
const IR::Operand& address, ImageViewKind view) {
|
||||
if (!inst.memory.image_has_mip || inst.memory.image_address_components == 0u) {
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
const auto lod_component = ImageViewCoordinateComponents(view);
|
||||
if (inst.memory.image_address_components <= lod_component) {
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
return EmitImageAddressValueLoad(state, inst, address, lod_component);
|
||||
}
|
||||
|
||||
uint32_t EmitImageQueryCoordF32(EmitterState* state, const IR::Instruction& inst,
|
||||
ImageViewKind view) {
|
||||
const auto x = EmitImageAddressFloatLoad(state, inst, inst.src[0], 0);
|
||||
const auto y = inst.memory.image_address_components > 1u
|
||||
? EmitImageAddressFloatLoad(state, inst, inst.src[0], 1)
|
||||
: EmitZeroF32(state);
|
||||
const auto coord = state->builder.AllocateId();
|
||||
if (ImageViewCoordinateComponents(view) == 3u) {
|
||||
const auto z = inst.memory.image_address_components > 2u
|
||||
? EmitImageAddressFloatLoad(state, inst, inst.src[0], 2)
|
||||
: EmitZeroF32(state);
|
||||
state->builder.AddFunction({OpCompositeConstruct, state->vec3_float_type, coord, x, y, z});
|
||||
} else {
|
||||
state->builder.AddFunction({OpCompositeConstruct, state->vec2_float_type, coord, x, y});
|
||||
}
|
||||
return coord;
|
||||
}
|
||||
|
||||
uint32_t DmaskComponentIndex(uint32_t dmask, uint32_t component) {
|
||||
uint32_t index = 0;
|
||||
for (uint32_t i = 0; i < component; i++) {
|
||||
index += (dmask >> i) & 1u;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
uint32_t EmitImageStoreComponentF32(EmitterState* state, const IR::Instruction& inst,
|
||||
uint32_t component) {
|
||||
const auto dmask = inst.memory.dmask != 0 ? inst.memory.dmask : 1u;
|
||||
if (((dmask >> component) & 1u) == 0) {
|
||||
return EmitZeroF32(state);
|
||||
}
|
||||
return EmitSequentialFloatLoad(state, inst.src[0], DmaskComponentIndex(dmask, component));
|
||||
}
|
||||
|
||||
uint32_t InverseStorageSwizzleComponent(uint32_t swizzle, uint32_t component) {
|
||||
const auto target = 4u + component;
|
||||
for (uint32_t source = 0; source < 4u; source++) {
|
||||
if (((swizzle >> (source * 3u)) & 0x7u) == target) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
uint32_t StorageImageSwizzle(const EmitterState& state, const IR::Instruction& inst) {
|
||||
if (state.program == nullptr || inst.memory.resource >= state.program->info.images.size()) {
|
||||
EXIT("storage image instruction has no specialized swizzle\n");
|
||||
}
|
||||
return state.program->info.images[inst.memory.resource].storage_swizzle;
|
||||
}
|
||||
|
||||
uint32_t EmitImageStoreTexelF32(EmitterState* state, const IR::Instruction& inst) {
|
||||
const auto swizzle = StorageImageSwizzle(*state, inst);
|
||||
const auto component_value = [&](uint32_t component) {
|
||||
const auto source = InverseStorageSwizzleComponent(swizzle, component);
|
||||
return source < 4u ? EmitImageStoreComponentF32(state, inst, source) : EmitZeroF32(state);
|
||||
};
|
||||
const auto x = component_value(0);
|
||||
const auto y = component_value(1);
|
||||
const auto z = component_value(2);
|
||||
const auto w = component_value(3);
|
||||
const auto texel = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpCompositeConstruct, state->vec4_float_type, texel, x, y, z, w});
|
||||
return texel;
|
||||
}
|
||||
|
||||
uint32_t EmitImageStoreComponentU32(EmitterState* state, const IR::Instruction& inst,
|
||||
uint32_t component) {
|
||||
const auto dmask = inst.memory.dmask != 0 ? inst.memory.dmask : 1u;
|
||||
if (((dmask >> component) & 1u) == 0) {
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
return EmitSequentialValueLoad(state, inst.src[0], DmaskComponentIndex(dmask, component));
|
||||
}
|
||||
|
||||
uint32_t EmitImageStoreTexelU32(EmitterState* state, const IR::Instruction& inst) {
|
||||
const auto swizzle = StorageImageSwizzle(*state, inst);
|
||||
const auto component_value = [&](uint32_t component) {
|
||||
const auto source = InverseStorageSwizzleComponent(swizzle, component);
|
||||
return source < 4u ? EmitImageStoreComponentU32(state, inst, source)
|
||||
: ConstantU32(state, 0);
|
||||
};
|
||||
const auto x = component_value(0);
|
||||
const auto y = component_value(1);
|
||||
const auto z = component_value(2);
|
||||
const auto w = component_value(3);
|
||||
const auto texel = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpCompositeConstruct, state->vec4_uint_type, texel, x, y, z, w});
|
||||
return texel;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
|
||||
@@ -0,0 +1,361 @@
|
||||
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
namespace {
|
||||
|
||||
uint32_t ConstantVec2I32(EmitterState* state, int32_t x, int32_t y) {
|
||||
const auto value = state->builder.AllocateId();
|
||||
state->builder.AddType({OpConstantComposite, state->vec2_int_type, value, ConstantI32(state, x),
|
||||
ConstantI32(state, y)});
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t ConstantImageGatherHorizontalOffsets(EmitterState* state) {
|
||||
const auto offset0 = ConstantVec2I32(state, -1, 0);
|
||||
const auto offset1 = ConstantVec2I32(state, 0, 0);
|
||||
const auto offset2 = ConstantVec2I32(state, 1, 0);
|
||||
const auto offset3 = ConstantVec2I32(state, 2, 0);
|
||||
const auto type = state->builder.AllocateId();
|
||||
state->builder.AddType({OpTypeArray, type, state->vec2_int_type, ConstantU32(state, 4)});
|
||||
const auto value = state->builder.AllocateId();
|
||||
state->builder.AddType({OpConstantComposite, type, value, offset0, offset1, offset2, offset3});
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t LoadStorageImageDescriptorAtIndex(EmitterState* state, uint32_t resource,
|
||||
uint32_t array_index, bool uint_image,
|
||||
ImageViewKind view) {
|
||||
uint32_t pointer_type =
|
||||
uint_image ? state->ptr_uniform_storage_image_uint : state->ptr_uniform_storage_image;
|
||||
uint32_t variable =
|
||||
uint_image ? state->storage_image_uint_variable : state->storage_image_variable;
|
||||
uint32_t image_type = uint_image ? state->storage_image_uint_type : state->storage_image_type;
|
||||
IR::DescriptorBindingKind kind = uint_image ? IR::DescriptorBindingKind::StorageUint2D
|
||||
: IR::DescriptorBindingKind::Storage2D;
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim2D: break;
|
||||
case ImageViewKind::Dim2DArray:
|
||||
pointer_type = uint_image ? state->ptr_uniform_storage_image_uint_2d_array
|
||||
: state->ptr_uniform_storage_image_2d_array;
|
||||
variable = uint_image ? state->storage_image_uint_2d_array_variable
|
||||
: state->storage_image_2d_array_variable;
|
||||
image_type = uint_image ? state->storage_image_uint_2d_array_type
|
||||
: state->storage_image_2d_array_type;
|
||||
kind = uint_image ? IR::DescriptorBindingKind::StorageUint2DArray
|
||||
: IR::DescriptorBindingKind::Storage2DArray;
|
||||
break;
|
||||
case ImageViewKind::Dim3D:
|
||||
pointer_type = uint_image ? state->ptr_uniform_storage_image_uint_3d
|
||||
: state->ptr_uniform_storage_image_3d;
|
||||
variable = uint_image ? state->storage_image_uint_3d_variable
|
||||
: state->storage_image_3d_variable;
|
||||
image_type =
|
||||
uint_image ? state->storage_image_uint_3d_type : state->storage_image_3d_type;
|
||||
kind = uint_image ? IR::DescriptorBindingKind::StorageUint3D
|
||||
: IR::DescriptorBindingKind::Storage3D;
|
||||
break;
|
||||
}
|
||||
const auto pointer =
|
||||
DescriptorElementPointer(state, pointer_type, variable, array_index, kind, resource,
|
||||
"storage image descriptor array was not emitted");
|
||||
const auto image = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpLoad, image_type, image, pointer});
|
||||
return image;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t EmitImageGetResinfoComponent(EmitterState* state, uint32_t image, uint32_t size,
|
||||
uint32_t component_index, ImageViewKind view) {
|
||||
switch (component_index) {
|
||||
case 0: {
|
||||
const auto width = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpCompositeExtract, state->uint_type, width, size, 0});
|
||||
return width;
|
||||
}
|
||||
case 1: {
|
||||
const auto height = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpCompositeExtract, state->uint_type, height, size, 1});
|
||||
return height;
|
||||
}
|
||||
case 2:
|
||||
if (ImageViewCoordinateComponents(view) == 3u) {
|
||||
const auto depth = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpCompositeExtract, state->uint_type, depth, size, 2});
|
||||
return depth;
|
||||
}
|
||||
return ConstantU32(state, 1);
|
||||
case 3: {
|
||||
const auto levels = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpImageQueryLevels, state->uint_type, levels, image});
|
||||
return levels;
|
||||
}
|
||||
default: return ConstantU32(state, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void EmitImageGetResinfo(EmitterState* state, const IR::Instruction& inst) {
|
||||
const auto view = SampledImageViewKind(*state, inst.memory, inst.pc);
|
||||
const auto image = LoadSampledImageDescriptor(state, inst.memory, inst.pc, view);
|
||||
const auto mip_level = EmitValueLoad(state, inst.src[0]);
|
||||
|
||||
const auto size = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpImageQuerySizeLod, ImageViewSizeType(*state, view), size, image, mip_level});
|
||||
|
||||
const auto dmask = inst.memory.dmask != 0 ? inst.memory.dmask : 1u;
|
||||
uint32_t dst_index = 0;
|
||||
for (uint32_t component_index = 0; component_index < 4u; component_index++) {
|
||||
if (((dmask >> component_index) & 1u) == 0) {
|
||||
continue;
|
||||
}
|
||||
const auto component =
|
||||
EmitImageGetResinfoComponent(state, image, size, component_index, view);
|
||||
EmitStoreU32(state, OffsetRegisterOperand(inst.dst, dst_index++), component);
|
||||
}
|
||||
}
|
||||
|
||||
void EmitImageGetLod(EmitterState* state, const IR::Instruction& inst) {
|
||||
const auto view = SampledImageViewKind(*state, inst.memory, inst.pc);
|
||||
const auto sampled_image = MakeSampledImage(state, inst.memory, inst.pc, view);
|
||||
|
||||
const auto lod = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpImageQueryLod, state->vec2_float_type, lod, sampled_image,
|
||||
EmitImageQueryCoordF32(state, inst, view)});
|
||||
|
||||
const auto dmask = inst.memory.dmask != 0 ? inst.memory.dmask : 1u;
|
||||
uint32_t dst_index = 0;
|
||||
for (uint32_t component_index = 0; component_index < 2u; component_index++) {
|
||||
if (((dmask >> component_index) & 1u) == 0) {
|
||||
continue;
|
||||
}
|
||||
const auto component = state->builder.AllocateId();
|
||||
const auto bits = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpCompositeExtract, state->float_type, component, lod, component_index});
|
||||
state->builder.AddFunction({OpBitcast, state->uint_type, bits, component});
|
||||
EmitStoreU32(state, OffsetRegisterOperand(inst.dst, dst_index++), bits);
|
||||
}
|
||||
}
|
||||
|
||||
void EmitImageLoad(EmitterState* state, const IR::Instruction& inst) {
|
||||
const auto view = SampledImageViewKind(*state, inst.memory, inst.pc);
|
||||
const auto image = LoadSampledImageDescriptor(state, inst.memory, inst.pc, view);
|
||||
const bool integer = inst.memory.kind == IR::ResourceKind::ImageUint;
|
||||
|
||||
const auto color = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpImageFetch, integer ? state->vec4_uint_type : state->vec4_float_type, color, image,
|
||||
EmitImageLoadCoordU32(state, inst, view), ImageOperandsLodMask,
|
||||
EmitImageMipLodU32(state, inst, inst.src[0], view)});
|
||||
|
||||
const auto dmask = inst.memory.dmask != 0 ? inst.memory.dmask : 1u;
|
||||
uint32_t dst_index = 0;
|
||||
for (uint32_t component_index = 0; component_index < 4u; component_index++) {
|
||||
if (((dmask >> component_index) & 1u) == 0) {
|
||||
continue;
|
||||
}
|
||||
const auto component = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpCompositeExtract,
|
||||
integer ? state->uint_type : state->float_type, component,
|
||||
color, component_index});
|
||||
const auto bits = integer ? component : state->builder.AllocateId();
|
||||
if (!integer) {
|
||||
state->builder.AddFunction({OpBitcast, state->uint_type, bits, component});
|
||||
}
|
||||
EmitStoreU32(state, OffsetRegisterOperand(inst.dst, dst_index++), bits);
|
||||
}
|
||||
}
|
||||
|
||||
void EmitImageStore(EmitterState* state, const IR::Instruction& inst) {
|
||||
const auto uint_image = inst.memory.kind == IR::ResourceKind::StorageImageUint;
|
||||
const auto view = StorageImageViewKind(*state, inst.memory, uint_image, inst.pc);
|
||||
const auto binding = ResourceForDescriptor(
|
||||
*state,
|
||||
uint_image
|
||||
? (view == ImageViewKind::Dim2DArray ? IR::DescriptorBindingKind::StorageUint2DArray
|
||||
: view == ImageViewKind::Dim3D ? IR::DescriptorBindingKind::StorageUint3D
|
||||
: IR::DescriptorBindingKind::StorageUint2D)
|
||||
: (view == ImageViewKind::Dim2DArray ? IR::DescriptorBindingKind::Storage2DArray
|
||||
: view == ImageViewKind::Dim3D ? IR::DescriptorBindingKind::Storage3D
|
||||
: IR::DescriptorBindingKind::Storage2D),
|
||||
inst.memory.resource);
|
||||
const auto image = LoadStorageImageDescriptorAtIndex(state, inst.memory.resource,
|
||||
binding.array_index, uint_image, view);
|
||||
|
||||
state->builder.AddFunction(
|
||||
{OpImageWrite, image, EmitImageCoordU32(state, inst, view),
|
||||
uint_image ? EmitImageStoreTexelU32(state, inst) : EmitImageStoreTexelF32(state, inst)});
|
||||
}
|
||||
|
||||
void EmitImageSampleResult(EmitterState* state, const IR::Instruction& inst, uint32_t sample,
|
||||
bool dref, bool integer) {
|
||||
if (dref) {
|
||||
const auto bits = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitcast, state->uint_type, bits, sample});
|
||||
for (uint32_t i = 0; i < inst.memory.data_dwords; i++) {
|
||||
EmitStoreU32(state, OffsetRegisterOperand(inst.dst, i), bits);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto dmask = inst.memory.dmask != 0 ? inst.memory.dmask : 1u;
|
||||
uint32_t dst_index = 0;
|
||||
for (uint32_t component_index = 0; component_index < 4u; component_index++) {
|
||||
if (((dmask >> component_index) & 1u) == 0) {
|
||||
continue;
|
||||
}
|
||||
const auto component = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpCompositeExtract,
|
||||
integer ? state->uint_type : state->float_type, component,
|
||||
sample, component_index});
|
||||
const auto bits = integer ? component : state->builder.AllocateId();
|
||||
if (!integer) {
|
||||
state->builder.AddFunction({OpBitcast, state->uint_type, bits, component});
|
||||
}
|
||||
EmitStoreU32(state, OffsetRegisterOperand(inst.dst, dst_index++), bits);
|
||||
}
|
||||
}
|
||||
|
||||
bool ImageSampleNeedsExplicitLod(const EmitterState& state, const IR::Instruction& inst) {
|
||||
// RDNA2 plain IMAGE_SAMPLE is derivative-based in pixel shaders. Do not translate it
|
||||
// to Lod(0): only _L/_LZ/_D variants supply explicit LOD/gradient information.
|
||||
// SPIR-V implicit-lod sampling is fragment-only, so non-pixel stages still need
|
||||
// an explicit operand even for the unsuffixed sample opcode.
|
||||
if (HasImageSampleFlag(inst, Decoder::ImageSampleFlagDerivative) ||
|
||||
HasImageSampleFlag(inst, Decoder::ImageSampleFlagLod) ||
|
||||
HasImageSampleFlag(inst, Decoder::ImageSampleFlagLevelZero)) {
|
||||
return true;
|
||||
}
|
||||
return state.stage != ShaderType::Pixel;
|
||||
}
|
||||
|
||||
void AddImageSampleOperands(EmitterState* state, const IR::Instruction& inst,
|
||||
const ImageSampleLayout& layout, bool explicit_lod,
|
||||
std::vector<uint32_t>* words) {
|
||||
if (words == nullptr) {
|
||||
return;
|
||||
}
|
||||
uint32_t mask = 0;
|
||||
std::vector<uint32_t> operands;
|
||||
|
||||
// Keep the SPIR-V operand class aligned with the RDNA2 opcode suffix. Grad/Lod
|
||||
// operands belong to explicit-lod instructions; bias belongs to implicit-lod
|
||||
// sampling because it modifies the hardware-computed LOD.
|
||||
if (HasImageSampleFlag(inst, Decoder::ImageSampleFlagDerivative)) {
|
||||
mask |= ImageOperandsGradMask;
|
||||
operands.push_back(EmitImageGradientF32(state, inst, layout.grad_x));
|
||||
operands.push_back(EmitImageGradientF32(state, inst, layout.grad_y));
|
||||
} else if (explicit_lod) {
|
||||
mask |= ImageOperandsLodMask;
|
||||
operands.push_back(EmitImageLodF32(state, inst, layout));
|
||||
} else if (HasImageSampleFlag(inst, Decoder::ImageSampleFlagBias)) {
|
||||
mask |= ImageOperandsBiasMask;
|
||||
operands.push_back(EmitImageBiasF32(state, inst, layout));
|
||||
}
|
||||
if (mask != 0) {
|
||||
words->push_back(mask);
|
||||
words->insert(words->end(), operands.begin(), operands.end());
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ImageSampleOpcode(const EmitterState& state, const IR::Instruction& inst) {
|
||||
const auto flags = inst.memory.image_sample_flags;
|
||||
const bool dref = (flags & Decoder::ImageSampleFlagCompare) != 0;
|
||||
const bool explicit_lod = ImageSampleNeedsExplicitLod(state, inst);
|
||||
if (explicit_lod) {
|
||||
return dref ? OpImageSampleDrefExplicitLod : OpImageSampleExplicitLod;
|
||||
}
|
||||
return dref ? OpImageSampleDrefImplicitLod : OpImageSampleImplicitLod;
|
||||
}
|
||||
|
||||
void EmitImageSample(EmitterState* state, const IR::Instruction& inst) {
|
||||
const auto view = SampledImageViewKind(*state, inst.memory, inst.pc);
|
||||
const auto sampled_image = MakeSampledImage(state, inst.memory, inst.pc, view);
|
||||
|
||||
const auto layout = MakeImageSampleLayout(inst, view);
|
||||
const auto base_coord = EmitImageCoordF32(state, inst, layout, view);
|
||||
const auto sample = state->builder.AllocateId();
|
||||
const auto dref = HasImageSampleFlag(inst, Decoder::ImageSampleFlagCompare);
|
||||
const bool integer = inst.memory.kind == IR::ResourceKind::ImageUint;
|
||||
const auto result_type = dref ? state->float_type
|
||||
: integer ? state->vec4_uint_type
|
||||
: state->vec4_float_type;
|
||||
const auto explicit_lod = ImageSampleNeedsExplicitLod(*state, inst);
|
||||
const auto opcode = ImageSampleOpcode(*state, inst);
|
||||
const auto coord =
|
||||
EmitImageOffsetCoordF32(state, inst, layout, sampled_image, base_coord, view);
|
||||
|
||||
std::vector<uint32_t> words = {opcode, result_type, sample, sampled_image, coord};
|
||||
if (dref) {
|
||||
words.push_back(EmitImageDrefF32(state, inst, layout));
|
||||
}
|
||||
AddImageSampleOperands(state, inst, layout, explicit_lod, &words);
|
||||
state->builder.AddFunction(words);
|
||||
EmitImageSampleResult(state, inst, sample, dref, integer);
|
||||
}
|
||||
|
||||
uint32_t ImageGatherComponent(uint32_t dmask) {
|
||||
switch (dmask) {
|
||||
case 0x2u: return 1;
|
||||
case 0x4u: return 2;
|
||||
case 0x8u: return 3;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void AddImageGatherOperands(EmitterState* state, const IR::Instruction& inst,
|
||||
const ImageSampleLayout& layout, std::vector<uint32_t>* words) {
|
||||
if (words == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (HasImageSampleFlag(inst, Decoder::ImageSampleFlagGatherHorizontal)) {
|
||||
words->push_back(ImageOperandsConstOffsetsMask);
|
||||
words->push_back(ConstantImageGatherHorizontalOffsets(state));
|
||||
return;
|
||||
}
|
||||
if (HasImageSampleFlag(inst, Decoder::ImageSampleFlagOffset)) {
|
||||
words->push_back(ImageOperandsOffsetMask);
|
||||
words->push_back(EmitImagePackedOffset2I32(state, inst, layout));
|
||||
}
|
||||
}
|
||||
|
||||
void EmitImageGather4(EmitterState* state, const IR::Instruction& inst) {
|
||||
const auto view = SampledImageViewKind(*state, inst.memory, inst.pc);
|
||||
const auto sampled_image = MakeSampledImage(state, inst.memory, inst.pc, view);
|
||||
|
||||
const auto layout = MakeImageSampleLayout(inst, view);
|
||||
const auto coord = EmitImageCoordF32(state, inst, layout, view);
|
||||
const auto texels = state->builder.AllocateId();
|
||||
const auto dref = HasImageSampleFlag(inst, Decoder::ImageSampleFlagCompare);
|
||||
const bool integer = inst.memory.kind == IR::ResourceKind::ImageUint;
|
||||
const auto result_type = integer ? state->vec4_uint_type : state->vec4_float_type;
|
||||
std::vector<uint32_t> words =
|
||||
dref ? std::vector<uint32_t> {OpImageDrefGather,
|
||||
state->vec4_float_type,
|
||||
texels,
|
||||
sampled_image,
|
||||
coord,
|
||||
EmitImageDrefF32(state, inst, layout)}
|
||||
: std::vector<uint32_t> {
|
||||
OpImageGather, result_type,
|
||||
texels, sampled_image,
|
||||
coord, ConstantU32(state, ImageGatherComponent(inst.memory.dmask))};
|
||||
AddImageGatherOperands(state, inst, layout, &words);
|
||||
state->builder.AddFunction(words);
|
||||
|
||||
for (uint32_t i = 0; i < inst.memory.data_dwords; i++) {
|
||||
const auto component = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpCompositeExtract,
|
||||
integer ? state->uint_type : state->float_type, component,
|
||||
texels, i});
|
||||
const auto bits = integer ? component : state->builder.AllocateId();
|
||||
if (!integer) {
|
||||
state->builder.AddFunction({OpBitcast, state->uint_type, bits, component});
|
||||
}
|
||||
EmitStoreU32(state, OffsetRegisterOperand(inst.dst, i), bits);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,929 @@
|
||||
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
const IR::DescriptorBinding* DescriptorBinding(const EmitterState& state,
|
||||
IR::DescriptorBindingKind kind) {
|
||||
return IR::FindBinding(state.program->bindings, kind);
|
||||
}
|
||||
|
||||
uint32_t DescriptorCount(const EmitterState& state, IR::DescriptorBindingKind kind) {
|
||||
const auto* binding = DescriptorBinding(state, kind);
|
||||
return binding != nullptr ? static_cast<uint32_t>(binding->resources.size()) : 0;
|
||||
}
|
||||
|
||||
uint32_t ConstantU32(EmitterState* state, uint32_t value) {
|
||||
if (auto it = state->constants.find(value); it != state->constants.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
const auto id = state->builder.AllocateId();
|
||||
state->constants.emplace(value, id);
|
||||
state->builder.AddType({OpConstant, state->uint_type, id, value});
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t ConstantI32(EmitterState* state, int32_t value) {
|
||||
const auto bits = static_cast<uint32_t>(value);
|
||||
if (auto it = state->signed_constants.find(bits); it != state->signed_constants.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
const auto id = state->builder.AllocateId();
|
||||
state->signed_constants.emplace(bits, id);
|
||||
state->builder.AddType({OpConstant, state->int_type, id, bits});
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t ConstantF32(EmitterState* state, uint32_t bits) {
|
||||
if (auto it = state->float_constants.find(bits); it != state->float_constants.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
const auto id = state->builder.AllocateId();
|
||||
state->float_constants.emplace(bits, id);
|
||||
state->builder.AddType({OpConstant, state->float_type, id, bits});
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t FloatBits(float value) {
|
||||
uint32_t bits = 0;
|
||||
std::memcpy(&bits, &value, sizeof(bits));
|
||||
return bits;
|
||||
}
|
||||
|
||||
uint32_t ConstantF32Value(EmitterState* state, float value) {
|
||||
return ConstantF32(state, FloatBits(value));
|
||||
}
|
||||
|
||||
VertexInputScalarKind VertexParameterScalarKind(const EmitterState& state, uint32_t location) {
|
||||
if (state.stage != ShaderType::Vertex || state.vertex_input_info == nullptr ||
|
||||
location >= ShaderVertexInputInfo::RES_MAX ||
|
||||
location >= static_cast<uint32_t>(state.vertex_input_info->resources_num)) {
|
||||
return VertexInputScalarKind::Float;
|
||||
}
|
||||
|
||||
switch (state.vertex_input_info->resources[location].Format()) {
|
||||
case 5:
|
||||
case 11:
|
||||
case 18:
|
||||
case 20:
|
||||
case 27:
|
||||
case 60:
|
||||
case 62:
|
||||
case 69:
|
||||
case 72:
|
||||
case 75: return VertexInputScalarKind::Uint;
|
||||
case 6:
|
||||
case 12:
|
||||
case 19:
|
||||
case 21:
|
||||
case 28:
|
||||
case 61:
|
||||
case 63:
|
||||
case 70:
|
||||
case 73:
|
||||
case 76: return VertexInputScalarKind::Sint;
|
||||
default: return VertexInputScalarKind::Float;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t VertexParameterComponentCount(const EmitterState& state, const InputBinding& input) {
|
||||
uint32_t count = input.component_count;
|
||||
if (state.stage == ShaderType::Vertex && state.vertex_input_info != nullptr &&
|
||||
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>(
|
||||
state.vertex_input_info->resources_dst[input.location].registers_num);
|
||||
}
|
||||
return std::clamp(count, 1u, 4u);
|
||||
}
|
||||
|
||||
uint32_t VertexParameterScalarType(const EmitterState& state, VertexInputScalarKind kind) {
|
||||
switch (kind) {
|
||||
case VertexInputScalarKind::Sint: return state.int_type;
|
||||
case VertexInputScalarKind::Uint: return state.uint_type;
|
||||
case VertexInputScalarKind::Float:
|
||||
default: return state.float_type;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t VertexParameterScalarPointerType(const EmitterState& state, VertexInputScalarKind kind) {
|
||||
switch (kind) {
|
||||
case VertexInputScalarKind::Sint: return state.ptr_input_int;
|
||||
case VertexInputScalarKind::Uint: return state.ptr_input_uint;
|
||||
case VertexInputScalarKind::Float:
|
||||
default: return state.ptr_input_float;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t VertexParameterVectorOrScalarType(const EmitterState& state, VertexInputScalarKind kind,
|
||||
uint32_t components) {
|
||||
switch (kind) {
|
||||
case VertexInputScalarKind::Sint:
|
||||
switch (components) {
|
||||
case 1: return state.int_type;
|
||||
case 2: return state.vec2_int_type;
|
||||
case 3: return state.vec3_int_type;
|
||||
default: return state.vec4_int_type;
|
||||
}
|
||||
case VertexInputScalarKind::Uint:
|
||||
switch (components) {
|
||||
case 1: return state.uint_type;
|
||||
case 2: return state.vec2_uint_type;
|
||||
case 3: return state.vec3_uint_type;
|
||||
default: return state.vec4_uint_type;
|
||||
}
|
||||
case VertexInputScalarKind::Float:
|
||||
default:
|
||||
switch (components) {
|
||||
case 1: return state.float_type;
|
||||
case 2: return state.vec2_float_type;
|
||||
case 3: return state.vec3_float_type;
|
||||
default: return state.vec4_float_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t VertexParameterInputPointerType(const EmitterState& state, VertexInputScalarKind kind,
|
||||
uint32_t components) {
|
||||
switch (kind) {
|
||||
case VertexInputScalarKind::Sint:
|
||||
switch (components) {
|
||||
case 1: return state.ptr_input_int;
|
||||
case 2: return state.ptr_input_vec2_int;
|
||||
case 3: return state.ptr_input_vec3_int;
|
||||
default: return state.ptr_input_vec4_int;
|
||||
}
|
||||
case VertexInputScalarKind::Uint:
|
||||
switch (components) {
|
||||
case 1: return state.ptr_input_uint;
|
||||
case 2: return state.ptr_input_vec2_uint;
|
||||
case 3: return state.ptr_input_vec3_uint;
|
||||
default: return state.ptr_input_vec4_uint;
|
||||
}
|
||||
case VertexInputScalarKind::Float:
|
||||
default:
|
||||
switch (components) {
|
||||
case 1: return state.ptr_input_float;
|
||||
case 2: return state.ptr_input_vec2_float;
|
||||
case 3: return state.ptr_input_vec3_float;
|
||||
default: return state.ptr_input_vec4_float;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AllocateInputVariables(EmitterState* state) {
|
||||
for (auto& binding: state->inputs) {
|
||||
binding.variable_id = state->builder.AllocateId();
|
||||
state->interface_variables.push_back(binding.variable_id);
|
||||
}
|
||||
if (state->needs_subgroup_local_invocation_id) {
|
||||
state->subgroup_local_invocation_id_variable = state->builder.AllocateId();
|
||||
state->interface_variables.push_back(state->subgroup_local_invocation_id_variable);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t AllocateInterfaceVariable(EmitterState* state) {
|
||||
const auto variable = state->builder.AllocateId();
|
||||
state->interface_variables.push_back(variable);
|
||||
return variable;
|
||||
}
|
||||
|
||||
static uint32_t AllocateSharedOutputVariable(EmitterState* state, uint32_t& variable) {
|
||||
if (variable == 0) {
|
||||
variable = AllocateInterfaceVariable(state);
|
||||
}
|
||||
return variable;
|
||||
}
|
||||
|
||||
void AllocateOutputVariables(EmitterState* state) {
|
||||
for (auto& binding: state->outputs) {
|
||||
switch (binding.kind) {
|
||||
case IR::StageOutputKind::Position:
|
||||
binding.variable_id =
|
||||
AllocateSharedOutputVariable(state, state->per_vertex_variable);
|
||||
break;
|
||||
case IR::StageOutputKind::Depth:
|
||||
binding.variable_id = AllocateSharedOutputVariable(state, state->depth_variable);
|
||||
break;
|
||||
case IR::StageOutputKind::SampleMask:
|
||||
binding.variable_id =
|
||||
AllocateSharedOutputVariable(state, state->sample_mask_variable);
|
||||
break;
|
||||
case IR::StageOutputKind::Parameter:
|
||||
case IR::StageOutputKind::Mrt:
|
||||
binding.variable_id = AllocateInterfaceVariable(state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t BuiltInForInput(IR::StageInputKind kind) {
|
||||
switch (kind) {
|
||||
case IR::StageInputKind::VertexIndex: return BuiltInVertexIndex;
|
||||
case IR::StageInputKind::InstanceIndex: return BuiltInInstanceIndex;
|
||||
case IR::StageInputKind::FragCoord: return BuiltInFragCoord;
|
||||
case IR::StageInputKind::FrontFacing: return BuiltInFrontFacing;
|
||||
case IR::StageInputKind::WorkgroupId: return BuiltInWorkgroupId;
|
||||
case IR::StageInputKind::LocalInvocationId: return BuiltInLocalInvocationId;
|
||||
case IR::StageInputKind::LocalInvocationIndex: return BuiltInLocalInvocationIndex;
|
||||
case IR::StageInputKind::GlobalInvocationId: return BuiltInGlobalInvocationId;
|
||||
default: return UINT32_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
void AddInputAnnotationsAndNames(EmitterState* state) {
|
||||
if (state->subgroup_local_invocation_id_variable != 0) {
|
||||
state->builder.AddName(state->subgroup_local_invocation_id_variable,
|
||||
"gl_SubgroupInvocationID");
|
||||
state->builder.AddAnnotation({OpDecorate, state->subgroup_local_invocation_id_variable,
|
||||
DecorationBuiltIn, BuiltInSubgroupLocalInvocationId});
|
||||
if (state->stage == ShaderType::Pixel) {
|
||||
state->builder.AddAnnotation(
|
||||
{OpDecorate, state->subgroup_local_invocation_id_variable, DecorationFlat});
|
||||
}
|
||||
}
|
||||
for (const auto& input: state->inputs) {
|
||||
state->builder.AddName(input.variable_id, input.debug_name.c_str());
|
||||
if (input.kind == IR::StageInputKind::Parameter) {
|
||||
const auto flat = PixelParameterIsFlat(*state, input.location);
|
||||
if (flat) {
|
||||
state->builder.AddAnnotation({OpDecorate, input.variable_id, DecorationFlat});
|
||||
}
|
||||
if (state->stage == ShaderType::Pixel && state->pixel_input_info != nullptr &&
|
||||
state->pixel_input_info->ps_no_perspective && !flat) {
|
||||
state->builder.AddAnnotation(
|
||||
{OpDecorate, input.variable_id, DecorationNoPerspective});
|
||||
}
|
||||
const auto location = PixelParameterLocation(*state, input.location);
|
||||
state->builder.AddAnnotation(
|
||||
{OpDecorate, input.variable_id, DecorationLocation, location});
|
||||
continue;
|
||||
}
|
||||
const auto builtin = BuiltInForInput(input.kind);
|
||||
if (builtin != UINT32_MAX) {
|
||||
state->builder.AddAnnotation(
|
||||
{OpDecorate, input.variable_id, DecorationBuiltIn, builtin});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddOutputAnnotationsAndNames(EmitterState* state) {
|
||||
if (state->per_vertex_variable != 0) {
|
||||
state->builder.AddName(state->per_vertex_type, "gl_PerVertex");
|
||||
state->builder.AddName(state->per_vertex_variable, "outPerVertex");
|
||||
state->builder.AddAnnotation(
|
||||
{OpMemberDecorate, state->per_vertex_type, 0, DecorationBuiltIn, BuiltInPosition});
|
||||
state->builder.AddAnnotation({OpDecorate, state->per_vertex_type, DecorationBlock});
|
||||
}
|
||||
if (state->depth_variable != 0) {
|
||||
state->builder.AddName(state->depth_variable, "gl_FragDepth");
|
||||
state->builder.AddAnnotation(
|
||||
{OpDecorate, state->depth_variable, DecorationBuiltIn, BuiltInFragDepth});
|
||||
}
|
||||
if (state->sample_mask_variable != 0) {
|
||||
state->builder.AddName(state->sample_mask_variable, "gl_SampleMask");
|
||||
state->builder.AddAnnotation(
|
||||
{OpDecorate, state->sample_mask_variable, DecorationBuiltIn, BuiltInSampleMask});
|
||||
}
|
||||
for (const auto& binding: state->outputs) {
|
||||
if (binding.kind == IR::StageOutputKind::Parameter ||
|
||||
binding.kind == IR::StageOutputKind::Mrt) {
|
||||
state->builder.AddName(binding.variable_id, binding.debug_name.c_str());
|
||||
state->builder.AddAnnotation(
|
||||
{OpDecorate, binding.variable_id, DecorationLocation, binding.location});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DecorateDescriptor(EmitterState* state, uint32_t variable, const char* name, uint32_t set,
|
||||
uint32_t binding) {
|
||||
if (variable == 0) {
|
||||
return;
|
||||
}
|
||||
state->builder.AddName(variable, name);
|
||||
state->builder.AddAnnotation({OpDecorate, variable, DecorationDescriptorSet, set});
|
||||
state->builder.AddAnnotation({OpDecorate, variable, DecorationBinding, binding});
|
||||
}
|
||||
|
||||
void AddDescriptorAnnotationsAndNames(EmitterState* state) {
|
||||
const auto set = state->program->bindings.descriptor_set;
|
||||
auto Decorate = [&](uint32_t variable, const char* name, IR::DescriptorBindingKind kind) {
|
||||
const auto* binding = DescriptorBinding(*state, kind);
|
||||
if (binding != nullptr) {
|
||||
DecorateDescriptor(state, variable, name, set, binding->binding);
|
||||
}
|
||||
};
|
||||
if (state->storage_buffer_variable != 0) {
|
||||
Decorate(state->storage_buffer_variable, "buffers", IR::DescriptorBindingKind::Buffers);
|
||||
}
|
||||
if (state->address_memory_variable != 0) {
|
||||
Decorate(state->address_memory_variable, "address_memory",
|
||||
IR::DescriptorBindingKind::AddressMemory);
|
||||
}
|
||||
constexpr const char* SampledNames[] = {
|
||||
"sampled_2d", "sampled_2d_array", "sampled_3d",
|
||||
"sampled_uint_2d", "sampled_uint_2d_array", "sampled_uint_3d"};
|
||||
for (uint32_t i = 0; i < state->sampled_images.size(); i++) {
|
||||
const auto view = static_cast<ImageViewKind>(i % 3u);
|
||||
Decorate(state->sampled_images[i].variable, SampledNames[i],
|
||||
SampledBindingKind(i >= 3u, view));
|
||||
}
|
||||
if (state->storage_image_variable != 0) {
|
||||
Decorate(state->storage_image_variable, "textures2D_L",
|
||||
IR::DescriptorBindingKind::Storage2D);
|
||||
}
|
||||
if (state->storage_image_uint_variable != 0) {
|
||||
Decorate(state->storage_image_uint_variable, "textures2D_L_U",
|
||||
IR::DescriptorBindingKind::StorageUint2D);
|
||||
}
|
||||
if (state->storage_image_2d_array_variable != 0) {
|
||||
Decorate(state->storage_image_2d_array_variable, "textures2D_L_A",
|
||||
IR::DescriptorBindingKind::Storage2DArray);
|
||||
}
|
||||
if (state->storage_image_uint_2d_array_variable != 0) {
|
||||
Decorate(state->storage_image_uint_2d_array_variable, "textures2D_L_U_A",
|
||||
IR::DescriptorBindingKind::StorageUint2DArray);
|
||||
}
|
||||
if (state->storage_image_3d_variable != 0) {
|
||||
Decorate(state->storage_image_3d_variable, "textures2D_L_3D",
|
||||
IR::DescriptorBindingKind::Storage3D);
|
||||
}
|
||||
if (state->storage_image_uint_3d_variable != 0) {
|
||||
Decorate(state->storage_image_uint_3d_variable, "textures2D_L_U_3D",
|
||||
IR::DescriptorBindingKind::StorageUint3D);
|
||||
}
|
||||
if (state->sampler_variable != 0) {
|
||||
Decorate(state->sampler_variable, "samplers", IR::DescriptorBindingKind::Samplers);
|
||||
}
|
||||
if (state->gds_variable != 0) {
|
||||
Decorate(state->gds_variable, "gds", IR::DescriptorBindingKind::Gds);
|
||||
}
|
||||
if (state->flattened_srt_variable != 0) {
|
||||
Decorate(state->flattened_srt_variable, "flattened_srt",
|
||||
IR::DescriptorBindingKind::FlattenedSrt);
|
||||
}
|
||||
}
|
||||
|
||||
void AddVsharpAnnotationsAndNames(EmitterState* state) {
|
||||
const auto& bind = state->program->bindings;
|
||||
if (state->push_constant_variable != 0) {
|
||||
state->builder.AddName(state->push_constant_block_type, "BufferResource");
|
||||
state->builder.AddName(state->push_constant_variable, "vsharp");
|
||||
state->builder.AddAnnotation(
|
||||
{OpDecorate, state->push_constant_u32x4_array_type, DecorationArrayStride, 4});
|
||||
state->builder.AddAnnotation(
|
||||
{OpDecorate, state->push_constant_rows_array_type, DecorationArrayStride, 16});
|
||||
state->builder.AddAnnotation({OpMemberDecorate, state->push_constant_block_type, 0,
|
||||
DecorationOffset, bind.push_constant_offset});
|
||||
state->builder.AddAnnotation(
|
||||
{OpDecorate, state->push_constant_block_type, DecorationBlock});
|
||||
}
|
||||
if (state->vsharp_storage_variable != 0) {
|
||||
const auto* storage = DescriptorBinding(*state, IR::DescriptorBindingKind::UserData);
|
||||
if (storage != nullptr) {
|
||||
DecorateDescriptor(state, state->vsharp_storage_variable, "user_data",
|
||||
bind.descriptor_set, storage->binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EmitHeaderAndTypes(EmitterState* state) {
|
||||
state->void_type = state->builder.AllocateId();
|
||||
state->bool_type = state->builder.AllocateId();
|
||||
state->uint_type = state->builder.AllocateId();
|
||||
state->uint_pair_type = state->builder.AllocateId();
|
||||
state->int_type = state->builder.AllocateId();
|
||||
state->int_pair_type = state->builder.AllocateId();
|
||||
state->float_type = state->builder.AllocateId();
|
||||
state->vec2_uint_type = state->builder.AllocateId();
|
||||
state->vec3_uint_type = state->builder.AllocateId();
|
||||
state->vec4_uint_type = state->builder.AllocateId();
|
||||
state->vec2_int_type = state->builder.AllocateId();
|
||||
state->vec3_int_type = state->builder.AllocateId();
|
||||
state->vec4_int_type = state->builder.AllocateId();
|
||||
state->vec2_float_type = state->builder.AllocateId();
|
||||
state->vec3_float_type = state->builder.AllocateId();
|
||||
state->vec4_float_type = state->builder.AllocateId();
|
||||
state->ptr_func_uint = state->builder.AllocateId();
|
||||
state->ptr_input_float = state->builder.AllocateId();
|
||||
state->ptr_input_bool = state->builder.AllocateId();
|
||||
state->ptr_input_int = state->builder.AllocateId();
|
||||
state->ptr_input_uint = state->builder.AllocateId();
|
||||
state->ptr_input_vec2_float = state->builder.AllocateId();
|
||||
state->ptr_input_vec3_float = state->builder.AllocateId();
|
||||
state->ptr_input_vec2_int = state->builder.AllocateId();
|
||||
state->ptr_input_vec3_int = state->builder.AllocateId();
|
||||
state->ptr_input_vec4_int = state->builder.AllocateId();
|
||||
state->ptr_input_vec2_uint = state->builder.AllocateId();
|
||||
state->ptr_input_vec3_uint = state->builder.AllocateId();
|
||||
state->ptr_input_vec4_uint = state->builder.AllocateId();
|
||||
state->ptr_input_vec4_float = state->builder.AllocateId();
|
||||
state->sample_mask_array_type = state->builder.AllocateId();
|
||||
state->ptr_output_int = state->builder.AllocateId();
|
||||
state->ptr_output_sample_mask_array = state->builder.AllocateId();
|
||||
state->ptr_output_float = state->builder.AllocateId();
|
||||
state->ptr_output_vec4_float = state->builder.AllocateId();
|
||||
state->per_vertex_type = state->builder.AllocateId();
|
||||
state->ptr_output_per_vertex = state->builder.AllocateId();
|
||||
state->storage_runtime_array_type = state->builder.AllocateId();
|
||||
state->storage_buffer_type = state->builder.AllocateId();
|
||||
state->ptr_storage_buffer = state->builder.AllocateId();
|
||||
state->ptr_storage_buffer_uint = state->builder.AllocateId();
|
||||
state->storage_buffer_array_type = state->builder.AllocateId();
|
||||
state->ptr_storage_buffer_array = state->builder.AllocateId();
|
||||
if (state->address_memory_variable != 0) {
|
||||
state->address_memory_array_type = state->builder.AllocateId();
|
||||
state->ptr_address_memory_array = state->builder.AllocateId();
|
||||
}
|
||||
if (state->push_constant_variable != 0) {
|
||||
state->push_constant_u32x4_array_type = state->builder.AllocateId();
|
||||
state->push_constant_rows_array_type = state->builder.AllocateId();
|
||||
state->push_constant_block_type = state->builder.AllocateId();
|
||||
state->ptr_push_constant_block = state->builder.AllocateId();
|
||||
state->ptr_push_constant_uint = state->builder.AllocateId();
|
||||
}
|
||||
state->lds_array_type = state->builder.AllocateId();
|
||||
state->ptr_workgroup_array = state->builder.AllocateId();
|
||||
state->ptr_workgroup_uint = state->builder.AllocateId();
|
||||
state->lds_variable = state->builder.AllocateId();
|
||||
for (auto& image: state->sampled_images) {
|
||||
image.image_type = state->builder.AllocateId();
|
||||
image.sampled_image_type = state->builder.AllocateId();
|
||||
image.pointer_type = state->builder.AllocateId();
|
||||
image.array_type = state->builder.AllocateId();
|
||||
image.array_pointer_type = state->builder.AllocateId();
|
||||
}
|
||||
state->sampler_type = state->builder.AllocateId();
|
||||
state->sampler_array_type = state->builder.AllocateId();
|
||||
state->ptr_uniform_sampler = state->builder.AllocateId();
|
||||
state->ptr_uniform_sampler_array = state->builder.AllocateId();
|
||||
state->storage_image_type = state->builder.AllocateId();
|
||||
state->ptr_uniform_storage_image = state->builder.AllocateId();
|
||||
state->storage_image_array_type = state->builder.AllocateId();
|
||||
state->ptr_uniform_storage_image_array = state->builder.AllocateId();
|
||||
state->storage_image_2d_array_type = state->builder.AllocateId();
|
||||
state->ptr_uniform_storage_image_2d_array = state->builder.AllocateId();
|
||||
state->storage_image_2d_array_array_type = state->builder.AllocateId();
|
||||
state->ptr_uniform_storage_image_2d_array_array = state->builder.AllocateId();
|
||||
state->storage_image_3d_type = state->builder.AllocateId();
|
||||
state->ptr_uniform_storage_image_3d = state->builder.AllocateId();
|
||||
state->storage_image_3d_array_type = state->builder.AllocateId();
|
||||
state->ptr_uniform_storage_image_3d_array = state->builder.AllocateId();
|
||||
state->storage_image_uint_type = state->builder.AllocateId();
|
||||
state->ptr_uniform_storage_image_uint = state->builder.AllocateId();
|
||||
state->storage_image_uint_array_type = state->builder.AllocateId();
|
||||
state->ptr_uniform_storage_image_uint_array = state->builder.AllocateId();
|
||||
state->storage_image_uint_2d_array_type = state->builder.AllocateId();
|
||||
state->ptr_uniform_storage_image_uint_2d_array = state->builder.AllocateId();
|
||||
state->storage_image_uint_2d_array_array_type = state->builder.AllocateId();
|
||||
state->ptr_uniform_storage_image_uint_2d_array_array = state->builder.AllocateId();
|
||||
state->storage_image_uint_3d_type = state->builder.AllocateId();
|
||||
state->ptr_uniform_storage_image_uint_3d = state->builder.AllocateId();
|
||||
state->storage_image_uint_3d_array_type = state->builder.AllocateId();
|
||||
state->ptr_uniform_storage_image_uint_3d_array = state->builder.AllocateId();
|
||||
state->ptr_image_uint = state->builder.AllocateId();
|
||||
state->func_type = state->builder.AllocateId();
|
||||
state->main_func = state->builder.AllocateId();
|
||||
state->entry_label = state->builder.AllocateId();
|
||||
state->glsl_std450 = state->builder.AllocateId();
|
||||
|
||||
state->builder.AddCapability({CapabilityShader});
|
||||
state->builder.AddCapability({CapabilityImageQuery});
|
||||
if (state->needs_image_gather_extended) {
|
||||
state->builder.AddCapability({CapabilityImageGatherExtended});
|
||||
}
|
||||
if (state->storage_image_variable != 0 || state->storage_image_2d_array_variable != 0 ||
|
||||
state->storage_image_3d_variable != 0) {
|
||||
state->builder.AddCapability({CapabilityStorageImageReadWithoutFormat});
|
||||
state->builder.AddCapability({CapabilityStorageImageWriteWithoutFormat});
|
||||
}
|
||||
if (state->needs_subgroup_ballot || state->needs_subgroup_shuffle ||
|
||||
state->needs_subgroup_local_invocation_id) {
|
||||
state->builder.AddCapability({CapabilityGroupNonUniform});
|
||||
}
|
||||
if (state->needs_subgroup_ballot) {
|
||||
state->builder.AddCapability({CapabilityGroupNonUniformBallot});
|
||||
}
|
||||
if (state->needs_subgroup_shuffle) {
|
||||
state->builder.AddCapability({CapabilityGroupNonUniformShuffle});
|
||||
}
|
||||
if (state->needs_compute_derivatives && state->stage == ShaderType::Compute) {
|
||||
state->builder.AddCapability({CapabilityComputeDerivativeGroupQuadsKHR});
|
||||
state->builder.AddExtension("SPV_KHR_compute_shader_derivatives");
|
||||
}
|
||||
state->builder.AddExtInstImport(state->glsl_std450, "GLSL.std.450");
|
||||
state->builder.AddMemoryModel({AddressingModelLogical, MemoryModelGLSL450});
|
||||
state->builder.AddEntryPoint(ExecutionModelForStage(state->stage), state->main_func, "main",
|
||||
state->interface_variables);
|
||||
if (state->stage == ShaderType::Compute) {
|
||||
uint32_t local_x = state->needs_compute_derivatives ? 2u : 1u;
|
||||
uint32_t local_y = state->needs_compute_derivatives ? 2u : 1u;
|
||||
uint32_t local_z = 1u;
|
||||
if (state->compute_input_info != nullptr) {
|
||||
const auto* cs = state->compute_input_info;
|
||||
local_x = cs->threads_num[0] != 0u ? cs->threads_num[0] : local_x;
|
||||
local_y = cs->threads_num[1] != 0u ? cs->threads_num[1] : local_y;
|
||||
local_z = cs->threads_num[2] != 0u ? cs->threads_num[2] : local_z;
|
||||
}
|
||||
state->builder.AddExecutionMode(
|
||||
{state->main_func, ExecutionModeLocalSize, local_x, local_y, local_z});
|
||||
}
|
||||
if (state->stage == ShaderType::Pixel) {
|
||||
state->builder.AddExecutionMode({state->main_func, ExecutionModeOriginUpperLeft});
|
||||
if (state->depth_variable != 0) {
|
||||
state->builder.AddExecutionMode({state->main_func, ExecutionModeDepthReplacing});
|
||||
}
|
||||
if (state->pixel_input_info != nullptr && state->pixel_input_info->ps_early_z &&
|
||||
!state->pixel_input_info->ps_pixel_kill_enable &&
|
||||
!state->pixel_input_info->ps_depth_export_enable &&
|
||||
!state->pixel_input_info->ps_sample_mask_export_enable) {
|
||||
state->builder.AddExecutionMode({state->main_func, ExecutionModeEarlyFragmentTests});
|
||||
}
|
||||
}
|
||||
if (state->needs_compute_derivatives && state->stage == ShaderType::Compute) {
|
||||
state->builder.AddExecutionMode({state->main_func, ExecutionModeDerivativeGroupQuadsKHR});
|
||||
}
|
||||
state->builder.AddName(state->main_func, "main");
|
||||
if (state->stage == ShaderType::Compute || state->needs_function_lds) {
|
||||
state->builder.AddName(state->lds_variable, "lds_dwords");
|
||||
}
|
||||
AddInputAnnotationsAndNames(state);
|
||||
AddOutputAnnotationsAndNames(state);
|
||||
AddDescriptorAnnotationsAndNames(state);
|
||||
AddVsharpAnnotationsAndNames(state);
|
||||
state->builder.AddAnnotation(
|
||||
{OpDecorate, state->storage_runtime_array_type, DecorationArrayStride, 4});
|
||||
state->builder.AddAnnotation(
|
||||
{OpMemberDecorate, state->storage_buffer_type, 0, DecorationOffset, 0});
|
||||
state->builder.AddAnnotation({OpDecorate, state->storage_buffer_type, DecorationBlock});
|
||||
|
||||
state->builder.AddType({OpTypeVoid, state->void_type});
|
||||
state->builder.AddType({OpTypeBool, state->bool_type});
|
||||
state->builder.AddType({OpTypeInt, state->uint_type, 32, 0});
|
||||
state->builder.AddType(
|
||||
{OpTypeStruct, state->uint_pair_type, state->uint_type, state->uint_type});
|
||||
state->builder.AddType({OpTypeInt, state->int_type, 32, 1});
|
||||
state->builder.AddType({OpTypeStruct, state->int_pair_type, state->int_type, state->int_type});
|
||||
state->builder.AddType({OpTypeFloat, state->float_type, 32});
|
||||
state->builder.AddType({OpTypeVector, state->vec2_uint_type, state->uint_type, 2});
|
||||
state->builder.AddType({OpTypeVector, state->vec3_uint_type, state->uint_type, 3});
|
||||
state->builder.AddType({OpTypeVector, state->vec4_uint_type, state->uint_type, 4});
|
||||
state->builder.AddType({OpTypeVector, state->vec2_int_type, state->int_type, 2});
|
||||
state->builder.AddType({OpTypeVector, state->vec3_int_type, state->int_type, 3});
|
||||
state->builder.AddType({OpTypeVector, state->vec4_int_type, state->int_type, 4});
|
||||
state->builder.AddType({OpTypeVector, state->vec2_float_type, state->float_type, 2});
|
||||
state->builder.AddType({OpTypeVector, state->vec3_float_type, state->float_type, 3});
|
||||
state->builder.AddType({OpTypeVector, state->vec4_float_type, state->float_type, 4});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_func_uint, StorageClassFunction, state->uint_type});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_input_float, StorageClassInput, state->float_type});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_input_bool, StorageClassInput, state->bool_type});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_input_int, StorageClassInput, state->int_type});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_input_uint, StorageClassInput, state->uint_type});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_input_vec2_float, StorageClassInput, state->vec2_float_type});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_input_vec3_float, StorageClassInput, state->vec3_float_type});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_input_vec2_int, StorageClassInput, state->vec2_int_type});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_input_vec3_int, StorageClassInput, state->vec3_int_type});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_input_vec4_int, StorageClassInput, state->vec4_int_type});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_input_vec2_uint, StorageClassInput, state->vec2_uint_type});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_input_vec3_uint, StorageClassInput, state->vec3_uint_type});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_input_vec4_uint, StorageClassInput, state->vec4_uint_type});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_input_vec4_float, StorageClassInput, state->vec4_float_type});
|
||||
if (state->subgroup_local_invocation_id_variable != 0) {
|
||||
state->builder.AddType({OpVariable, state->ptr_input_uint,
|
||||
state->subgroup_local_invocation_id_variable, StorageClassInput});
|
||||
}
|
||||
for (const auto& input: state->inputs) {
|
||||
uint32_t ptr_type = state->ptr_input_uint;
|
||||
switch (input.kind) {
|
||||
case IR::StageInputKind::VertexIndex:
|
||||
case IR::StageInputKind::InstanceIndex: ptr_type = state->ptr_input_int; break;
|
||||
case IR::StageInputKind::WorkgroupId:
|
||||
case IR::StageInputKind::LocalInvocationId:
|
||||
case IR::StageInputKind::GlobalInvocationId:
|
||||
ptr_type = state->ptr_input_vec3_uint;
|
||||
break;
|
||||
case IR::StageInputKind::FragCoord: ptr_type = state->ptr_input_vec4_float; break;
|
||||
case IR::StageInputKind::FrontFacing: ptr_type = state->ptr_input_bool; break;
|
||||
case IR::StageInputKind::Parameter:
|
||||
if (state->stage == ShaderType::Vertex) {
|
||||
const auto kind = VertexParameterScalarKind(*state, input.location);
|
||||
const auto components = VertexParameterComponentCount(*state, input);
|
||||
ptr_type = VertexParameterInputPointerType(*state, kind, components);
|
||||
} else {
|
||||
ptr_type = state->ptr_input_vec4_float;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
state->builder.AddType({OpVariable, ptr_type, input.variable_id, StorageClassInput});
|
||||
}
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_output_float, StorageClassOutput, state->float_type});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_output_int, StorageClassOutput, state->int_type});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_output_vec4_float, StorageClassOutput, state->vec4_float_type});
|
||||
if (state->per_vertex_variable != 0) {
|
||||
state->builder.AddType({OpTypeStruct, state->per_vertex_type, state->vec4_float_type});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_output_per_vertex, StorageClassOutput,
|
||||
state->per_vertex_type});
|
||||
state->builder.AddType({OpVariable, state->ptr_output_per_vertex,
|
||||
state->per_vertex_variable, StorageClassOutput});
|
||||
}
|
||||
for (const auto& binding: state->outputs) {
|
||||
if (binding.kind == IR::StageOutputKind::Parameter ||
|
||||
binding.kind == IR::StageOutputKind::Mrt) {
|
||||
state->builder.AddType({OpVariable, state->ptr_output_vec4_float, binding.variable_id,
|
||||
StorageClassOutput});
|
||||
}
|
||||
}
|
||||
if (state->depth_variable != 0) {
|
||||
state->builder.AddType(
|
||||
{OpVariable, state->ptr_output_float, state->depth_variable, StorageClassOutput});
|
||||
}
|
||||
if (state->sample_mask_variable != 0) {
|
||||
state->builder.AddType(
|
||||
{OpTypeArray, state->sample_mask_array_type, state->int_type, ConstantU32(state, 1)});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_output_sample_mask_array,
|
||||
StorageClassOutput, state->sample_mask_array_type});
|
||||
state->builder.AddType({OpVariable, state->ptr_output_sample_mask_array,
|
||||
state->sample_mask_variable, StorageClassOutput});
|
||||
}
|
||||
state->builder.AddType(
|
||||
{OpTypeRuntimeArray, state->storage_runtime_array_type, state->uint_type});
|
||||
state->builder.AddType(
|
||||
{OpTypeStruct, state->storage_buffer_type, state->storage_runtime_array_type});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_storage_buffer, StorageClassStorageBuffer,
|
||||
state->storage_buffer_type});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_storage_buffer_uint,
|
||||
StorageClassStorageBuffer, state->uint_type});
|
||||
if (state->storage_buffer_variable != 0) {
|
||||
const auto count =
|
||||
ConstantU32(state, DescriptorCount(*state, IR::DescriptorBindingKind::Buffers));
|
||||
state->builder.AddType(
|
||||
{OpTypeArray, state->storage_buffer_array_type, state->storage_buffer_type, count});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_storage_buffer_array,
|
||||
StorageClassStorageBuffer, state->storage_buffer_array_type});
|
||||
state->builder.AddType({OpVariable, state->ptr_storage_buffer_array,
|
||||
state->storage_buffer_variable, StorageClassStorageBuffer});
|
||||
}
|
||||
if (state->gds_variable != 0) {
|
||||
state->builder.AddType({OpVariable, state->ptr_storage_buffer, state->gds_variable,
|
||||
StorageClassStorageBuffer});
|
||||
}
|
||||
if (state->push_constant_variable != 0) {
|
||||
const auto row_width = ConstantU32(state, 4);
|
||||
const auto row_count = ConstantU32(
|
||||
state, std::max((state->program->bindings.push_constant_size + 15u) / 16u, 1u));
|
||||
state->builder.AddType(
|
||||
{OpTypeArray, state->push_constant_u32x4_array_type, state->uint_type, row_width});
|
||||
state->builder.AddType({OpTypeArray, state->push_constant_rows_array_type,
|
||||
state->push_constant_u32x4_array_type, row_count});
|
||||
state->builder.AddType(
|
||||
{OpTypeStruct, state->push_constant_block_type, state->push_constant_rows_array_type});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_push_constant_block,
|
||||
StorageClassPushConstant, state->push_constant_block_type});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_push_constant_uint,
|
||||
StorageClassPushConstant, state->uint_type});
|
||||
state->builder.AddType({OpVariable, state->ptr_push_constant_block,
|
||||
state->push_constant_variable, StorageClassPushConstant});
|
||||
}
|
||||
if (state->vsharp_storage_variable != 0) {
|
||||
state->builder.AddType({OpVariable, state->ptr_storage_buffer,
|
||||
state->vsharp_storage_variable, StorageClassStorageBuffer});
|
||||
}
|
||||
if (state->address_memory_variable != 0) {
|
||||
const auto count =
|
||||
ConstantU32(state, DescriptorCount(*state, IR::DescriptorBindingKind::AddressMemory));
|
||||
state->builder.AddType(
|
||||
{OpTypeArray, state->address_memory_array_type, state->storage_buffer_type, count});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_address_memory_array,
|
||||
StorageClassStorageBuffer, state->address_memory_array_type});
|
||||
state->builder.AddType({OpVariable, state->ptr_address_memory_array,
|
||||
state->address_memory_variable, StorageClassStorageBuffer});
|
||||
}
|
||||
if (state->flattened_srt_variable != 0) {
|
||||
state->builder.AddType({OpVariable, state->ptr_storage_buffer,
|
||||
state->flattened_srt_variable, StorageClassStorageBuffer});
|
||||
}
|
||||
if (state->stage == ShaderType::Compute || state->needs_function_lds) {
|
||||
const auto storage_class =
|
||||
state->needs_function_lds ? StorageClassFunction : StorageClassWorkgroup;
|
||||
const auto lds_size = ConstantU32(state, state->needs_function_lds ? 8192u : 1024u);
|
||||
state->builder.AddType({OpTypeArray, state->lds_array_type, state->uint_type, lds_size});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_workgroup_array, storage_class, state->lds_array_type});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_workgroup_uint, storage_class, state->uint_type});
|
||||
if (state->stage == ShaderType::Compute) {
|
||||
state->builder.AddType({OpVariable, state->ptr_workgroup_array, state->lds_variable,
|
||||
StorageClassWorkgroup});
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < state->sampled_images.size(); i++) {
|
||||
auto& image = state->sampled_images[i];
|
||||
const auto view = static_cast<ImageViewKind>(i % 3u);
|
||||
const auto component = i >= 3u ? state->uint_type : state->float_type;
|
||||
const auto dimension = view == ImageViewKind::Dim3D ? Dim3D : Dim2D;
|
||||
const auto arrayed = view == ImageViewKind::Dim2DArray ? 1u : 0u;
|
||||
state->builder.AddType({OpTypeImage, image.image_type, component, dimension, 0, arrayed, 0,
|
||||
1, ImageFormatUnknown});
|
||||
state->builder.AddType({OpTypeSampledImage, image.sampled_image_type, image.image_type});
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, image.pointer_type, StorageClassUniformConstant, image.image_type});
|
||||
if (image.variable != 0) {
|
||||
const auto kind = SampledBindingKind(i >= 3u, view);
|
||||
const auto count = ConstantU32(state, DescriptorCount(*state, kind));
|
||||
state->builder.AddType({OpTypeArray, image.array_type, image.image_type, count});
|
||||
state->builder.AddType({OpTypePointer, image.array_pointer_type,
|
||||
StorageClassUniformConstant, image.array_type});
|
||||
state->builder.AddType({OpVariable, image.array_pointer_type, image.variable,
|
||||
StorageClassUniformConstant});
|
||||
}
|
||||
}
|
||||
state->builder.AddType({OpTypeSampler, state->sampler_type});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_uniform_sampler, StorageClassUniformConstant,
|
||||
state->sampler_type});
|
||||
if (state->sampler_variable != 0) {
|
||||
const auto count =
|
||||
ConstantU32(state, DescriptorCount(*state, IR::DescriptorBindingKind::Samplers));
|
||||
state->builder.AddType(
|
||||
{OpTypeArray, state->sampler_array_type, state->sampler_type, count});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_uniform_sampler_array,
|
||||
StorageClassUniformConstant, state->sampler_array_type});
|
||||
state->builder.AddType({OpVariable, state->ptr_uniform_sampler_array,
|
||||
state->sampler_variable, StorageClassUniformConstant});
|
||||
}
|
||||
state->builder.AddType({OpTypeImage, state->storage_image_type, state->float_type, Dim2D, 0, 0,
|
||||
0, 2, ImageFormatUnknown});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_uniform_storage_image,
|
||||
StorageClassUniformConstant, state->storage_image_type});
|
||||
if (state->storage_image_variable != 0) {
|
||||
const auto count =
|
||||
ConstantU32(state, DescriptorCount(*state, IR::DescriptorBindingKind::Storage2D));
|
||||
state->builder.AddType(
|
||||
{OpTypeArray, state->storage_image_array_type, state->storage_image_type, count});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_uniform_storage_image_array,
|
||||
StorageClassUniformConstant, state->storage_image_array_type});
|
||||
state->builder.AddType({OpVariable, state->ptr_uniform_storage_image_array,
|
||||
state->storage_image_variable, StorageClassUniformConstant});
|
||||
}
|
||||
state->builder.AddType({OpTypeImage, state->storage_image_2d_array_type, state->float_type,
|
||||
Dim2D, 0, 1, 0, 2, ImageFormatUnknown});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_uniform_storage_image_2d_array,
|
||||
StorageClassUniformConstant, state->storage_image_2d_array_type});
|
||||
if (state->storage_image_2d_array_variable != 0) {
|
||||
const auto count =
|
||||
ConstantU32(state, DescriptorCount(*state, IR::DescriptorBindingKind::Storage2DArray));
|
||||
state->builder.AddType({OpTypeArray, state->storage_image_2d_array_array_type,
|
||||
state->storage_image_2d_array_type, count});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_uniform_storage_image_2d_array_array,
|
||||
StorageClassUniformConstant,
|
||||
state->storage_image_2d_array_array_type});
|
||||
state->builder.AddType({OpVariable, state->ptr_uniform_storage_image_2d_array_array,
|
||||
state->storage_image_2d_array_variable,
|
||||
StorageClassUniformConstant});
|
||||
}
|
||||
state->builder.AddType({OpTypeImage, state->storage_image_3d_type, state->float_type, Dim3D, 0,
|
||||
0, 0, 2, ImageFormatUnknown});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_uniform_storage_image_3d,
|
||||
StorageClassUniformConstant, state->storage_image_3d_type});
|
||||
if (state->storage_image_3d_variable != 0) {
|
||||
const auto count =
|
||||
ConstantU32(state, DescriptorCount(*state, IR::DescriptorBindingKind::Storage3D));
|
||||
state->builder.AddType(
|
||||
{OpTypeArray, state->storage_image_3d_array_type, state->storage_image_3d_type, count});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_uniform_storage_image_3d_array,
|
||||
StorageClassUniformConstant, state->storage_image_3d_array_type});
|
||||
state->builder.AddType({OpVariable, state->ptr_uniform_storage_image_3d_array,
|
||||
state->storage_image_3d_variable, StorageClassUniformConstant});
|
||||
}
|
||||
state->builder.AddType({OpTypeImage, state->storage_image_uint_type, state->uint_type, Dim2D, 0,
|
||||
0, 0, 2, ImageFormatR32ui});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_uniform_storage_image_uint,
|
||||
StorageClassUniformConstant, state->storage_image_uint_type});
|
||||
if (state->storage_image_uint_variable != 0) {
|
||||
const auto count =
|
||||
ConstantU32(state, DescriptorCount(*state, IR::DescriptorBindingKind::StorageUint2D));
|
||||
state->builder.AddType({OpTypeArray, state->storage_image_uint_array_type,
|
||||
state->storage_image_uint_type, count});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_uniform_storage_image_uint_array,
|
||||
StorageClassUniformConstant, state->storage_image_uint_array_type});
|
||||
state->builder.AddType({OpVariable, state->ptr_uniform_storage_image_uint_array,
|
||||
state->storage_image_uint_variable, StorageClassUniformConstant});
|
||||
}
|
||||
state->builder.AddType({OpTypeImage, state->storage_image_uint_2d_array_type, state->uint_type,
|
||||
Dim2D, 0, 1, 0, 2, ImageFormatR32ui});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_uniform_storage_image_uint_2d_array,
|
||||
StorageClassUniformConstant, state->storage_image_uint_2d_array_type});
|
||||
if (state->storage_image_uint_2d_array_variable != 0) {
|
||||
const auto count = ConstantU32(
|
||||
state, DescriptorCount(*state, IR::DescriptorBindingKind::StorageUint2DArray));
|
||||
state->builder.AddType({OpTypeArray, state->storage_image_uint_2d_array_array_type,
|
||||
state->storage_image_uint_2d_array_type, count});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_uniform_storage_image_uint_2d_array_array,
|
||||
StorageClassUniformConstant,
|
||||
state->storage_image_uint_2d_array_array_type});
|
||||
state->builder.AddType({OpVariable, state->ptr_uniform_storage_image_uint_2d_array_array,
|
||||
state->storage_image_uint_2d_array_variable,
|
||||
StorageClassUniformConstant});
|
||||
}
|
||||
state->builder.AddType({OpTypeImage, state->storage_image_uint_3d_type, state->uint_type, Dim3D,
|
||||
0, 0, 0, 2, ImageFormatR32ui});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_uniform_storage_image_uint_3d,
|
||||
StorageClassUniformConstant, state->storage_image_uint_3d_type});
|
||||
if (state->storage_image_uint_3d_variable != 0) {
|
||||
const auto count =
|
||||
ConstantU32(state, DescriptorCount(*state, IR::DescriptorBindingKind::StorageUint3D));
|
||||
state->builder.AddType({OpTypeArray, state->storage_image_uint_3d_array_type,
|
||||
state->storage_image_uint_3d_type, count});
|
||||
state->builder.AddType({OpTypePointer, state->ptr_uniform_storage_image_uint_3d_array,
|
||||
StorageClassUniformConstant,
|
||||
state->storage_image_uint_3d_array_type});
|
||||
state->builder.AddType({OpVariable, state->ptr_uniform_storage_image_uint_3d_array,
|
||||
state->storage_image_uint_3d_variable,
|
||||
StorageClassUniformConstant});
|
||||
}
|
||||
state->builder.AddType(
|
||||
{OpTypePointer, state->ptr_image_uint, StorageClassImage, state->uint_type});
|
||||
state->builder.AddType({OpTypeFunction, state->func_type, state->void_type});
|
||||
}
|
||||
|
||||
void AllocateRegisterVariables(EmitterState* state) {
|
||||
if (state->stage == ShaderType::Pixel && state->needs_pixel_valid_mask) {
|
||||
state->pixel_valid_mask_variable = state->builder.AllocateId();
|
||||
state->builder.AddName(state->pixel_valid_mask_variable, "pixel_valid_mask_active");
|
||||
}
|
||||
for (auto& binding: state->registers) {
|
||||
binding.pointer_id = state->builder.AllocateId();
|
||||
const auto name = IR::RegisterToString(binding.reg);
|
||||
state->builder.AddName(binding.pointer_id, name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void AllocateDescriptorVariables(EmitterState* state) {
|
||||
if (DescriptorBinding(*state, IR::DescriptorBindingKind::Buffers) != nullptr) {
|
||||
state->storage_buffer_variable = state->builder.AllocateId();
|
||||
}
|
||||
if (DescriptorBinding(*state, IR::DescriptorBindingKind::AddressMemory) != nullptr) {
|
||||
state->address_memory_variable = state->builder.AllocateId();
|
||||
}
|
||||
if (state->program->bindings.push_constant_size != 0) {
|
||||
state->push_constant_variable = state->builder.AllocateId();
|
||||
}
|
||||
if (DescriptorBinding(*state, IR::DescriptorBindingKind::UserData) != nullptr) {
|
||||
state->vsharp_storage_variable = state->builder.AllocateId();
|
||||
}
|
||||
if (DescriptorBinding(*state, IR::DescriptorBindingKind::FlattenedSrt) != nullptr) {
|
||||
state->flattened_srt_variable = state->builder.AllocateId();
|
||||
}
|
||||
for (uint32_t i = 0; i < state->sampled_images.size(); i++) {
|
||||
const auto view = static_cast<ImageViewKind>(i % 3u);
|
||||
if (DescriptorBinding(*state, SampledBindingKind(i >= 3u, view)) != nullptr) {
|
||||
state->sampled_images[i].variable = state->builder.AllocateId();
|
||||
}
|
||||
}
|
||||
if (DescriptorBinding(*state, IR::DescriptorBindingKind::Storage2D) != nullptr) {
|
||||
state->storage_image_variable = state->builder.AllocateId();
|
||||
}
|
||||
if (DescriptorBinding(*state, IR::DescriptorBindingKind::StorageUint2D) != nullptr) {
|
||||
state->storage_image_uint_variable = state->builder.AllocateId();
|
||||
}
|
||||
if (DescriptorBinding(*state, IR::DescriptorBindingKind::Storage2DArray) != nullptr) {
|
||||
state->storage_image_2d_array_variable = state->builder.AllocateId();
|
||||
}
|
||||
if (DescriptorBinding(*state, IR::DescriptorBindingKind::StorageUint2DArray) != nullptr) {
|
||||
state->storage_image_uint_2d_array_variable = state->builder.AllocateId();
|
||||
}
|
||||
if (DescriptorBinding(*state, IR::DescriptorBindingKind::Storage3D) != nullptr) {
|
||||
state->storage_image_3d_variable = state->builder.AllocateId();
|
||||
}
|
||||
if (DescriptorBinding(*state, IR::DescriptorBindingKind::StorageUint3D) != nullptr) {
|
||||
state->storage_image_uint_3d_variable = state->builder.AllocateId();
|
||||
}
|
||||
if (DescriptorBinding(*state, IR::DescriptorBindingKind::Samplers) != nullptr) {
|
||||
state->sampler_variable = state->builder.AllocateId();
|
||||
}
|
||||
if (DescriptorBinding(*state, IR::DescriptorBindingKind::Gds) != nullptr) {
|
||||
state->gds_variable = state->builder.AllocateId();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
|
||||
@@ -0,0 +1,840 @@
|
||||
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
bool SdwaSelectorOffsetWidth(uint32_t sel, uint32_t& offset, uint32_t& width) {
|
||||
if (sel <= 3u) {
|
||||
offset = sel * 8u;
|
||||
width = 8u;
|
||||
return true;
|
||||
}
|
||||
if (sel == 4u || sel == 5u) {
|
||||
offset = sel == 5u ? 16u : 0u;
|
||||
width = 16u;
|
||||
return true;
|
||||
}
|
||||
if (sel == 6u) {
|
||||
offset = 0;
|
||||
width = 32u;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t EmitSdwaExtractU32(EmitterState* state, const IR::Operand& operand, uint32_t value) {
|
||||
if (operand.sdwa_sel == 6u && !operand.sdwa_sext) {
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t offset = 0;
|
||||
uint32_t width = 0;
|
||||
if (!SdwaSelectorOffsetWidth(operand.sdwa_sel, offset, width) || width == 32u) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (operand.sdwa_sext) {
|
||||
const auto signed_value = state->builder.AllocateId();
|
||||
const auto extracted = state->builder.AllocateId();
|
||||
const auto bits = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitcast, state->int_type, signed_value, value});
|
||||
state->builder.AddFunction({OpBitFieldSExtract, state->int_type, extracted, signed_value,
|
||||
ConstantU32(state, offset), ConstantU32(state, width)});
|
||||
state->builder.AddFunction({OpBitcast, state->uint_type, bits, extracted});
|
||||
return bits;
|
||||
}
|
||||
|
||||
const auto extracted = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitFieldUExtract, state->uint_type, extracted, value,
|
||||
ConstantU32(state, offset), ConstantU32(state, width)});
|
||||
return extracted;
|
||||
}
|
||||
|
||||
uint32_t EmitTrueBool(EmitterState* state) {
|
||||
const auto ret = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpINotEqual, state->bool_type, ret, ConstantU32(state, 1), ConstantU32(state, 0)});
|
||||
return ret;
|
||||
}
|
||||
|
||||
DppTargetLane EmitDppQuadPermTargetLane(EmitterState* state, uint32_t subid, uint32_t control) {
|
||||
const auto quad_base = state->builder.AllocateId();
|
||||
const auto lane = state->builder.AllocateId();
|
||||
const auto shift = state->builder.AllocateId();
|
||||
const auto selected0 = state->builder.AllocateId();
|
||||
const auto selected = state->builder.AllocateId();
|
||||
const auto target = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, quad_base, subid, ConstantU32(state, 0xfffffffcu)});
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, lane, subid, ConstantU32(state, 3)});
|
||||
state->builder.AddFunction(
|
||||
{OpShiftLeftLogical, state->uint_type, shift, lane, ConstantU32(state, 1)});
|
||||
state->builder.AddFunction(
|
||||
{OpShiftRightLogical, state->uint_type, selected0, ConstantU32(state, control), shift});
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, selected, selected0, ConstantU32(state, 3)});
|
||||
state->builder.AddFunction({OpBitwiseOr, state->uint_type, target, quad_base, selected});
|
||||
return {target, EmitTrueBool(state)};
|
||||
}
|
||||
|
||||
DppTargetLane EmitDppRowShiftTargetLane(EmitterState* state, uint32_t subid, uint32_t amount,
|
||||
bool left) {
|
||||
const auto row = state->builder.AllocateId();
|
||||
const auto lane = state->builder.AllocateId();
|
||||
const auto lane_shifted = state->builder.AllocateId();
|
||||
const auto target = state->builder.AllocateId();
|
||||
const auto valid = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, row, subid, ConstantU32(state, 0xfffffff0u)});
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, lane, subid, ConstantU32(state, 15)});
|
||||
if (left) {
|
||||
state->builder.AddFunction(
|
||||
{OpIAdd, state->uint_type, lane_shifted, lane, ConstantU32(state, amount)});
|
||||
state->builder.AddFunction(
|
||||
{OpULessThan, state->bool_type, valid, lane, ConstantU32(state, 16u - amount)});
|
||||
} else {
|
||||
state->builder.AddFunction(
|
||||
{OpISub, state->uint_type, lane_shifted, lane, ConstantU32(state, amount)});
|
||||
state->builder.AddFunction(
|
||||
{OpUGreaterThanEqual, state->bool_type, valid, lane, ConstantU32(state, amount)});
|
||||
}
|
||||
state->builder.AddFunction({OpBitwiseOr, state->uint_type, target, row, lane_shifted});
|
||||
return {target, valid};
|
||||
}
|
||||
|
||||
DppTargetLane EmitDppRowRotateRightTargetLane(EmitterState* state, uint32_t subid,
|
||||
uint32_t amount) {
|
||||
const auto row = state->builder.AllocateId();
|
||||
const auto lane = state->builder.AllocateId();
|
||||
const auto in_high = state->builder.AllocateId();
|
||||
const auto minus = state->builder.AllocateId();
|
||||
const auto plus = state->builder.AllocateId();
|
||||
const auto selected = state->builder.AllocateId();
|
||||
const auto target = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, row, subid, ConstantU32(state, 0xfffffff0u)});
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, lane, subid, ConstantU32(state, 15)});
|
||||
state->builder.AddFunction(
|
||||
{OpUGreaterThanEqual, state->bool_type, in_high, lane, ConstantU32(state, amount)});
|
||||
state->builder.AddFunction({OpISub, state->uint_type, minus, lane, ConstantU32(state, amount)});
|
||||
state->builder.AddFunction(
|
||||
{OpIAdd, state->uint_type, plus, lane, ConstantU32(state, 16u - amount)});
|
||||
state->builder.AddFunction({OpSelect, state->uint_type, selected, in_high, minus, plus});
|
||||
state->builder.AddFunction({OpBitwiseOr, state->uint_type, target, row, selected});
|
||||
return {target, EmitTrueBool(state)};
|
||||
}
|
||||
|
||||
DppTargetLane EmitDppMirrorTargetLane(EmitterState* state, uint32_t subid, bool half_row) {
|
||||
const auto base_mask = half_row ? 0xfffffff8u : 0xfffffff0u;
|
||||
const auto lane_mask = half_row ? 7u : 15u;
|
||||
const auto base = state->builder.AllocateId();
|
||||
const auto lane = state->builder.AllocateId();
|
||||
const auto mirrored = state->builder.AllocateId();
|
||||
const auto target = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, base, subid, ConstantU32(state, base_mask)});
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, lane, subid, ConstantU32(state, lane_mask)});
|
||||
state->builder.AddFunction(
|
||||
{OpISub, state->uint_type, mirrored, ConstantU32(state, lane_mask), lane});
|
||||
state->builder.AddFunction({OpBitwiseOr, state->uint_type, target, base, mirrored});
|
||||
return {target, EmitTrueBool(state)};
|
||||
}
|
||||
|
||||
DppTargetLane EmitDppTargetLane(EmitterState* state, uint32_t control) {
|
||||
const auto subid = EmitSubgroupLocalInvocationId(state);
|
||||
if (control <= 0xffu) {
|
||||
return EmitDppQuadPermTargetLane(state, subid, control);
|
||||
}
|
||||
if (control >= 0x101u && control <= 0x10fu) {
|
||||
return EmitDppRowShiftTargetLane(state, subid, control & 0xfu, true);
|
||||
}
|
||||
if (control >= 0x111u && control <= 0x11fu) {
|
||||
return EmitDppRowShiftTargetLane(state, subid, control & 0xfu, false);
|
||||
}
|
||||
if (control >= 0x121u && control <= 0x12fu) {
|
||||
return EmitDppRowRotateRightTargetLane(state, subid, control & 0xfu);
|
||||
}
|
||||
if (control == 0x140u) {
|
||||
return EmitDppMirrorTargetLane(state, subid, false);
|
||||
}
|
||||
if (control == 0x141u) {
|
||||
return EmitDppMirrorTargetLane(state, subid, true);
|
||||
}
|
||||
return {subid, EmitTrueBool(state)};
|
||||
}
|
||||
|
||||
uint32_t EmitDppValueU32(EmitterState* state, const IR::Operand& operand, uint32_t value) {
|
||||
if (!operand.dpp) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const auto target = EmitDppTargetLane(state, operand.dpp_ctrl);
|
||||
const auto shuffled = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpGroupNonUniformShuffle, state->uint_type, shuffled,
|
||||
ConstantU32(state, ScopeSubgroup), value, target.lane});
|
||||
if (operand.dpp_fetch_inactive) {
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
const auto source_active = EmitLaneIndexActiveBool(state, target.lane);
|
||||
const auto can_fetch = state->builder.AllocateId();
|
||||
const auto ret = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpLogicalAnd, state->bool_type, can_fetch, target.valid, source_active});
|
||||
state->builder.AddFunction(
|
||||
{OpSelect, state->uint_type, ret, can_fetch, shuffled, ConstantU32(state, 0)});
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitValueLoad(EmitterState* state, const IR::Operand& operand) {
|
||||
uint32_t value = 0;
|
||||
switch (operand.kind) {
|
||||
case IR::OperandKind::ImmediateU32:
|
||||
case IR::OperandKind::PcRelativeU32: value = ConstantU32(state, operand.imm); break;
|
||||
case IR::OperandKind::Register: {
|
||||
const auto pointer = PointerForRegister(*state, operand.reg);
|
||||
if (pointer == 0) {
|
||||
value = ConstantU32(state, 0);
|
||||
break;
|
||||
}
|
||||
value = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpLoad, state->uint_type, value, pointer});
|
||||
break;
|
||||
}
|
||||
case IR::OperandKind::Null: value = ConstantU32(state, 0); break;
|
||||
default: value = ConstantU32(state, 0); break;
|
||||
}
|
||||
value = EmitDppValueU32(state, operand, value);
|
||||
return EmitSdwaExtractU32(state, operand, value);
|
||||
}
|
||||
|
||||
uint32_t EmitRegisterLoad(EmitterState* state, IR::Register reg) {
|
||||
if (IsInactiveWave32ExecHigh(*state, reg)) {
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
const auto pointer = PointerForRegister(*state, reg);
|
||||
if (pointer == 0) {
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
const auto value = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpLoad, state->uint_type, value, pointer});
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t WaveMaskHighLoad(EmitterState* state, IR::RegisterFile file) {
|
||||
if (state->wave_size == 32u && IsMaskRegisterFile(file)) {
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
return EmitRegisterLoad(state, {file, 1});
|
||||
}
|
||||
|
||||
uint32_t EmitLaneMaskPartU32(EmitterState* state, uint32_t part) {
|
||||
const auto subid = EmitSubgroupLocalInvocationId(state);
|
||||
const auto lane = state->builder.AllocateId();
|
||||
const auto bit = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, lane, subid, ConstantU32(state, 31)});
|
||||
state->builder.AddFunction(
|
||||
{OpShiftLeftLogical, state->uint_type, bit, ConstantU32(state, 1), lane});
|
||||
if (part == 0) {
|
||||
const auto in_low = state->builder.AllocateId();
|
||||
const auto ret = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpULessThan, state->bool_type, in_low, subid, ConstantU32(state, 32)});
|
||||
state->builder.AddFunction(
|
||||
{OpSelect, state->uint_type, ret, in_low, bit, ConstantU32(state, 0)});
|
||||
return ret;
|
||||
}
|
||||
if (state->wave_size == 32u) {
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
const auto in_high = state->builder.AllocateId();
|
||||
const auto ret = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpUGreaterThanEqual, state->bool_type, in_high, subid, ConstantU32(state, 32)});
|
||||
state->builder.AddFunction(
|
||||
{OpSelect, state->uint_type, ret, in_high, bit, ConstantU32(state, 0)});
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitThreadMaskBelowPartU32(EmitterState* state, uint32_t part) {
|
||||
const auto subid = EmitSubgroupLocalInvocationId(state);
|
||||
const auto lane = state->builder.AllocateId();
|
||||
const auto bit = state->builder.AllocateId();
|
||||
const auto below = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, lane, subid, ConstantU32(state, 31)});
|
||||
state->builder.AddFunction(
|
||||
{OpShiftLeftLogical, state->uint_type, bit, ConstantU32(state, 1), lane});
|
||||
state->builder.AddFunction({OpISub, state->uint_type, below, bit, ConstantU32(state, 1)});
|
||||
|
||||
if (part == 0) {
|
||||
const auto in_high = state->builder.AllocateId();
|
||||
const auto ret = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpUGreaterThanEqual, state->bool_type, in_high, subid, ConstantU32(state, 32)});
|
||||
state->builder.AddFunction(
|
||||
{OpSelect, state->uint_type, ret, in_high, ConstantU32(state, 0xffffffffu), below});
|
||||
return ret;
|
||||
}
|
||||
if (state->wave_size == 32u) {
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
const auto in_high = state->builder.AllocateId();
|
||||
const auto ret = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpUGreaterThanEqual, state->bool_type, in_high, subid, ConstantU32(state, 32)});
|
||||
state->builder.AddFunction(
|
||||
{OpSelect, state->uint_type, ret, in_high, below, ConstantU32(state, 0)});
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitMaskActiveBool(EmitterState* state, IR::RegisterFile file) {
|
||||
const auto low = EmitRegisterLoad(state, {file, 0});
|
||||
const auto condition = state->builder.AllocateId();
|
||||
if (state->per_invocation_masks) {
|
||||
const auto combined = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseOr, state->uint_type, combined, low, WaveMaskHighLoad(state, file)});
|
||||
state->builder.AddFunction(
|
||||
{OpINotEqual, state->bool_type, condition, combined, ConstantU32(state, 0)});
|
||||
return condition;
|
||||
}
|
||||
if (state->exact_subgroup_operations) {
|
||||
const auto high = WaveMaskHighLoad(state, file);
|
||||
const auto low_hit = state->builder.AllocateId();
|
||||
const auto high_hit = state->builder.AllocateId();
|
||||
const auto active = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, low_hit, low, EmitLaneMaskPartU32(state, 0)});
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, high_hit, high, EmitLaneMaskPartU32(state, 1)});
|
||||
state->builder.AddFunction({OpBitwiseOr, state->uint_type, active, low_hit, high_hit});
|
||||
state->builder.AddFunction(
|
||||
{OpINotEqual, state->bool_type, condition, active, ConstantU32(state, 0)});
|
||||
return condition;
|
||||
}
|
||||
state->builder.AddFunction(
|
||||
{OpINotEqual, state->bool_type, condition, low, ConstantU32(state, 0)});
|
||||
return condition;
|
||||
}
|
||||
|
||||
uint32_t EmitMaskZeroBool(EmitterState* state, IR::RegisterFile file, bool zero) {
|
||||
const auto active = EmitMaskActiveBool(state, file);
|
||||
if (!zero) {
|
||||
return active;
|
||||
}
|
||||
const auto cond = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpLogicalNot, state->bool_type, cond, active});
|
||||
return cond;
|
||||
}
|
||||
|
||||
uint32_t EmitMaskSummaryZeroBool(EmitterState* state, IR::RegisterFile file, bool zero) {
|
||||
if (state->per_invocation_masks) {
|
||||
return EmitMaskZeroBool(state, file, zero);
|
||||
}
|
||||
if (state->exact_subgroup_operations) {
|
||||
const auto low = EmitRegisterLoad(state, {file, 0});
|
||||
const auto high = WaveMaskHighLoad(state, file);
|
||||
const auto combined = state->builder.AllocateId();
|
||||
const auto active = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitwiseOr, state->uint_type, combined, low, high});
|
||||
state->builder.AddFunction(
|
||||
{OpINotEqual, state->bool_type, active, combined, ConstantU32(state, 0)});
|
||||
if (!zero) {
|
||||
return active;
|
||||
}
|
||||
const auto inactive = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpLogicalNot, state->bool_type, inactive, active});
|
||||
return inactive;
|
||||
}
|
||||
const auto active = EmitMaskActiveBool(state, file);
|
||||
if (!zero) {
|
||||
return active;
|
||||
}
|
||||
const auto inactive = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpLogicalNot, state->bool_type, inactive, active});
|
||||
return inactive;
|
||||
}
|
||||
|
||||
uint32_t EmitExecActiveBool(EmitterState* state) {
|
||||
return EmitMaskZeroBool(state, IR::RegisterFile::Exec, false);
|
||||
}
|
||||
|
||||
uint32_t EmitConditionBool(EmitterState* state, const IR::Operand& operand) {
|
||||
if (operand.kind == IR::OperandKind::Register) {
|
||||
if (operand.reg.file == IR::RegisterFile::Exec ||
|
||||
operand.reg.file == IR::RegisterFile::Vcc) {
|
||||
return EmitMaskActiveBool(state, operand.reg.file);
|
||||
}
|
||||
}
|
||||
const auto cond_value = EmitValueLoad(state, operand);
|
||||
const auto cond = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpINotEqual, state->bool_type, cond, cond_value, ConstantU32(state, 0)});
|
||||
return cond;
|
||||
}
|
||||
|
||||
uint32_t EmitSubgroupLocalInvocationId(EmitterState* state) {
|
||||
if (state->subgroup_local_invocation_id_variable == 0) {
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
const auto value = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpLoad, state->uint_type, value, state->subgroup_local_invocation_id_variable});
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t InputVariableForKind(const EmitterState& state, IR::StageInputKind kind) {
|
||||
for (const auto& input: state.inputs) {
|
||||
if (input.kind == kind) {
|
||||
return input.variable_id;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t InputVariableForParameter(const EmitterState& state, uint32_t location) {
|
||||
for (const auto& input: state.inputs) {
|
||||
if (input.kind == IR::StageInputKind::Parameter && input.location == location) {
|
||||
return input.variable_id;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const InputBinding* InputBindingForParameter(const EmitterState& state, uint32_t location) {
|
||||
for (const auto& input: state.inputs) {
|
||||
if (input.kind == IR::StageInputKind::Parameter && input.location == location) {
|
||||
return &input;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t EmitInputComponentU32(EmitterState* state, IR::StageInputKind kind, uint32_t component) {
|
||||
const auto variable = InputVariableForKind(*state, kind);
|
||||
if (variable == 0) {
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
const auto pointer = state->builder.AllocateId();
|
||||
const auto value = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpAccessChain, state->ptr_input_uint, pointer, variable, ConstantU32(state, component)});
|
||||
state->builder.AddFunction({OpLoad, state->uint_type, value, pointer});
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t EmitLocalInvocationIndex(EmitterState* state) {
|
||||
const auto variable = InputVariableForKind(*state, IR::StageInputKind::LocalInvocationIndex);
|
||||
if (variable == 0) {
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
const auto value = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpLoad, state->uint_type, value, variable});
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t VertexInputDefaultComponentU32(EmitterState* state, VertexInputScalarKind kind,
|
||||
uint32_t component) {
|
||||
if ((component & 3u) != 3u) {
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
return ConstantU32(state, kind == VertexInputScalarKind::Float ? 0x3f800000u : 1u);
|
||||
}
|
||||
|
||||
uint32_t EmitVertexParameterComponentU32(EmitterState* state, const InputBinding& input,
|
||||
uint32_t component) {
|
||||
const auto count = VertexParameterComponentCount(*state, input);
|
||||
const auto kind = VertexParameterScalarKind(*state, input.location);
|
||||
if (component >= count) {
|
||||
return VertexInputDefaultComponentU32(state, kind, component);
|
||||
}
|
||||
|
||||
const auto scalar_type = VertexParameterScalarType(*state, kind);
|
||||
uint32_t raw = state->builder.AllocateId();
|
||||
if (count == 1u) {
|
||||
state->builder.AddFunction({OpLoad, scalar_type, raw, input.variable_id});
|
||||
} else {
|
||||
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)});
|
||||
state->builder.AddFunction({OpLoad, scalar_type, raw, pointer});
|
||||
}
|
||||
|
||||
if (kind == VertexInputScalarKind::Uint) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
const auto bits = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitcast, state->uint_type, bits, raw});
|
||||
return bits;
|
||||
}
|
||||
|
||||
void EmitLoadInputF32(EmitterState* state, const IR::Instruction& inst) {
|
||||
const auto* input = InputBindingForParameter(*state, inst.input_info.attr);
|
||||
if (input == nullptr || input->variable_id == 0) {
|
||||
EmitStoreU32(state, inst.dst, ConstantU32(state, 0));
|
||||
return;
|
||||
}
|
||||
|
||||
if (state->stage == ShaderType::Vertex) {
|
||||
EmitStoreU32(state, inst.dst,
|
||||
EmitVertexParameterComponentU32(state, *input, inst.input_info.chan & 3u));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto input_value = state->builder.AllocateId();
|
||||
const auto component = state->builder.AllocateId();
|
||||
const auto bits = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpLoad, state->vec4_float_type, input_value, input->variable_id});
|
||||
state->builder.AddFunction(
|
||||
{OpCompositeExtract, state->float_type, component, input_value, inst.input_info.chan & 3u});
|
||||
state->builder.AddFunction({OpBitcast, state->uint_type, bits, component});
|
||||
EmitStoreU32(state, inst.dst, bits);
|
||||
}
|
||||
|
||||
uint32_t EmitSccBool(EmitterState* state, bool non_zero) {
|
||||
const auto value = EmitRegisterLoad(state, SccRegister());
|
||||
const auto cond = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{non_zero ? OpINotEqual : OpIEqual, state->bool_type, cond, value, ConstantU32(state, 0)});
|
||||
return cond;
|
||||
}
|
||||
|
||||
uint32_t EmitFloatLoad(EmitterState* state, const IR::Operand& operand) {
|
||||
const auto uint_value = EmitValueLoad(state, operand);
|
||||
auto float_value = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitcast, state->float_type, float_value, uint_value});
|
||||
if (operand.absolute) {
|
||||
const auto abs_value = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpExtInst, state->float_type, abs_value, state->glsl_std450, GlslFAbs, float_value});
|
||||
float_value = abs_value;
|
||||
}
|
||||
if (operand.negate) {
|
||||
const auto neg_value = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpFNegate, state->float_type, neg_value, float_value});
|
||||
float_value = neg_value;
|
||||
}
|
||||
return float_value;
|
||||
}
|
||||
|
||||
uint32_t ApplyResultModifiersF32(EmitterState* state, uint32_t value, const IR::Operand& dst) {
|
||||
uint32_t result = value;
|
||||
if (dst.omod != 0) {
|
||||
const uint32_t bits = dst.omod == 1u ? 0x40000000u
|
||||
: dst.omod == 2u ? 0x40800000u
|
||||
: 0x3f000000u;
|
||||
const auto scaled = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpFMul, state->float_type, scaled, result, ConstantF32(state, bits)});
|
||||
result = scaled;
|
||||
}
|
||||
if (!dst.clamp) {
|
||||
return result;
|
||||
}
|
||||
const auto ret = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpExtInst, state->float_type, ret, state->glsl_std450, GlslFClamp,
|
||||
result, ConstantF32(state, 0), ConstantF32(state, 0x3f800000u)});
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitMixF32Load(EmitterState* state, const IR::Operand& operand) {
|
||||
auto value_operand = operand;
|
||||
value_operand.negate_hi = false;
|
||||
|
||||
if (operand.op_sel_hi) {
|
||||
auto raw = operand;
|
||||
raw.sdwa_sel = 6;
|
||||
raw.sdwa_sext = false;
|
||||
raw.op_sel = false;
|
||||
raw.op_sel_hi = false;
|
||||
raw.negate = false;
|
||||
raw.negate_hi = false;
|
||||
raw.absolute = false;
|
||||
raw.dpp = false;
|
||||
raw.dpp_fetch_inactive = false;
|
||||
raw.dpp_bound_ctrl = false;
|
||||
|
||||
const auto bits = EmitValueLoad(state, raw);
|
||||
const auto unpacked = state->builder.AllocateId();
|
||||
auto f32 = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpExtInst, state->vec2_float_type, unpacked, state->glsl_std450,
|
||||
GlslUnpackHalf2x16, bits});
|
||||
state->builder.AddFunction(
|
||||
{OpCompositeExtract, state->float_type, f32, unpacked, operand.op_sel ? 1u : 0u});
|
||||
if (operand.absolute) {
|
||||
const auto abs_value = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpExtInst, state->float_type, abs_value, state->glsl_std450, GlslFAbs, f32});
|
||||
f32 = abs_value;
|
||||
}
|
||||
if (value_operand.negate) {
|
||||
const auto neg_value = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpFNegate, state->float_type, neg_value, f32});
|
||||
f32 = neg_value;
|
||||
}
|
||||
return f32;
|
||||
}
|
||||
|
||||
value_operand.op_sel = false;
|
||||
value_operand.op_sel_hi = false;
|
||||
return EmitFloatLoad(state, value_operand);
|
||||
}
|
||||
|
||||
IR::Operand OffsetRegisterOperand(const IR::Operand& operand, uint32_t offset) {
|
||||
if (offset == 0 || operand.kind != IR::OperandKind::Register) {
|
||||
return operand;
|
||||
}
|
||||
auto ret = operand;
|
||||
ret.reg.index += offset;
|
||||
ret.sdwa_sel = 6;
|
||||
ret.omod = 0;
|
||||
ret.sdwa_sext = false;
|
||||
ret.op_sel = false;
|
||||
ret.op_sel_hi = false;
|
||||
ret.negate = false;
|
||||
ret.negate_hi = false;
|
||||
ret.absolute = false;
|
||||
ret.clamp = false;
|
||||
ret.dpp_ctrl = 0;
|
||||
ret.dpp_row_mask = 0xf;
|
||||
ret.dpp_bank_mask = 0xf;
|
||||
ret.dpp_fetch_inactive = false;
|
||||
ret.dpp_bound_ctrl = false;
|
||||
ret.dpp = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitLaneMaskOperandActiveBool(EmitterState* state, const IR::Operand& operand) {
|
||||
const auto low = EmitValueLoad(state, operand);
|
||||
const auto active = state->builder.AllocateId();
|
||||
if (state->per_invocation_masks || state->exact_subgroup_operations) {
|
||||
uint32_t high = ConstantU32(state, 0);
|
||||
if (operand.kind == IR::OperandKind::Register &&
|
||||
(operand.reg.file == IR::RegisterFile::Scalar ||
|
||||
operand.reg.file == IR::RegisterFile::Vcc ||
|
||||
operand.reg.file == IR::RegisterFile::Exec)) {
|
||||
high = IsMaskRegisterFile(operand.reg.file)
|
||||
? WaveMaskHighLoad(state, operand.reg.file)
|
||||
: EmitValueLoad(state, OffsetRegisterOperand(operand, 1));
|
||||
}
|
||||
if (state->per_invocation_masks) {
|
||||
const auto combined = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitwiseOr, state->uint_type, combined, low, high});
|
||||
state->builder.AddFunction(
|
||||
{OpINotEqual, state->bool_type, active, combined, ConstantU32(state, 0)});
|
||||
return active;
|
||||
}
|
||||
const auto low_hit = state->builder.AllocateId();
|
||||
const auto high_hit = state->builder.AllocateId();
|
||||
const auto hit = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, low_hit, low, EmitLaneMaskPartU32(state, 0)});
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, high_hit, high, EmitLaneMaskPartU32(state, 1)});
|
||||
state->builder.AddFunction({OpBitwiseOr, state->uint_type, hit, low_hit, high_hit});
|
||||
state->builder.AddFunction(
|
||||
{OpINotEqual, state->bool_type, active, hit, ConstantU32(state, 0)});
|
||||
return active;
|
||||
}
|
||||
state->builder.AddFunction({OpINotEqual, state->bool_type, active, low, ConstantU32(state, 0)});
|
||||
return active;
|
||||
}
|
||||
|
||||
uint32_t EmitLaneIndexActiveBool(EmitterState* state, uint32_t lane) {
|
||||
if (state->per_invocation_masks) {
|
||||
const auto ballot = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpGroupNonUniformBallot, state->vec4_uint_type, ballot,
|
||||
ConstantU32(state, ScopeSubgroup), EmitExecActiveBool(state)});
|
||||
return EmitBallotLaneActiveBool(state, ballot, lane);
|
||||
}
|
||||
const auto lane_low = state->builder.AllocateId();
|
||||
const auto bit = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, lane_low, lane, ConstantU32(state, 31)});
|
||||
state->builder.AddFunction(
|
||||
{OpShiftLeftLogical, state->uint_type, bit, ConstantU32(state, 1), lane_low});
|
||||
|
||||
uint32_t mask = EmitRegisterLoad(state, {IR::RegisterFile::Exec, 0});
|
||||
if (state->wave_size == 64u) {
|
||||
const auto high = EmitRegisterLoad(state, {IR::RegisterFile::Exec, 1});
|
||||
const auto in_high = state->builder.AllocateId();
|
||||
const auto selected = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpUGreaterThanEqual, state->bool_type, in_high, lane, ConstantU32(state, 32)});
|
||||
state->builder.AddFunction({OpSelect, state->uint_type, selected, in_high, high, mask});
|
||||
mask = selected;
|
||||
}
|
||||
|
||||
const auto hit = state->builder.AllocateId();
|
||||
const auto ret = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitwiseAnd, state->uint_type, hit, mask, bit});
|
||||
state->builder.AddFunction({OpINotEqual, state->bool_type, ret, hit, ConstantU32(state, 0)});
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitSubgroupLaneActiveBool(EmitterState* state, uint32_t lane) {
|
||||
const auto active_ballot = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpGroupNonUniformBallot, state->vec4_uint_type, active_ballot,
|
||||
ConstantU32(state, ScopeSubgroup), EmitTrueBool(state)});
|
||||
return EmitBallotLaneActiveBool(state, active_ballot, lane);
|
||||
}
|
||||
uint32_t EmitBallotLaneActiveBool(EmitterState* state, uint32_t active_ballot, uint32_t lane) {
|
||||
const auto low = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpCompositeExtract, state->uint_type, low, active_ballot, 0});
|
||||
uint32_t mask = low;
|
||||
if (state->wave_size == 64u) {
|
||||
const auto high = state->builder.AllocateId();
|
||||
const auto in_high = state->builder.AllocateId();
|
||||
const auto selected = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpCompositeExtract, state->uint_type, high, active_ballot, 1});
|
||||
state->builder.AddFunction(
|
||||
{OpUGreaterThanEqual, state->bool_type, in_high, lane, ConstantU32(state, 32)});
|
||||
state->builder.AddFunction({OpSelect, state->uint_type, selected, in_high, high, low});
|
||||
mask = selected;
|
||||
}
|
||||
|
||||
const auto lane_low = state->builder.AllocateId();
|
||||
const auto bit = state->builder.AllocateId();
|
||||
state->builder.AddFunction(
|
||||
{OpBitwiseAnd, state->uint_type, lane_low, lane, ConstantU32(state, 31)});
|
||||
state->builder.AddFunction(
|
||||
{OpShiftLeftLogical, state->uint_type, bit, ConstantU32(state, 1), lane_low});
|
||||
|
||||
const auto hit = state->builder.AllocateId();
|
||||
const auto active = state->builder.AllocateId();
|
||||
const auto in_range = state->builder.AllocateId();
|
||||
const auto ret = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitwiseAnd, state->uint_type, hit, mask, bit});
|
||||
state->builder.AddFunction({OpINotEqual, state->bool_type, active, hit, ConstantU32(state, 0)});
|
||||
state->builder.AddFunction(
|
||||
{OpULessThan, state->bool_type, in_range, lane, ConstantU32(state, state->wave_size)});
|
||||
state->builder.AddFunction({OpLogicalAnd, state->bool_type, ret, active, in_range});
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitSequentialValueLoad(EmitterState* state, const IR::Operand& operand, uint32_t offset) {
|
||||
if (offset == 0 || operand.kind == IR::OperandKind::Register) {
|
||||
return EmitValueLoad(state, OffsetRegisterOperand(operand, offset));
|
||||
}
|
||||
if (operand.kind == IR::OperandKind::ImmediateU32 && offset == 1) {
|
||||
return ConstantU32(state, operand.sext_64 ? 0xffffffffu : 0u);
|
||||
}
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
|
||||
uint32_t EmitSequentialFloatLoad(EmitterState* state, const IR::Operand& operand, uint32_t offset) {
|
||||
const auto value = EmitSequentialValueLoad(state, operand, offset);
|
||||
const auto f32 = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitcast, state->float_type, f32, value});
|
||||
return f32;
|
||||
}
|
||||
|
||||
bool ImageAddressUsesA16(const IR::Instruction& inst) {
|
||||
return (inst.memory.image_sample_flags & Decoder::ImageSampleFlagA16) != 0u;
|
||||
}
|
||||
|
||||
bool ImageAddressComponentUsesA16(const IR::Instruction& inst, uint32_t component) {
|
||||
if (!ImageAddressUsesA16(inst)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t cursor = 0;
|
||||
if ((inst.memory.image_sample_flags & Decoder::ImageSampleFlagOffset) != 0u) {
|
||||
if (component == cursor) {
|
||||
return false;
|
||||
}
|
||||
cursor++;
|
||||
}
|
||||
if ((inst.memory.image_sample_flags & Decoder::ImageSampleFlagBias) != 0u) {
|
||||
if (component == cursor) {
|
||||
return true;
|
||||
}
|
||||
cursor++;
|
||||
}
|
||||
if ((inst.memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0u &&
|
||||
component == cursor) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t ImageAddressHalfComponent(const IR::Instruction& inst, uint32_t component) {
|
||||
if (!ImageAddressUsesA16(inst)) {
|
||||
return component * 2u;
|
||||
}
|
||||
|
||||
uint32_t half_component = 0;
|
||||
for (uint32_t i = 0; i < component; i++) {
|
||||
const auto half_width = ImageAddressComponentUsesA16(inst, i) ? 1u : 2u;
|
||||
if (half_width == 2u && (half_component & 1u) != 0u) {
|
||||
half_component++;
|
||||
}
|
||||
half_component += half_width;
|
||||
}
|
||||
if (!ImageAddressComponentUsesA16(inst, component) && (half_component & 1u) != 0u) {
|
||||
half_component++;
|
||||
}
|
||||
return half_component;
|
||||
}
|
||||
|
||||
uint32_t ImageAddressPackedComponent(const IR::Instruction& inst, uint32_t component) {
|
||||
return ImageAddressHalfComponent(inst, component) / 2u;
|
||||
}
|
||||
|
||||
IR::Operand ImageAddressOperand(const IR::Instruction& inst, const IR::Operand& base,
|
||||
uint32_t component) {
|
||||
const auto packed_component = ImageAddressPackedComponent(inst, component);
|
||||
if (packed_component > 0 && inst.memory.image_nsa_dwords != 0) {
|
||||
const auto nsa_index = packed_component - 1u;
|
||||
const auto nsa_count = std::min<uint32_t>(inst.memory.image_nsa_dwords * 4u,
|
||||
Decoder::MaxImageNsaAddressComponents);
|
||||
if (nsa_index < nsa_count) {
|
||||
return MakeRegisterOperand(IR::RegisterFile::Vector,
|
||||
inst.memory.image_nsa_addr[nsa_index]);
|
||||
}
|
||||
}
|
||||
return OffsetRegisterOperand(base, packed_component);
|
||||
}
|
||||
|
||||
uint32_t EmitImageAddressValueLoad(EmitterState* state, const IR::Instruction& inst,
|
||||
const IR::Operand& base, uint32_t component) {
|
||||
const auto operand = ImageAddressOperand(inst, base, component);
|
||||
const auto packed_component = ImageAddressPackedComponent(inst, component);
|
||||
if (packed_component == 0 || operand.kind == IR::OperandKind::Register) {
|
||||
auto value = EmitValueLoad(state, operand);
|
||||
if (ImageAddressComponentUsesA16(inst, component)) {
|
||||
if ((ImageAddressHalfComponent(inst, component) & 1u) != 0u) {
|
||||
value = EmitShiftRightConstant(state, value, 16u);
|
||||
}
|
||||
value = EmitAndConstant(state, value, 0xffffu);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return ConstantU32(state, 0);
|
||||
}
|
||||
|
||||
uint32_t EmitImageAddressFloatLoad(EmitterState* state, const IR::Instruction& inst,
|
||||
const IR::Operand& base, uint32_t component) {
|
||||
const auto value = EmitImageAddressValueLoad(state, inst, base, component);
|
||||
if (ImageAddressComponentUsesA16(inst, component)) {
|
||||
return EmitF16BitsToF32(state, value);
|
||||
}
|
||||
const auto f32 = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitcast, state->float_type, f32, value});
|
||||
return f32;
|
||||
}
|
||||
|
||||
uint32_t EmitZeroF32(EmitterState* state) {
|
||||
const auto f32 = state->builder.AllocateId();
|
||||
state->builder.AddFunction({OpBitcast, state->float_type, f32, ConstantU32(state, 0)});
|
||||
return f32;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
|
||||
Reference in new issue
Block a user