mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-24 04:46:56 +02:00
refactor(graphics): split renderer and recompiler into focused modules
This commit is contained in:
@@ -0,0 +1,306 @@
|
||||
#include "graphics/shader/recompiler/ir/BindingLayout.h"
|
||||
|
||||
#include "graphics/shader/recompiler/ir/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::Sampled1D, DescriptorBindingKind::Sampled1DArray,
|
||||
DescriptorBindingKind::Sampled2D, DescriptorBindingKind::Sampled2DArray,
|
||||
DescriptorBindingKind::Sampled3D, DescriptorBindingKind::SampledUint1D,
|
||||
DescriptorBindingKind::SampledUint1DArray, DescriptorBindingKind::SampledUint2D,
|
||||
DescriptorBindingKind::SampledUint2DArray, DescriptorBindingKind::SampledUint3D,
|
||||
DescriptorBindingKind::Storage1D, DescriptorBindingKind::Storage1DArray,
|
||||
DescriptorBindingKind::Storage2D, DescriptorBindingKind::Storage2DArray,
|
||||
DescriptorBindingKind::Storage3D, DescriptorBindingKind::StorageUint1D,
|
||||
DescriptorBindingKind::StorageUint1DArray, 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::Dim1D: result = Kind::Sampled1D; return true;
|
||||
case Dimension::Dim1DArray: result = Kind::Sampled1DArray; return true;
|
||||
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::Dim1D: result = Kind::SampledUint1D; return true;
|
||||
case Dimension::Dim1DArray: result = Kind::SampledUint1DArray; return true;
|
||||
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::Dim1D: result = Kind::Storage1D; return true;
|
||||
case Dimension::Dim1DArray: result = Kind::Storage1DArray; return true;
|
||||
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::Dim1D: result = Kind::StorageUint1D; return true;
|
||||
case Dimension::Dim1DArray: result = Kind::StorageUint1DArray; return true;
|
||||
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, registers)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto& image: program.info.images) {
|
||||
if (!CollectSource(program, image.source, false, visited, registers)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto& sampler: program.info.samplers) {
|
||||
if (!CollectSource(program, sampler.source, false, visited, registers)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto& address: program.info.addresses) {
|
||||
if (!CollectSource(program, address.source, true, visited, registers)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto& read: program.srt.reads) {
|
||||
if (!CollectValue(program.provenance, read.value, visited, registers)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto read: program.srt.dynamic_reads) {
|
||||
if (!CollectValue(program.provenance, read, visited, registers)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto source: program.srt.dynamic_sources) {
|
||||
if (!CollectSource(program, source, true, visited, registers)) {
|
||||
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, registers)) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < inst.src_count; i++) {
|
||||
if (!CollectValue(program.provenance, inst.scalar_sources[i], visited,
|
||||
registers)) {
|
||||
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.shader_info_complete || program.binding_layout_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = !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;
|
||||
}
|
||||
next.buffer_offset_dword = static_cast<uint32_t>(next.user_data_registers.size());
|
||||
next.buffer_offset_count = static_cast<uint32_t>(program.info.buffers.size());
|
||||
|
||||
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.ShaderDataDwords() <= push_limit) {
|
||||
next.push_constant_size = next.ShaderDataDwords() * sizeof(uint32_t);
|
||||
} 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/ir/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,434 @@
|
||||
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
|
||||
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
#include <functional>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
namespace {
|
||||
|
||||
constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
|
||||
|
||||
Decoder::ImageDimension DescriptorDimension(const DescriptorValue& descriptor,
|
||||
Decoder::ImageDimension requested) {
|
||||
const bool is_array = requested == Decoder::ImageDimension::Dim1DArray ||
|
||||
requested == Decoder::ImageDimension::Dim2DArray;
|
||||
switch (static_cast<Prospero::ImageType>((descriptor.dwords[3] >> 28u) & 0xfu)) {
|
||||
case Prospero::ImageType::kColor1D: return Decoder::ImageDimension::Dim1D;
|
||||
case Prospero::ImageType::kColor1DArray:
|
||||
if (is_array) {
|
||||
return Decoder::ImageDimension::Dim1DArray;
|
||||
}
|
||||
return Decoder::ImageDimension::Dim1D;
|
||||
case Prospero::ImageType::kColor3D: return Decoder::ImageDimension::Dim3D;
|
||||
case Prospero::ImageType::kCube: return Decoder::ImageDimension::Dim2DArray;
|
||||
case Prospero::ImageType::kColor2DArray:
|
||||
case Prospero::ImageType::kColor2DMsaaArray:
|
||||
if (is_array) {
|
||||
return Decoder::ImageDimension::Dim2DArray;
|
||||
}
|
||||
return Decoder::ImageDimension::Dim2D;
|
||||
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;
|
||||
}
|
||||
|
||||
bool ValidImageDescriptor(const DescriptorValue& descriptor) {
|
||||
const auto type = static_cast<Prospero::ImageType>((descriptor.dwords[3] >> 28u) & 0xfu);
|
||||
if (type < Prospero::ImageType::kColor1D) {
|
||||
return false;
|
||||
}
|
||||
if (type == Prospero::ImageType::kColor2DMsaa ||
|
||||
type == Prospero::ImageType::kColor2DMsaaArray) {
|
||||
const auto base_level = (descriptor.dwords[3] >> 12u) & 0xfu;
|
||||
const auto fragments = (descriptor.dwords[3] >> 16u) & 0xfu;
|
||||
const auto max_mip = (descriptor.dwords[5] >> 4u) & 0xfu;
|
||||
return base_level == 0 && fragments >= 1 && fragments <= 3 &&
|
||||
max_mip == fragments;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t DescriptorImageSwizzle(const DescriptorValue& descriptor) {
|
||||
return descriptor.dwords[3] & 0xfffu;
|
||||
}
|
||||
|
||||
bool DecodeBufferDescriptor(const DescriptorValue& descriptor, ShaderBufferResource& result) {
|
||||
if (descriptor.dword_count != std::size(result.fields)) {
|
||||
return false;
|
||||
}
|
||||
std::copy_n(descriptor.dwords.begin(), std::size(result.fields), result.fields);
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
const auto alias = program.info.buffers[i].image_alias;
|
||||
if (alias != BufferResource::NoImageAlias && alias >= program.info.images.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("buffer resource {} has invalid image alias {}", i, alias);
|
||||
}
|
||||
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];
|
||||
ShaderBufferResource descriptor;
|
||||
if (!DecodeBufferDescriptor(snapshot.buffers[i], descriptor)) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("buffer descriptor {} has invalid width", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (buffer.packed_stride != descriptor.PackedStride() ||
|
||||
buffer.descriptor_format != descriptor.Format()) {
|
||||
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)) {
|
||||
bool canonical_kind = image.kind == ResourceKind::Image ||
|
||||
image.kind == ResourceKind::StorageImage;
|
||||
if (image.atomic) {
|
||||
canonical_kind = image.kind == ResourceKind::StorageImageUint;
|
||||
}
|
||||
if (image.dimension != Decoder::ImageDimension::Dim2D || !canonical_kind) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format(
|
||||
"image descriptor {} no longer matches canonical null specialization", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const auto dimension = DescriptorDimension(descriptor, image.dimension);
|
||||
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 (!program.resource_tracking_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = "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();
|
||||
for (auto& descriptor: next.buffers) {
|
||||
ShaderBufferResource buffer;
|
||||
if (DecodeBufferDescriptor(descriptor, buffer) && buffer.Type() != 0) {
|
||||
descriptor.dwords.fill(0);
|
||||
}
|
||||
}
|
||||
next.images.assign(cursor, cursor + program.info.images.size());
|
||||
cursor += program.info.images.size();
|
||||
for (auto& descriptor: next.images) {
|
||||
if (!ValidImageDescriptor(descriptor)) {
|
||||
descriptor.dwords.fill(0);
|
||||
}
|
||||
}
|
||||
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));
|
||||
uint64_t binding_base = 0;
|
||||
if (address.kind == ResourceKind::Flat) {
|
||||
binding_base = base & ~(FlatAddressWindowSize - 1u);
|
||||
} else if (base >= before) {
|
||||
binding_base = base - before;
|
||||
}
|
||||
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.resource_tracking_complete || program.shader_info_complete ||
|
||||
program.binding_layout_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = !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++) {
|
||||
ShaderBufferResource descriptor;
|
||||
if (!DecodeBufferDescriptor(snapshot.buffers[i], descriptor)) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("buffer descriptor {} has invalid width", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
next.buffers[i].packed_stride = descriptor.PackedStride();
|
||||
next.buffers[i].descriptor_format = descriptor.Format();
|
||||
}
|
||||
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)) {
|
||||
image.dimension = Decoder::ImageDimension::Dim2D;
|
||||
switch (image.kind) {
|
||||
case ResourceKind::ImageUint: image.kind = ResourceKind::Image; break;
|
||||
case ResourceKind::StorageImageUint:
|
||||
if (!image.atomic) {
|
||||
image.kind = ResourceKind::StorageImage;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const auto descriptor_dimension = DescriptorDimension(descriptor, image.dimension);
|
||||
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 {
|
||||
std::reference_wrapper<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({std::ref(inst), image.kind, image.dimension});
|
||||
}
|
||||
}
|
||||
program.info = std::move(next);
|
||||
for (const auto& patch: patches) {
|
||||
patch.inst.get().memory.kind = patch.kind;
|
||||
patch.inst.get().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/ir/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,462 @@
|
||||
#include "graphics/shader/recompiler/ir/ResourceTracking.h"
|
||||
|
||||
#include "graphics/shader/recompiler/ir/ScalarProvenance.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
#include <functional>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
LinkImageAliases();
|
||||
m_program.info = std::move(m_info);
|
||||
for (const auto& patch: m_patches) {
|
||||
auto& inst = patch.inst.get();
|
||||
inst.memory.resource = patch.resource;
|
||||
inst.memory.sampler = patch.sampler;
|
||||
inst.memory.resource_source = ScalarProvenance::Undefined;
|
||||
inst.memory.sampler_source = ScalarProvenance::Undefined;
|
||||
}
|
||||
m_program.resource_tracking_complete = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Patch {
|
||||
std::reference_wrapper<Instruction> inst;
|
||||
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;
|
||||
}
|
||||
|
||||
void LinkImageAliases() {
|
||||
for (auto& buffer: m_info.buffers) {
|
||||
const auto* buffer_source = GetDescriptorSource(m_program, buffer.source);
|
||||
if (buffer_source == nullptr || buffer_source->dword_count != 4) {
|
||||
continue;
|
||||
}
|
||||
for (uint32_t i = 0; i < m_info.images.size(); i++) {
|
||||
const auto* image_source = GetDescriptorSource(m_program, m_info.images[i].source);
|
||||
if (image_source != nullptr && image_source->dword_count == 8 &&
|
||||
std::equal(buffer_source->dwords.begin(), buffer_source->dwords.begin() + 4,
|
||||
image_source->dwords.begin())) {
|
||||
buffer.image_alias = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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({std::ref(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;
|
||||
}
|
||||
const auto resource = IsBuffer(inst) ? AddBuffer(inst) : AddImage(inst);
|
||||
if (resource == UINT32_MAX) {
|
||||
return Fail(inst.pc, error,
|
||||
IsBuffer(inst) ? "buffer resource limit exceeded"
|
||||
: "image resource limit exceeded");
|
||||
}
|
||||
uint32_t sampler = 0;
|
||||
if (NeedsSampler(inst.op)) {
|
||||
if (!ValidateSource(inst.memory.sampler_source, 4, inst.pc, error)) {
|
||||
return false;
|
||||
}
|
||||
sampler = AddSampler(inst);
|
||||
if (sampler == UINT32_MAX) {
|
||||
return Fail(inst.pc, error, "sampler resource limit exceeded");
|
||||
}
|
||||
if (!AddSampledPair(resource, sampler, inst.pc, error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
m_patches.push_back({std::ref(inst), resource, sampler});
|
||||
return true;
|
||||
}
|
||||
|
||||
Program& m_program;
|
||||
ShaderInfo m_info;
|
||||
std::vector<Patch> m_patches;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool TrackResources(Program& program, std::string* error) {
|
||||
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/ir/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,943 @@
|
||||
#include "graphics/shader/recompiler/ir/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) {
|
||||
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 = ScalarProvenance::Unknown;
|
||||
if (inst.op == Opcode::MoveU32 && inst.src_count != 0) {
|
||||
state.m0 = OperandValue(inst.src[0], before);
|
||||
} else if (op != ScalarValueOp::Unknown) {
|
||||
state.m0 = Define(inst, op, before);
|
||||
}
|
||||
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:
|
||||
if (inst.src_count != 0) {
|
||||
if (inst.src[0].kind == OperandKind::PcRelativeU32) {
|
||||
value =
|
||||
InternValue({ScalarValueOp::PcRelativeLow, inst.pc, inst.src[0].imm});
|
||||
} else {
|
||||
value = 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.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/ir/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,781 @@
|
||||
#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/cfg/ShaderCFG.h"
|
||||
#include "graphics/shader/recompiler/decompiler/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 {
|
||||
static constexpr uint32_t NoImageAlias = UINT32_MAX;
|
||||
|
||||
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;
|
||||
uint32_t image_alias = NoImageAlias;
|
||||
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,
|
||||
Sampled1D,
|
||||
Sampled1DArray,
|
||||
Sampled2D,
|
||||
Sampled2DArray,
|
||||
Sampled3D,
|
||||
SampledUint1D,
|
||||
SampledUint1DArray,
|
||||
SampledUint2D,
|
||||
SampledUint2DArray,
|
||||
SampledUint3D,
|
||||
Storage1D,
|
||||
Storage1DArray,
|
||||
Storage2D,
|
||||
Storage2DArray,
|
||||
Storage3D,
|
||||
StorageUint1D,
|
||||
StorageUint1DArray,
|
||||
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;
|
||||
uint32_t buffer_offset_dword = 0;
|
||||
uint32_t buffer_offset_count = 0;
|
||||
std::vector<uint32_t> user_data_registers;
|
||||
std::vector<DescriptorBinding> descriptors;
|
||||
|
||||
[[nodiscard]] uint32_t ShaderDataDwords() const {
|
||||
return buffer_offset_dword + (buffer_offset_count + 3u) / 4u;
|
||||
}
|
||||
|
||||
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 has_bitwise_xor = false;
|
||||
|
||||
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,55 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_SHADER_RECOMPILER_SHADERIR_SHADERIRINTERNAL_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_SHADER_RECOMPILER_SHADERIR_SHADERIRINTERNAL_H_
|
||||
|
||||
#include "graphics/shader/recompiler/ir/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,549 @@
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIRInternal.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";
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
Decoder::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,817 @@
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIRInternal.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
namespace {
|
||||
|
||||
Opcode BufferLoadOpcode(const Decoder::Instruction& decoded) {
|
||||
switch (decoded.data_bits) {
|
||||
case 8u: return decoded.data_signed ? Opcode::BufferLoadSbyte : Opcode::BufferLoadUbyte;
|
||||
case 16u: return decoded.data_signed ? Opcode::BufferLoadSshort : Opcode::BufferLoadUshort;
|
||||
default: return Opcode::BufferLoadDword;
|
||||
}
|
||||
}
|
||||
|
||||
Opcode BufferStoreOpcode(uint32_t data_bits) {
|
||||
switch (data_bits) {
|
||||
case 8u: return Opcode::BufferStoreByte;
|
||||
case 16u: return Opcode::BufferStoreShort;
|
||||
default: return Opcode::BufferStoreDword;
|
||||
}
|
||||
}
|
||||
|
||||
Opcode DsReadOpcode(const Decoder::Instruction& decoded) {
|
||||
switch (decoded.data_bits) {
|
||||
case 8u: return decoded.data_signed ? Opcode::DsReadSbyte : Opcode::DsReadUbyte;
|
||||
case 16u: return decoded.data_signed ? Opcode::DsReadSshort : Opcode::DsReadUshort;
|
||||
default: return Opcode::DsReadB32;
|
||||
}
|
||||
}
|
||||
|
||||
Opcode FlatLoadOpcode(const Decoder::Instruction& decoded) {
|
||||
switch (decoded.data_bits) {
|
||||
case 8u: return decoded.data_signed ? Opcode::FlatLoadSbyte : Opcode::FlatLoadUbyte;
|
||||
case 16u: return decoded.data_signed ? Opcode::FlatLoadSshort : Opcode::FlatLoadUshort;
|
||||
default: return Opcode::FlatLoadDword;
|
||||
}
|
||||
}
|
||||
|
||||
Opcode FlatStoreOpcode(uint32_t data_bits) {
|
||||
switch (data_bits) {
|
||||
case 8u: return Opcode::FlatStoreByte;
|
||||
case 16u: return Opcode::FlatStoreShort;
|
||||
default: return Opcode::FlatStoreDword;
|
||||
}
|
||||
}
|
||||
|
||||
Opcode DsWriteOpcode(uint32_t data_bits) {
|
||||
switch (data_bits) {
|
||||
case 8u: return Opcode::DsWriteByte;
|
||||
case 16u: return Opcode::DsWriteShort;
|
||||
default: return Opcode::DsWriteB32;
|
||||
}
|
||||
}
|
||||
|
||||
Opcode LoweredImageOpcode(Decoder::Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Decoder::Opcode::ImageGetResinfo: return Opcode::ImageGetResinfo;
|
||||
case Decoder::Opcode::ImageGetLod: return Opcode::ImageGetLod;
|
||||
case Decoder::Opcode::ImageLoad:
|
||||
case Decoder::Opcode::ImageLoadMip: return Opcode::ImageLoad;
|
||||
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: return Opcode::ImageGather4;
|
||||
default: return Opcode::ImageSample;
|
||||
}
|
||||
}
|
||||
|
||||
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 = BufferLoadOpcode(decoded);
|
||||
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 = BufferStoreOpcode(decoded.data_bits);
|
||||
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 = DsReadOpcode(decoded);
|
||||
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 = FlatLoadOpcode(decoded);
|
||||
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 = FlatStoreOpcode(decoded.data_bits);
|
||||
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 = DsWriteOpcode(decoded.data_bits);
|
||||
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 = LoweredImageOpcode(decoded.opcode);
|
||||
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,532 @@
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIRInternal.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
|
||||
@@ -0,0 +1,235 @@
|
||||
#include "graphics/shader/recompiler/ir/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.resource_tracking_complete || program.shader_info_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = !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();
|
||||
next.has_bitwise_xor =
|
||||
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) {
|
||||
switch (inst.op) {
|
||||
case Opcode::BitwiseXorU32:
|
||||
case Opcode::BitwiseXor3U32:
|
||||
case Opcode::XorAddU32: return true;
|
||||
default: return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
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/ir/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,138 @@
|
||||
#include "graphics/shader/recompiler/ir/SrtPatcher.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
bool PatchSrtReads(Program& program, std::string* error) {
|
||||
if (!program.srt_plan_complete || program.srt_patching_complete ||
|
||||
program.resource_tracking_complete) {
|
||||
if (error != nullptr) {
|
||||
if (!program.srt_plan_complete) {
|
||||
*error = "SRT plan is not ready";
|
||||
} else if (program.srt_patching_complete) {
|
||||
*error = "SRT reads already patched";
|
||||
} else {
|
||||
*error = "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 {
|
||||
std::reference_wrapper<Instruction> inst;
|
||||
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({std::ref(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) {
|
||||
auto& inst = patch.inst.get();
|
||||
inst.op = Opcode::LoadSrtDword;
|
||||
inst.src_count = 1;
|
||||
for (auto& src: inst.src) {
|
||||
src = {};
|
||||
}
|
||||
inst.src[0].kind = OperandKind::ImmediateU32;
|
||||
inst.src[0].imm = patch.flat_offset;
|
||||
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/ir/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,625 @@
|
||||
#include "graphics/shader/recompiler/ir/SrtWalker.h"
|
||||
|
||||
#include "graphics/shader/recompiler/ir/ScalarProvenance.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#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 (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 std::array<uint32_t, 3>& args, uint32_t& result) {
|
||||
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);
|
||||
}
|
||||
std::array<uint32_t, 3> args {};
|
||||
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)));
|
||||
}
|
||||
std::array<uint32_t, 3> args {};
|
||||
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));
|
||||
}
|
||||
}
|
||||
if (m_runtime.read_memory != nullptr &&
|
||||
!m_runtime.read_memory(m_runtime.userdata, address, &result)) {
|
||||
return Fail(
|
||||
error, fmt::format("ReadConst pc=0x{:08x} failed at 0x{:016x}", value.pc, address));
|
||||
}
|
||||
if (m_runtime.read_memory == nullptr) {
|
||||
std::memcpy(&result, reinterpret_cast<const void*>(address), sizeof(result));
|
||||
}
|
||||
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) {
|
||||
ConstantFolder folder(provenance);
|
||||
return folder.Fold(value, result);
|
||||
}
|
||||
|
||||
bool BuildSrtPlan(Program& program, std::string* error) {
|
||||
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) {
|
||||
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 (!program.srt_plan_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = Diagnostic(program, 0, "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) {
|
||||
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) {
|
||||
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/ir/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_ */
|
||||
Reference in New Issue
Block a user