Replacing old Shader with TranslatedShader.

This commit is contained in:
Ben Vanik 2015-12-06 10:36:07 -08:00
parent 083f936bbc
commit c86e479214
14 changed files with 693 additions and 727 deletions

View File

@ -491,12 +491,21 @@ Shader* GL4CommandProcessor::LoadShader(ShaderType shader_type,
// Perform translation.
// If this fails the shader will be marked as invalid and ignored later.
shader_ptr->Prepare(&shader_translator_);
if (shader_translator_.Translate(shader_ptr)) {
shader_ptr->Prepare();
// Dump shader files if desired.
if (!FLAGS_dump_shaders.empty()) {
shader_ptr->Dump(FLAGS_dump_shaders, "gl4");
}
} else {
XELOGE("Shader failed translation");
}
XELOGGPU("Set %s shader at %0.8X (%db):\n%s",
shader_type == ShaderType::kVertex ? "vertex" : "pixel",
guest_address, dword_count * 4,
shader_ptr->translated_shader()->ucode_disassembly().c_str());
shader_ptr->ucode_disassembly().c_str());
}
return shader_ptr;
}
@ -779,7 +788,6 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::UpdateRenderTargets() {
// Note that write mask may be more permissive than we want, so we mix that
// with the actual targets the pixel shader writes to.
GLenum draw_buffers[4] = {GL_NONE, GL_NONE, GL_NONE, GL_NONE};
auto pixel_shader = active_pixel_shader_->translated_shader();
GLuint color_targets[4] = {kAnyTarget, kAnyTarget, kAnyTarget, kAnyTarget};
if (enable_mode == ModeControl::kColorDepth) {
uint32_t color_info[4] = {
@ -789,7 +797,7 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::UpdateRenderTargets() {
// A2XX_RB_COLOR_MASK_WRITE_* == D3DRS_COLORWRITEENABLE
for (int n = 0; n < xe::countof(color_info); n++) {
uint32_t write_mask = (regs.rb_color_mask >> (n * 4)) & 0xF;
if (!write_mask || !pixel_shader->writes_color_target(n)) {
if (!write_mask || !active_pixel_shader_->writes_color_target(n)) {
// Unused, so keep disabled and set to wildcard so we'll take any
// framebuffer that has it.
continue;
@ -1362,9 +1370,7 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::PopulateVertexBuffers() {
auto& regs = *register_file_;
assert_not_null(active_vertex_shader_);
const auto& vertex_bindings =
active_vertex_shader_->translated_shader()->vertex_bindings();
for (const auto& vertex_binding : vertex_bindings) {
for (const auto& vertex_binding : active_vertex_shader_->vertex_bindings()) {
int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 +
(vertex_binding.fetch_constant / 3) * 6;
const auto group = reinterpret_cast<xe_gpu_fetch_group_t*>(&regs.values[r]);
@ -1434,9 +1440,7 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::PopulateSamplers() {
bool has_setup_sampler[32] = {false};
// Vertex texture samplers.
const auto& vertex_sampler_inputs =
active_vertex_shader_->translated_shader()->texture_bindings();
for (auto& texture_binding : vertex_sampler_inputs) {
for (auto& texture_binding : active_vertex_shader_->texture_bindings()) {
if (has_setup_sampler[texture_binding.fetch_constant]) {
continue;
}
@ -1450,9 +1454,7 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::PopulateSamplers() {
}
// Pixel shader texture sampler.
const auto& pixel_sampler_inputs =
active_pixel_shader_->translated_shader()->texture_bindings();
for (auto& texture_binding : pixel_sampler_inputs) {
for (auto& texture_binding : active_pixel_shader_->texture_bindings()) {
if (has_setup_sampler[texture_binding.fetch_constant]) {
continue;
}
@ -1469,7 +1471,7 @@ GL4CommandProcessor::UpdateStatus GL4CommandProcessor::PopulateSamplers() {
}
GL4CommandProcessor::UpdateStatus GL4CommandProcessor::PopulateSampler(
const TranslatedShader::TextureBinding& texture_binding) {
const Shader::TextureBinding& texture_binding) {
auto& regs = *register_file_;
int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 +
texture_binding.fetch_constant * 6;

View File

@ -123,8 +123,7 @@ class GL4CommandProcessor : public CommandProcessor {
UpdateStatus PopulateIndexBuffer(IndexBufferInfo* index_buffer_info);
UpdateStatus PopulateVertexBuffers();
UpdateStatus PopulateSamplers();
UpdateStatus PopulateSampler(
const TranslatedShader::TextureBinding& texture_binding);
UpdateStatus PopulateSampler(const Shader::TextureBinding& texture_binding);
bool IssueCopy() override;
CachedFramebuffer* GetFramebuffer(GLuint color_targets[4],

View File

@ -9,11 +9,8 @@
#include "xenia/gpu/gl4/gl4_shader.h"
#include "xenia/base/filesystem.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/gpu/gl4/gl4_gpu_flags.h"
#include "xenia/gpu/gpu_flags.h"
namespace xe {
namespace gpu {
@ -30,8 +27,8 @@ GL4Shader::~GL4Shader() {
glDeleteVertexArrays(1, &vao_);
}
bool GL4Shader::Prepare(ShaderTranslator* shader_translator) {
if (!Shader::Prepare(shader_translator)) {
bool GL4Shader::Prepare() {
if (!Shader::Prepare()) {
return false;
}
@ -51,7 +48,7 @@ bool GL4Shader::Prepare(ShaderTranslator* shader_translator) {
bool GL4Shader::PrepareVertexArrayObject() {
glCreateVertexArrays(1, &vao_);
for (const auto& vertex_binding : translated_shader_->vertex_bindings()) {
for (const auto& vertex_binding : vertex_bindings()) {
for (const auto& attrib : vertex_binding.attributes) {
auto comp_count = GetVertexFormatComponentCount(
attrib.fetch_instr.attributes.data_format);
@ -126,43 +123,8 @@ bool GL4Shader::PrepareVertexArrayObject() {
bool GL4Shader::CompileProgram() {
assert_zero(program_);
auto source_str = translated_shader_->GetBinaryString();
// Save to disk, if we asked for it.
auto base_path = FLAGS_dump_shaders.c_str();
char file_name[kMaxPath];
snprintf(file_name, xe::countof(file_name), "%s/gl4_gen_%.16llX.%s",
base_path, data_hash_,
shader_type_ == ShaderType::kVertex ? "vert" : "frag");
if (!FLAGS_dump_shaders.empty()) {
// Ensure shader dump path exists.
auto dump_shaders_path = xe::to_wstring(FLAGS_dump_shaders);
if (!dump_shaders_path.empty()) {
dump_shaders_path = xe::to_absolute_path(dump_shaders_path);
xe::filesystem::CreateFolder(dump_shaders_path);
}
char bin_file_name[kMaxPath];
snprintf(bin_file_name, xe::countof(file_name), "%s/gl4_gen_%.16llX.bin.%s",
base_path, data_hash_,
shader_type_ == ShaderType::kVertex ? "vert" : "frag");
FILE* f = fopen(bin_file_name, "w");
if (f) {
fwrite(data_.data(), 4, data_.size(), f);
fclose(f);
}
// Note that we put the translated source first so we get good line numbers.
f = fopen(file_name, "w");
if (f) {
fprintf(f, "%s", source_str.c_str());
fprintf(f, "/*\n");
fprintf(f, "%s", translated_shader_->ucode_disassembly().c_str());
fprintf(f, " */\n");
fclose(f);
}
}
// Give source to GL.
auto source_str = GetTranslatedBinaryString();
auto source_str_ptr = source_str.c_str();
program_ = glCreateShaderProgramv(shader_type_ == ShaderType::kVertex
? GL_VERTEX_SHADER
@ -185,7 +147,7 @@ bool GL4Shader::CompileProgram() {
glGetProgramInfoLog(program_, log_length, &log_length,
const_cast<char*>(info_log.data()));
XELOGE("Unable to link program: %s", info_log.c_str());
error_log_ = std::move(info_log);
host_error_log_ = std::move(info_log);
assert_always("Unable to link generated shader");
return false;
}
@ -194,21 +156,21 @@ bool GL4Shader::CompileProgram() {
GLint binary_length = 0;
glGetProgramiv(program_, GL_PROGRAM_BINARY_LENGTH, &binary_length);
if (binary_length) {
translated_binary_.resize(binary_length);
host_binary_.resize(binary_length);
GLenum binary_format;
glGetProgramBinary(program_, binary_length, &binary_length, &binary_format,
translated_binary_.data());
host_binary_.data());
// If we are on nvidia, we can find the disassembly string.
// I haven't been able to figure out from the format how to do this
// without a search like this.
const char* disasm_start = nullptr;
size_t search_offset = 0;
char* search_start = reinterpret_cast<char*>(translated_binary_.data());
char* search_start = reinterpret_cast<char*>(host_binary_.data());
while (true) {
auto p = reinterpret_cast<char*>(
memchr(translated_binary_.data() + search_offset, '!',
translated_binary_.size() - search_offset));
memchr(host_binary_.data() + search_offset, '!',
host_binary_.size() - search_offset));
if (!p) {
break;
}
@ -224,19 +186,6 @@ bool GL4Shader::CompileProgram() {
} else {
host_disassembly_ = std::string("Shader disassembly not available.");
}
// Append to shader dump.
if (!FLAGS_dump_shaders.empty()) {
if (disasm_start) {
FILE* f = fopen(file_name, "a");
fprintf(f, "\n\n/*\n");
fprintf(f, "%s", disasm_start);
fprintf(f, "\n*/\n");
fclose(f);
} else {
XELOGW("Got program binary but unable to find disassembly");
}
}
}
return true;

View File

@ -13,7 +13,6 @@
#include <string>
#include "xenia/gpu/shader.h"
#include "xenia/gpu/shader_translator.h"
#include "xenia/ui/gl/gl_context.h"
namespace xe {
@ -29,7 +28,7 @@ class GL4Shader : public Shader {
GLuint program() const { return program_; }
GLuint vao() const { return vao_; }
bool Prepare(ShaderTranslator* shader_translator);
bool Prepare() override;
protected:
bool PrepareVertexArrayObject();

View File

@ -90,8 +90,7 @@ void GlslShaderTranslator::StartTranslation() {
// Tons of boilerplate for shaders, here.
// We have a large amount of shared state defining uniforms and some common
// utility functions used in both vertex and pixel shaders.
EmitSource(R"(
#version 450
EmitSource(R"(#version 450
#extension all : warn
#extension GL_ARB_bindless_texture : require
#extension GL_ARB_explicit_uniform_location : require

View File

@ -10,7 +10,7 @@
#ifndef XENIA_GPU_SAMPLER_INFO_H_
#define XENIA_GPU_SAMPLER_INFO_H_
#include "xenia/gpu/shader_translator.h"
#include "xenia/gpu/shader.h"
#include "xenia/gpu/xenos.h"
namespace xe {

View File

@ -2,38 +2,83 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/gpu/shader.h"
#include "xenia/base/logging.h"
#include <cstring>
#include "xenia/base/filesystem.h"
#include "xenia/base/math.h"
#include "xenia/base/memory.h"
#include "xenia/base/string.h"
namespace xe {
namespace gpu {
Shader::Shader(ShaderType shader_type, uint64_t data_hash,
const uint32_t* dword_ptr, uint32_t dword_count)
: shader_type_(shader_type), data_hash_(data_hash) {
data_.resize(dword_count);
xe::copy_and_swap(data_.data(), dword_ptr, dword_count);
Shader::Shader(ShaderType shader_type, uint64_t ucode_data_hash,
const uint32_t* ucode_dwords, size_t ucode_dword_count)
: shader_type_(shader_type), ucode_data_hash_(ucode_data_hash) {
// We keep ucode data in host native format so it's easier to work with.
ucode_data_.resize(ucode_dword_count);
xe::copy_and_swap(ucode_data_.data(), ucode_dwords, ucode_dword_count);
}
Shader::~Shader() = default;
bool Shader::Prepare(ShaderTranslator* shader_translator) {
// Perform translation.
translated_shader_ = shader_translator->Translate(shader_type_, data_hash_,
data_.data(), data_.size());
if (!translated_shader_) {
XELOGE("Shader failed translation");
return false;
std::string Shader::GetTranslatedBinaryString() const {
std::string result;
result.resize(translated_binary_.size());
std::memcpy(const_cast<char*>(result.data()), translated_binary_.data(),
translated_binary_.size());
return result;
}
void Shader::Dump(const std::string& base_path, const char* path_prefix) {
// Ensure target path exists.
auto target_path = xe::to_wstring(base_path);
if (!target_path.empty()) {
target_path = xe::to_absolute_path(target_path);
xe::filesystem::CreateFolder(target_path);
}
return true;
char txt_file_name[kMaxPath];
std::snprintf(txt_file_name, xe::countof(txt_file_name),
"%s/shader_%s_%.16llX.%s", base_path.c_str(), path_prefix,
ucode_data_hash_,
shader_type_ == ShaderType::kVertex ? "vert" : "frag");
FILE* f = fopen(txt_file_name, "w");
if (f) {
fwrite(translated_binary_.data(), 1, translated_binary_.size(), f);
fprintf(f, "\n\n");
auto ucode_disasm_ptr = ucode_disassembly().c_str();
while (*ucode_disasm_ptr) {
auto line_end = std::strchr(ucode_disasm_ptr, '\n');
fprintf(f, "// ");
fwrite(ucode_disasm_ptr, 1, line_end - ucode_disasm_ptr + 1, f);
ucode_disasm_ptr = line_end + 1;
}
fprintf(f, "\n\n");
if (!host_disassembly_.empty()) {
fprintf(f, "\n\n/*\n%s\n*/\n", host_disassembly_.c_str());
}
fclose(f);
}
char bin_file_name[kMaxPath];
std::snprintf(bin_file_name, xe::countof(bin_file_name),
"%s/shader_%s_%.16llX.bin.%s", base_path.c_str(), path_prefix,
ucode_data_hash_,
shader_type_ == ShaderType::kVertex ? "vert" : "frag");
f = fopen(bin_file_name, "w");
if (f) {
fwrite(ucode_data_.data(), 4, ucode_data_.size(), f);
fclose(f);
}
}
} // namespace gpu
} // namespace xe

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -13,42 +13,565 @@
#include <string>
#include <vector>
#include "xenia/gpu/shader_translator.h"
#include "xenia/base/string_buffer.h"
#include "xenia/gpu/ucode.h"
#include "xenia/gpu/xenos.h"
namespace xe {
namespace gpu {
enum class InstructionStorageTarget {
// Result is not stored.
kNone,
// Result is stored to a temporary register indexed by storage_index [0-31].
kRegister,
// Result is stored into a vertex shader interpolant export [0-15].
kInterpolant,
// Result is stored to the position export (gl_Position).
kPosition,
// Result is stored to the point size export (gl_PointSize).
kPointSize,
// Result is stored to a color target export indexed by storage_index [0-3].
kColorTarget,
// Result is stored to the depth export (gl_FragDepth).
kDepth,
};
enum class InstructionStorageAddressingMode {
// The storage index is not dynamically addressed.
kStatic,
// The storage index is addressed by a0.
kAddressAbsolute,
// The storage index is addressed by aL.
kAddressRelative,
};
// Describes the source value of a particular component.
enum class SwizzleSource {
// Component receives the source X.
kX,
// Component receives the source Y.
kY,
// Component receives the source Z.
kZ,
// Component receives the source W.
kW,
// Component receives constant 0.
k0,
// Component receives constant 1.
k1,
};
constexpr SwizzleSource GetSwizzleFromComponentIndex(int i) {
return static_cast<SwizzleSource>(i);
}
inline char GetCharForComponentIndex(int i) {
const static char kChars[] = {'x', 'y', 'z', 'w'};
return kChars[i];
}
inline char GetCharForSwizzle(SwizzleSource swizzle_source) {
const static char kChars[] = {'x', 'y', 'z', 'w', '0', '1'};
return kChars[static_cast<int>(swizzle_source)];
}
struct InstructionResult {
// Where the result is going.
InstructionStorageTarget storage_target = InstructionStorageTarget::kNone;
// Index into the storage_target, if it is indexed.
int storage_index = 0;
// How the storage index is dynamically addressed, if it is.
InstructionStorageAddressingMode storage_addressing_mode =
InstructionStorageAddressingMode::kStatic;
// True if the result is exporting from the shader.
bool is_export = false;
// True to clamp the result value to [0-1].
bool is_clamped = false;
// Defines whether each output component is written.
bool write_mask[4] = {false, false, false, false};
// Defines the source for each output component xyzw.
SwizzleSource components[4] = {SwizzleSource::kX, SwizzleSource::kY,
SwizzleSource::kZ, SwizzleSource::kW};
// Returns true if any component is written to.
bool has_any_writes() const {
return write_mask[0] || write_mask[1] || write_mask[2] || write_mask[3];
}
// Returns true if all components are written to.
bool has_all_writes() const {
return write_mask[0] && write_mask[1] && write_mask[2] && write_mask[3];
}
// Returns true if any non-constant components are written.
bool stores_non_constants() const {
for (int i = 0; i < 4; ++i) {
if (write_mask[i] && components[i] != SwizzleSource::k0 &&
components[i] != SwizzleSource::k1) {
return true;
}
}
return false;
}
// True if the components are in their 'standard' swizzle arrangement (xyzw).
bool is_standard_swizzle() const {
return has_all_writes() && components[0] == SwizzleSource::kX &&
components[1] == SwizzleSource::kY &&
components[2] == SwizzleSource::kZ &&
components[3] == SwizzleSource::kW;
}
};
enum class InstructionStorageSource {
// Source is stored in a temporary register indexed by storage_index [0-31].
kRegister,
// Source is stored in a float constant indexed by storage_index [0-511].
kConstantFloat,
// Source is stored in a float constant indexed by storage_index [0-31].
kConstantInt,
// Source is stored in a float constant indexed by storage_index [0-255].
kConstantBool,
// Source is stored in a vertex fetch constant indexed by storage_index
// [0-95].
kVertexFetchConstant,
// Source is stored in a texture fetch constant indexed by storage_index
// [0-31].
kTextureFetchConstant,
};
struct InstructionOperand {
// Where the source comes from.
InstructionStorageSource storage_source = InstructionStorageSource::kRegister;
// Index into the storage_target, if it is indexed.
int storage_index = 0;
// How the storage index is dynamically addressed, if it is.
InstructionStorageAddressingMode storage_addressing_mode =
InstructionStorageAddressingMode::kStatic;
// True to negate the operand value.
bool is_negated = false;
// True to take the absolute value of the source (before any negation).
bool is_absolute_value = false;
// Number of components taken from the source operand.
int component_count = 0;
// Defines the source for each component xyzw (up to the given
// component_count).
SwizzleSource components[4] = {SwizzleSource::kX, SwizzleSource::kY,
SwizzleSource::kZ, SwizzleSource::kW};
// True if the components are in their 'standard' swizzle arrangement (xyzw).
bool is_standard_swizzle() const {
switch (component_count) {
case 4:
return components[0] == SwizzleSource::kX &&
components[1] == SwizzleSource::kY &&
components[2] == SwizzleSource::kZ &&
components[3] == SwizzleSource::kW;
}
return false;
}
};
struct ParsedExecInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Opcode for the instruction.
ucode::ControlFlowOpcode opcode;
// Friendly name of the instruction.
const char* opcode_name = nullptr;
// Instruction address where ALU/fetch instructions reside.
uint32_t instruction_address = 0;
// Number of instructions to execute.
uint32_t instruction_count = 0;
enum class Type {
// Block is always executed.
kUnconditional,
// Execution is conditional on the value of the boolean constant.
kConditional,
// Execution is predicated.
kPredicated,
};
// Condition required to execute the instructions.
Type type = Type::kUnconditional;
// Constant index used as the conditional if kConditional.
uint32_t bool_constant_index = 0;
// Required condition value of the comparision (true or false).
bool condition = false;
// Whether to reset the current predicate.
bool clean = true;
// ?
bool is_yield = false;
// Sequence bits, 2 per instruction, indicating whether ALU or fetch.
uint32_t sequence = 0;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedLoopStartInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Integer constant register that holds the loop parameters.
// Byte-wise: [loop count, start, step [-128, 127], ?]
uint32_t loop_constant_index = 0;
// Whether to reuse the current aL instead of reset it to loop start.
bool is_repeat = false;
// Target address to jump to when skipping the loop.
uint32_t loop_skip_address = 0;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedLoopEndInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Break from the loop if the predicate matches the expected value.
bool is_predicated_break = false;
// Required condition value of the comparision (true or false).
bool predicate_condition = false;
// Integer constant register that holds the loop parameters.
// Byte-wise: [loop count, start, step [-128, 127], ?]
uint32_t loop_constant_index = 0;
// Target address of the start of the loop body.
uint32_t loop_body_address = 0;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedCallInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Target address.
uint32_t target_address = 0;
enum class Type {
// Call is always made.
kUnconditional,
// Call is conditional on the value of the boolean constant.
kConditional,
// Call is predicated.
kPredicated,
};
// Condition required to make the call.
Type type = Type::kUnconditional;
// Constant index used as the conditional if kConditional.
uint32_t bool_constant_index = 0;
// Required condition value of the comparision (true or false).
bool condition = false;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedReturnInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedJumpInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Target address.
uint32_t target_address = 0;
enum class Type {
// Jump is always taken.
kUnconditional,
// Jump is conditional on the value of the boolean constant.
kConditional,
// Jump is predicated.
kPredicated,
};
// Condition required to make the jump.
Type type = Type::kUnconditional;
// Constant index used as the conditional if kConditional.
uint32_t bool_constant_index = 0;
// Required condition value of the comparision (true or false).
bool condition = false;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedAllocInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// The type of resource being allocated.
ucode::AllocType type = ucode::AllocType::kNone;
// Total count associated with the allocation.
int count = 0;
// True if this allocation is in a vertex shader.
bool is_vertex_shader = false;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedVertexFetchInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Opcode for the instruction.
ucode::FetchOpcode opcode;
// Friendly name of the instruction.
const char* opcode_name = nullptr;
// True if the fetch is reusing a previous full fetch.
// The previous fetch source and constant data will be populated.
bool is_mini_fetch = false;
// True if the instruction is predicated on the specified
// predicate_condition.
bool is_predicated = false;
// Expected predication condition value if predicated.
bool predicate_condition = false;
// Describes how the instruction result is stored.
InstructionResult result;
// Number of source operands.
size_t operand_count = 0;
// Describes each source operand.
InstructionOperand operands[2];
struct Attributes {
VertexFormat data_format = VertexFormat::kUndefined;
int offset = 0;
int stride = 0; // In dwords.
int exp_adjust = 0;
bool is_index_rounded = false;
bool is_signed = false;
bool is_integer = false;
int prefetch_count = 0;
};
// Attributes describing the fetch operation.
Attributes attributes;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedTextureFetchInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Opcode for the instruction.
ucode::FetchOpcode opcode;
// Friendly name of the instruction.
const char* opcode_name = nullptr;
// Texture dimension for opcodes that have multiple dimension forms.
TextureDimension dimension = TextureDimension::k1D;
// True if the instruction is predicated on the specified
// predicate_condition.
bool is_predicated = false;
// Expected predication condition value if predicated.
bool predicate_condition = false;
// True if the instruction has a result.
bool has_result() const {
return result.storage_target != InstructionStorageTarget::kNone;
}
// Describes how the instruction result is stored.
InstructionResult result;
// Number of source operands.
size_t operand_count = 0;
// Describes each source operand.
InstructionOperand operands[2];
struct Attributes {
bool fetch_valid_only = true;
bool unnormalized_coordinates = false;
TextureFilter mag_filter = TextureFilter::kUseFetchConst;
TextureFilter min_filter = TextureFilter::kUseFetchConst;
TextureFilter mip_filter = TextureFilter::kUseFetchConst;
AnisoFilter aniso_filter = AnisoFilter::kUseFetchConst;
bool use_computed_lod = true;
bool use_register_lod = false;
bool use_register_gradients = false;
float offset_x = 0.0f;
float offset_y = 0.0f;
float offset_z = 0.0f;
};
// Attributes describing the fetch operation.
Attributes attributes;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedAluInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
enum class Type {
kNop,
kVector,
kScalar,
};
// Type of the instruction.
Type type = Type::kNop;
bool is_nop() const { return type == Type::kNop; }
bool is_vector_type() const { return type == Type::kVector; }
bool is_scalar_type() const { return type == Type::kScalar; }
// Opcode for the instruction if it is a vector type.
ucode::AluVectorOpcode vector_opcode = ucode::AluVectorOpcode::kAdd;
// Opcode for the instruction if it is a scalar type.
ucode::AluScalarOpcode scalar_opcode = ucode::AluScalarOpcode::kAdds;
// Friendly name of the instruction.
const char* opcode_name = nullptr;
// True if the instruction is paired with another instruction.
bool is_paired = false;
// True if the instruction is predicated on the specified
// predicate_condition.
bool is_predicated = false;
// Expected predication condition value if predicated.
bool predicate_condition = false;
// Describes how the instruction result is stored.
InstructionResult result;
// Number of source operands.
size_t operand_count = 0;
// Describes each source operand.
InstructionOperand operands[3];
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
class Shader {
public:
struct Error {
bool is_fatal = false;
std::string message;
};
struct VertexBinding {
struct Attribute {
// Attribute index, 0-based in the entire shader.
int attrib_index;
// Fetch instruction with all parameters.
ParsedVertexFetchInstruction fetch_instr;
// Size of the attribute, in words.
uint32_t size_words;
};
// Index within the vertex binding listing.
int binding_index;
// Fetch constant index [0-95].
uint32_t fetch_constant;
// Stride of the entire binding, in words.
uint32_t stride_words;
// Packed attributes within the binding buffer.
std::vector<Attribute> attributes;
};
struct TextureBinding {
// Index within the texture binding listing.
size_t binding_index;
// Fetch constant index [0-31].
uint32_t fetch_constant;
// Fetch instruction with all parameters.
ParsedTextureFetchInstruction fetch_instr;
};
Shader(ShaderType shader_type, uint64_t ucode_data_hash,
const uint32_t* ucode_dwords, size_t ucode_dword_count);
virtual ~Shader();
// Whether the shader is identified as a vertex or pixel shader.
ShaderType type() const { return shader_type_; }
bool is_valid() const { return !!translated_shader_; }
const std::string& host_disassembly() const { return host_disassembly_; }
TranslatedShader* translated_shader() const {
return translated_shader_.get();
// Microcode dwords in host endianness.
const std::vector<uint32_t>& ucode_data() const { return ucode_data_; }
const uint32_t* ucode_dwords() const { return ucode_data_.data(); }
size_t ucode_dword_count() const { return ucode_data_.size(); }
// All vertex bindings used in the shader.
// Valid for vertex shaders only.
const std::vector<VertexBinding>& vertex_bindings() const {
return vertex_bindings_;
}
const uint32_t* data() const { return data_.data(); }
uint32_t dword_count() const { return uint32_t(data_.size()); }
// All texture bindings used in the shader.
// Valid for both vertex and pixel shaders.
const std::vector<TextureBinding>& texture_bindings() const {
return texture_bindings_;
}
virtual bool Prepare(ShaderTranslator* shader_translator);
// Returns true if the given color target index [0-3].
bool writes_color_target(int i) const { return writes_color_targets_[i]; }
// True if the shader was translated and prepared without error.
bool is_valid() const { return is_valid_; }
// Errors that occurred during translation.
const std::vector<Error>& errors() const { return errors_; }
// Microcode disassembly in D3D format.
const std::string& ucode_disassembly() const { return ucode_disassembly_; }
// Translated shader binary (or text).
const std::vector<uint8_t>& translated_binary() const {
return translated_binary_;
}
// Gets the translated shader binary as a string.
// This is only valid if it is actually text.
std::string GetTranslatedBinaryString() const;
// Disassembly of the translated from the host graphics layer.
// May be empty if the host does not support disassembly.
const std::string& host_disassembly() const { return host_disassembly_; }
// A lot of errors that occurred during preparation of the host shader.
const std::string& host_error_log() const { return host_error_log_; }
// Host binary that can be saved and reused across runs.
// May be empty if the host does not support saving binaries.
const std::vector<uint8_t>& host_binary() const { return host_binary_; }
// Prepares the shader for use in the host graphics API.
virtual bool Prepare() { return is_valid_; }
// Dumps the shader to a file in the given path based on ucode hash.
// Both the ucode binary and disassembled and translated shader will be
// written.
void Dump(const std::string& base_path, const char* path_prefix);
protected:
Shader(ShaderType shader_type, uint64_t data_hash, const uint32_t* dword_ptr,
uint32_t dword_count);
friend class ShaderTranslator;
ShaderType shader_type_;
uint64_t data_hash_;
std::vector<uint32_t> data_;
std::vector<uint32_t> ucode_data_;
uint64_t ucode_data_hash_;
std::string translated_disassembly_;
std::vector<VertexBinding> vertex_bindings_;
std::vector<TextureBinding> texture_bindings_;
bool writes_color_targets_[4] = {false, false, false, false};
bool is_valid_ = false;
std::vector<Error> errors_;
std::string ucode_disassembly_;
std::vector<uint8_t> translated_binary_;
std::string host_disassembly_;
std::string error_log_;
std::unique_ptr<TranslatedShader> translated_shader_;
std::string host_error_log_;
std::vector<uint8_t> host_binary_;
};
} // namespace gpu

View File

@ -79,6 +79,11 @@ int shader_compiler_main(const std::vector<std::wstring>& args) {
shader_type == ShaderType::kVertex ? "vertex" : "pixel",
ucode_dwords.size(), ucode_dwords.size() * 4);
// TODO(benvanik): hash? need to return the data to big-endian format first.
uint64_t ucode_data_hash = 0;
auto shader = std::make_unique<Shader>(
shader_type, ucode_data_hash, ucode_dwords.data(), ucode_dwords.size());
std::unique_ptr<ShaderTranslator> translator;
if (FLAGS_shader_output_type == "spirv" ||
FLAGS_shader_output_type == "spirvtext") {
@ -90,13 +95,10 @@ int shader_compiler_main(const std::vector<std::wstring>& args) {
translator = std::make_unique<UcodeShaderTranslator>();
}
// TODO(benvanik): hash? need to return the data to big-endian format first.
uint64_t ucode_data_hash = 0;
auto translated_shader = translator->Translate(
shader_type, ucode_data_hash, ucode_dwords.data(), ucode_dwords.size());
translator->Translate(shader.get());
const void* source_data = translated_shader->binary().data();
size_t source_data_size = translated_shader->binary().size();
const void* source_data = shader->translated_binary().data();
size_t source_data_size = shader->translated_binary().size();
std::unique_ptr<xe::ui::spirv::SpirvDisassembler::Result> spirv_disasm_result;
if (FLAGS_shader_output_type == "spirvtext") {

View File

@ -40,36 +40,6 @@ using namespace ucode;
// Lots of naming comes from the disassembly spit out by the XNA GS compiler
// and dumps of d3dcompiler and games: http://pastebin.com/i4kAv7bB
TranslatedShader::TranslatedShader(ShaderType shader_type,
uint64_t ucode_data_hash,
const uint32_t* ucode_dwords,
size_t ucode_dword_count,
std::vector<Error> errors)
: shader_type_(shader_type),
ucode_data_hash_(ucode_data_hash),
errors_(std::move(errors)) {
ucode_data_.resize(ucode_dword_count);
std::memcpy(ucode_data_.data(), ucode_dwords,
ucode_dword_count * sizeof(uint32_t));
is_valid_ = true;
for (const auto& error : errors_) {
if (error.is_fatal) {
is_valid_ = false;
break;
}
}
}
TranslatedShader::~TranslatedShader() = default;
std::string TranslatedShader::GetBinaryString() const {
std::string result;
result.resize(binary_.size());
std::memcpy(const_cast<char*>(result.data()), binary_.data(), binary_.size());
return result;
}
ShaderTranslator::ShaderTranslator() = default;
ShaderTranslator::~ShaderTranslator() = default;
@ -87,14 +57,12 @@ void ShaderTranslator::Reset() {
}
}
std::unique_ptr<TranslatedShader> ShaderTranslator::Translate(
ShaderType shader_type, uint64_t ucode_data_hash,
const uint32_t* ucode_dwords, size_t ucode_dword_count) {
bool ShaderTranslator::Translate(Shader* shader) {
Reset();
shader_type_ = shader_type;
ucode_dwords_ = ucode_dwords;
ucode_dword_count_ = ucode_dword_count;
shader_type_ = shader->type();
ucode_dwords_ = shader->ucode_dwords();
ucode_dword_count_ = shader->ucode_dword_count();
// Run through and gather all binding information.
// Translators may need this before they start codegen.
@ -119,17 +87,24 @@ std::unique_ptr<TranslatedShader> ShaderTranslator::Translate(
TranslateBlocks();
std::unique_ptr<TranslatedShader> translated_shader(
new TranslatedShader(shader_type, ucode_data_hash, ucode_dwords,
ucode_dword_count, std::move(errors_)));
translated_shader->binary_ = CompleteTranslation();
translated_shader->ucode_disassembly_ = ucode_disasm_buffer_.to_string();
translated_shader->vertex_bindings_ = std::move(vertex_bindings_);
translated_shader->texture_bindings_ = std::move(texture_bindings_);
shader->errors_ = std::move(errors_);
shader->translated_binary_ = CompleteTranslation();
shader->ucode_disassembly_ = ucode_disasm_buffer_.to_string();
shader->vertex_bindings_ = std::move(vertex_bindings_);
shader->texture_bindings_ = std::move(texture_bindings_);
for (size_t i = 0; i < xe::countof(writes_color_targets_); ++i) {
translated_shader->writes_color_targets_[i] = writes_color_targets_[i];
shader->writes_color_targets_[i] = writes_color_targets_[i];
}
return translated_shader;
shader->is_valid_ = true;
for (const auto& error : shader->errors_) {
if (error.is_fatal) {
shader->is_valid_ = false;
break;
}
}
return shader->is_valid_;
}
void ShaderTranslator::MarkUcodeInstruction(uint32_t dword_offset) {
@ -159,7 +134,7 @@ void ShaderTranslator::AppendUcodeDisasmFormat(const char* format, ...) {
}
void ShaderTranslator::EmitTranslationError(const char* message) {
TranslatedShader::Error error;
Shader::Error error;
error.is_fatal = true;
error.message = message;
// TODO(benvanik): location information.
@ -167,7 +142,7 @@ void ShaderTranslator::EmitTranslationError(const char* message) {
}
void ShaderTranslator::EmitUnimplementedTranslationError() {
TranslatedShader::Error error;
Shader::Error error;
error.is_fatal = true;
error.message = "Unimplemented translation";
// TODO(benvanik): location information.
@ -195,6 +170,7 @@ void ShaderTranslator::GatherBindingInformation(
auto fetch_opcode =
static_cast<FetchOpcode>(ucode_dwords_[instr_offset * 3] & 0x1F);
if (fetch_opcode == FetchOpcode::kVertexFetch) {
assert_true(is_vertex_shader());
GatherVertexBindingInformation(
*reinterpret_cast<const VertexFetchInstruction*>(
ucode_dwords_ + instr_offset * 3));
@ -231,7 +207,7 @@ void ShaderTranslator::GatherVertexBindingInformation(
// Try to allocate an attribute on an existing binding.
// If no binding for this fetch slot is found create it.
using VertexBinding = TranslatedShader::VertexBinding;
using VertexBinding = Shader::VertexBinding;
VertexBinding::Attribute* attrib = nullptr;
for (auto& vertex_binding : vertex_bindings_) {
if (vertex_binding.fetch_constant == op.fetch_constant_index()) {
@ -244,7 +220,7 @@ void ShaderTranslator::GatherVertexBindingInformation(
}
if (!attrib) {
assert_not_zero(op.stride());
TranslatedShader::VertexBinding vertex_binding;
VertexBinding vertex_binding;
vertex_binding.binding_index = static_cast<int>(vertex_bindings_.size());
vertex_binding.fetch_constant = op.fetch_constant_index();
vertex_binding.stride_words = op.stride();
@ -269,7 +245,7 @@ void ShaderTranslator::GatherTextureBindingInformation(
// Doesn't use bindings.
return;
}
TranslatedShader::TextureBinding binding;
Shader::TextureBinding binding;
binding.binding_index = texture_bindings_.size();
ParseTextureFetchInstruction(op, &binding.fetch_instr);
binding.fetch_constant = binding.fetch_instr.operands[1].storage_index;

View File

@ -15,537 +15,18 @@
#include <vector>
#include "xenia/base/string_buffer.h"
#include "xenia/gpu/shader.h"
#include "xenia/gpu/ucode.h"
#include "xenia/gpu/xenos.h"
namespace xe {
namespace gpu {
enum class InstructionStorageTarget {
// Result is not stored.
kNone,
// Result is stored to a temporary register indexed by storage_index [0-31].
kRegister,
// Result is stored into a vertex shader interpolant export [0-15].
kInterpolant,
// Result is stored to the position export (gl_Position).
kPosition,
// Result is stored to the point size export (gl_PointSize).
kPointSize,
// Result is stored to a color target export indexed by storage_index [0-3].
kColorTarget,
// Result is stored to the depth export (gl_FragDepth).
kDepth,
};
enum class InstructionStorageAddressingMode {
// The storage index is not dynamically addressed.
kStatic,
// The storage index is addressed by a0.
kAddressAbsolute,
// The storage index is addressed by aL.
kAddressRelative,
};
// Describes the source value of a particular component.
enum class SwizzleSource {
// Component receives the source X.
kX,
// Component receives the source Y.
kY,
// Component receives the source Z.
kZ,
// Component receives the source W.
kW,
// Component receives constant 0.
k0,
// Component receives constant 1.
k1,
};
constexpr SwizzleSource GetSwizzleFromComponentIndex(int i) {
return static_cast<SwizzleSource>(i);
}
inline char GetCharForComponentIndex(int i) {
const static char kChars[] = {'x', 'y', 'z', 'w'};
return kChars[i];
}
inline char GetCharForSwizzle(SwizzleSource swizzle_source) {
const static char kChars[] = {'x', 'y', 'z', 'w', '0', '1'};
return kChars[static_cast<int>(swizzle_source)];
}
struct InstructionResult {
// Where the result is going.
InstructionStorageTarget storage_target = InstructionStorageTarget::kNone;
// Index into the storage_target, if it is indexed.
int storage_index = 0;
// How the storage index is dynamically addressed, if it is.
InstructionStorageAddressingMode storage_addressing_mode =
InstructionStorageAddressingMode::kStatic;
// True if the result is exporting from the shader.
bool is_export = false;
// True to clamp the result value to [0-1].
bool is_clamped = false;
// Defines whether each output component is written.
bool write_mask[4] = {false, false, false, false};
// Defines the source for each output component xyzw.
SwizzleSource components[4] = {SwizzleSource::kX, SwizzleSource::kY,
SwizzleSource::kZ, SwizzleSource::kW};
// Returns true if any component is written to.
bool has_any_writes() const {
return write_mask[0] || write_mask[1] || write_mask[2] || write_mask[3];
}
// Returns true if all components are written to.
bool has_all_writes() const {
return write_mask[0] && write_mask[1] && write_mask[2] && write_mask[3];
}
// Returns true if any non-constant components are written.
bool stores_non_constants() const {
for (int i = 0; i < 4; ++i) {
if (write_mask[i] && components[i] != SwizzleSource::k0 &&
components[i] != SwizzleSource::k1) {
return true;
}
}
return false;
}
// True if the components are in their 'standard' swizzle arrangement (xyzw).
bool is_standard_swizzle() const {
return has_all_writes() && components[0] == SwizzleSource::kX &&
components[1] == SwizzleSource::kY &&
components[2] == SwizzleSource::kZ &&
components[3] == SwizzleSource::kW;
}
};
enum class InstructionStorageSource {
// Source is stored in a temporary register indexed by storage_index [0-31].
kRegister,
// Source is stored in a float constant indexed by storage_index [0-511].
kConstantFloat,
// Source is stored in a float constant indexed by storage_index [0-31].
kConstantInt,
// Source is stored in a float constant indexed by storage_index [0-255].
kConstantBool,
// Source is stored in a vertex fetch constant indexed by storage_index
// [0-95].
kVertexFetchConstant,
// Source is stored in a texture fetch constant indexed by storage_index
// [0-31].
kTextureFetchConstant,
};
struct InstructionOperand {
// Where the source comes from.
InstructionStorageSource storage_source = InstructionStorageSource::kRegister;
// Index into the storage_target, if it is indexed.
int storage_index = 0;
// How the storage index is dynamically addressed, if it is.
InstructionStorageAddressingMode storage_addressing_mode =
InstructionStorageAddressingMode::kStatic;
// True to negate the operand value.
bool is_negated = false;
// True to take the absolute value of the source (before any negation).
bool is_absolute_value = false;
// Number of components taken from the source operand.
int component_count = 0;
// Defines the source for each component xyzw (up to the given
// component_count).
SwizzleSource components[4] = {SwizzleSource::kX, SwizzleSource::kY,
SwizzleSource::kZ, SwizzleSource::kW};
// True if the components are in their 'standard' swizzle arrangement (xyzw).
bool is_standard_swizzle() const {
switch (component_count) {
case 4:
return components[0] == SwizzleSource::kX &&
components[1] == SwizzleSource::kY &&
components[2] == SwizzleSource::kZ &&
components[3] == SwizzleSource::kW;
}
return false;
}
};
struct ParsedExecInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Opcode for the instruction.
ucode::ControlFlowOpcode opcode;
// Friendly name of the instruction.
const char* opcode_name = nullptr;
// Instruction address where ALU/fetch instructions reside.
uint32_t instruction_address = 0;
// Number of instructions to execute.
uint32_t instruction_count = 0;
enum class Type {
// Block is always executed.
kUnconditional,
// Execution is conditional on the value of the boolean constant.
kConditional,
// Execution is predicated.
kPredicated,
};
// Condition required to execute the instructions.
Type type = Type::kUnconditional;
// Constant index used as the conditional if kConditional.
uint32_t bool_constant_index = 0;
// Required condition value of the comparision (true or false).
bool condition = false;
// Whether to reset the current predicate.
bool clean = true;
// ?
bool is_yield = false;
// Sequence bits, 2 per instruction, indicating whether ALU or fetch.
uint32_t sequence = 0;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedLoopStartInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Integer constant register that holds the loop parameters.
// Byte-wise: [loop count, start, step [-128, 127], ?]
uint32_t loop_constant_index = 0;
// Whether to reuse the current aL instead of reset it to loop start.
bool is_repeat = false;
// Target address to jump to when skipping the loop.
uint32_t loop_skip_address = 0;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedLoopEndInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Break from the loop if the predicate matches the expected value.
bool is_predicated_break = false;
// Required condition value of the comparision (true or false).
bool predicate_condition = false;
// Integer constant register that holds the loop parameters.
// Byte-wise: [loop count, start, step [-128, 127], ?]
uint32_t loop_constant_index = 0;
// Target address of the start of the loop body.
uint32_t loop_body_address = 0;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedCallInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Target address.
uint32_t target_address = 0;
enum class Type {
// Call is always made.
kUnconditional,
// Call is conditional on the value of the boolean constant.
kConditional,
// Call is predicated.
kPredicated,
};
// Condition required to make the call.
Type type = Type::kUnconditional;
// Constant index used as the conditional if kConditional.
uint32_t bool_constant_index = 0;
// Required condition value of the comparision (true or false).
bool condition = false;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedReturnInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedJumpInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Target address.
uint32_t target_address = 0;
enum class Type {
// Jump is always taken.
kUnconditional,
// Jump is conditional on the value of the boolean constant.
kConditional,
// Jump is predicated.
kPredicated,
};
// Condition required to make the jump.
Type type = Type::kUnconditional;
// Constant index used as the conditional if kConditional.
uint32_t bool_constant_index = 0;
// Required condition value of the comparision (true or false).
bool condition = false;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedAllocInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// The type of resource being allocated.
ucode::AllocType type = ucode::AllocType::kNone;
// Total count associated with the allocation.
int count = 0;
// True if this allocation is in a vertex shader.
bool is_vertex_shader = false;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedVertexFetchInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Opcode for the instruction.
ucode::FetchOpcode opcode;
// Friendly name of the instruction.
const char* opcode_name = nullptr;
// True if the fetch is reusing a previous full fetch.
// The previous fetch source and constant data will be populated.
bool is_mini_fetch = false;
// True if the instruction is predicated on the specified
// predicate_condition.
bool is_predicated = false;
// Expected predication condition value if predicated.
bool predicate_condition = false;
// Describes how the instruction result is stored.
InstructionResult result;
// Number of source operands.
size_t operand_count = 0;
// Describes each source operand.
InstructionOperand operands[2];
struct Attributes {
VertexFormat data_format = VertexFormat::kUndefined;
int offset = 0;
int stride = 0; // In dwords.
int exp_adjust = 0;
bool is_index_rounded = false;
bool is_signed = false;
bool is_integer = false;
int prefetch_count = 0;
};
// Attributes describing the fetch operation.
Attributes attributes;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedTextureFetchInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
// Opcode for the instruction.
ucode::FetchOpcode opcode;
// Friendly name of the instruction.
const char* opcode_name = nullptr;
// Texture dimension for opcodes that have multiple dimension forms.
TextureDimension dimension = TextureDimension::k1D;
// True if the instruction is predicated on the specified
// predicate_condition.
bool is_predicated = false;
// Expected predication condition value if predicated.
bool predicate_condition = false;
// True if the instruction has a result.
bool has_result() const {
return result.storage_target != InstructionStorageTarget::kNone;
}
// Describes how the instruction result is stored.
InstructionResult result;
// Number of source operands.
size_t operand_count = 0;
// Describes each source operand.
InstructionOperand operands[2];
struct Attributes {
bool fetch_valid_only = true;
bool unnormalized_coordinates = false;
TextureFilter mag_filter = TextureFilter::kUseFetchConst;
TextureFilter min_filter = TextureFilter::kUseFetchConst;
TextureFilter mip_filter = TextureFilter::kUseFetchConst;
AnisoFilter aniso_filter = AnisoFilter::kUseFetchConst;
bool use_computed_lod = true;
bool use_register_lod = false;
bool use_register_gradients = false;
float offset_x = 0.0f;
float offset_y = 0.0f;
float offset_z = 0.0f;
};
// Attributes describing the fetch operation.
Attributes attributes;
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
struct ParsedAluInstruction {
// Index into the ucode dword source.
uint32_t dword_index = 0;
enum class Type {
kNop,
kVector,
kScalar,
};
// Type of the instruction.
Type type = Type::kNop;
bool is_nop() const { return type == Type::kNop; }
bool is_vector_type() const { return type == Type::kVector; }
bool is_scalar_type() const { return type == Type::kScalar; }
// Opcode for the instruction if it is a vector type.
ucode::AluVectorOpcode vector_opcode = ucode::AluVectorOpcode::kAdd;
// Opcode for the instruction if it is a scalar type.
ucode::AluScalarOpcode scalar_opcode = ucode::AluScalarOpcode::kAdds;
// Friendly name of the instruction.
const char* opcode_name = nullptr;
// True if the instruction is paired with another instruction.
bool is_paired = false;
// True if the instruction is predicated on the specified
// predicate_condition.
bool is_predicated = false;
// Expected predication condition value if predicated.
bool predicate_condition = false;
// Describes how the instruction result is stored.
InstructionResult result;
// Number of source operands.
size_t operand_count = 0;
// Describes each source operand.
InstructionOperand operands[3];
// Disassembles the instruction into ucode assembly text.
void Disassemble(StringBuffer* out) const;
};
class TranslatedShader {
public:
struct Error {
bool is_fatal = false;
std::string message;
};
struct VertexBinding {
struct Attribute {
// Attribute index, 0-based in the entire shader.
int attrib_index;
// Fetch instruction with all parameters.
ParsedVertexFetchInstruction fetch_instr;
// Size of the attribute, in words.
uint32_t size_words;
};
// Index within the vertex binding listing.
int binding_index;
// Fetch constant index [0-95].
uint32_t fetch_constant;
// Stride of the entire binding, in words.
uint32_t stride_words;
// Packed attributes within the binding buffer.
std::vector<Attribute> attributes;
};
struct TextureBinding {
// Index within the texture binding listing.
size_t binding_index;
// Fetch constant index [0-31].
uint32_t fetch_constant;
// Fetch instruction with all parameters.
ParsedTextureFetchInstruction fetch_instr;
};
~TranslatedShader();
ShaderType type() const { return shader_type_; }
const std::vector<uint32_t>& ucode_data() const { return ucode_data_; }
const uint32_t* ucode_dwords() const { return ucode_data_.data(); }
size_t ucode_dword_count() const { return ucode_data_.size(); }
const std::vector<VertexBinding>& vertex_bindings() const {
return vertex_bindings_;
}
const std::vector<TextureBinding>& texture_bindings() const {
return texture_bindings_;
}
// Returns true if the given color target index [0-3].
bool writes_color_target(int i) const { return writes_color_targets_[i]; }
bool is_valid() const { return is_valid_; }
const std::vector<Error>& errors() const { return errors_; }
const std::vector<uint8_t>& binary() const { return binary_; }
const std::string& ucode_disassembly() const { return ucode_disassembly_; }
std::string GetBinaryString() const;
private:
friend class ShaderTranslator;
TranslatedShader(ShaderType shader_type, uint64_t ucode_data_hash,
const uint32_t* ucode_dwords, size_t ucode_dword_count,
std::vector<Error> errors);
ShaderType shader_type_;
std::vector<uint32_t> ucode_data_;
uint64_t ucode_data_hash_;
std::vector<VertexBinding> vertex_bindings_;
std::vector<TextureBinding> texture_bindings_;
bool writes_color_targets_[4] = {false, false, false, false};
bool is_valid_ = false;
std::vector<Error> errors_;
std::string ucode_disassembly_;
std::vector<uint8_t> binary_;
};
class ShaderTranslator {
public:
virtual ~ShaderTranslator();
std::unique_ptr<TranslatedShader> Translate(ShaderType shader_type,
uint64_t ucode_data_hash,
const uint32_t* ucode_dwords,
size_t ucode_dword_count);
bool Translate(Shader* shader);
protected:
ShaderTranslator();
@ -558,12 +39,11 @@ class ShaderTranslator {
// True if the current shader is a pixel shader.
bool is_pixel_shader() const { return shader_type_ == ShaderType::kPixel; }
// A list of all vertex bindings, populated before translation occurs.
const std::vector<TranslatedShader::VertexBinding>& vertex_bindings() const {
const std::vector<Shader::VertexBinding>& vertex_bindings() const {
return vertex_bindings_;
}
// A list of all texture bindings, populated before translation occurs.
const std::vector<TranslatedShader::TextureBinding>& texture_bindings()
const {
const std::vector<Shader::TextureBinding>& texture_bindings() const {
return texture_bindings_;
}
@ -684,7 +164,7 @@ class ShaderTranslator {
size_t ucode_dword_count_;
// Accumulated translation errors.
std::vector<TranslatedShader::Error> errors_;
std::vector<Shader::Error> errors_;
// Microcode disassembly buffer, accumulated throughout the translation.
StringBuffer ucode_disasm_buffer_;
@ -698,8 +178,8 @@ class ShaderTranslator {
// Detected binding information gathered before translation.
int total_attrib_count_ = 0;
std::vector<TranslatedShader::VertexBinding> vertex_bindings_;
std::vector<TranslatedShader::TextureBinding> texture_bindings_;
std::vector<Shader::VertexBinding> vertex_bindings_;
std::vector<Shader::TextureBinding> texture_bindings_;
bool writes_color_targets_[4] = {false, false, false, false};
static const AluOpcodeInfo alu_vector_opcode_infos_[0x20];

View File

@ -486,11 +486,11 @@ void TraceViewer::DrawShaderUI(Shader* shader, ShaderDisplayType display_type) {
switch (display_type) {
case ShaderDisplayType::kUcode: {
DrawMultilineString(shader->translated_shader()->ucode_disassembly());
DrawMultilineString(shader->ucode_disassembly());
break;
}
case ShaderDisplayType::kTranslated: {
const auto& str = shader->translated_shader()->GetBinaryString();
const auto& str = shader->GetTranslatedBinaryString();
size_t i = 0;
bool done = false;
while (!done && i < str.size()) {
@ -567,7 +567,7 @@ void TraceViewer::DrawBlendMode(uint32_t src_blend, uint32_t dest_blend,
}
void TraceViewer::DrawTextureInfo(
const TranslatedShader::TextureBinding& texture_binding) {
const Shader::TextureBinding& texture_binding) {
auto& regs = *graphics_system_->register_file();
int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 +
@ -633,15 +633,14 @@ void TraceViewer::DrawTextureInfo(
}
void TraceViewer::DrawFailedTextureInfo(
const TranslatedShader::TextureBinding& texture_binding,
const char* message) {
const Shader::TextureBinding& texture_binding, const char* message) {
// TODO(benvanik): better error info/etc.
ImGui::TextColored(kColorError, "ERROR: %s", message);
}
void TraceViewer::DrawVertexFetcher(
Shader* shader, const TranslatedShader::VertexBinding& vertex_binding,
const xe_gpu_vertex_fetch_t* fetch) {
void TraceViewer::DrawVertexFetcher(Shader* shader,
const Shader::VertexBinding& vertex_binding,
const xe_gpu_vertex_fetch_t* fetch) {
const uint8_t* addr = memory_->TranslatePhysical(fetch->address << 2);
uint32_t vertex_count = (fetch->size * 4) / vertex_binding.stride_words;
int column_count = 0;
@ -1411,9 +1410,7 @@ void TraceViewer::DrawStateUI() {
if (ImGui::CollapsingHeader("Vertex Buffers")) {
auto shader = command_processor->active_vertex_shader();
if (shader) {
const auto& vertex_bindings =
shader->translated_shader()->vertex_bindings();
for (const auto& vertex_binding : vertex_bindings) {
for (const auto& vertex_binding : shader->vertex_bindings()) {
int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 +
(vertex_binding.fetch_constant / 3) * 6;
const auto group =
@ -1451,8 +1448,7 @@ void TraceViewer::DrawStateUI() {
if (ImGui::CollapsingHeader("Vertex Textures")) {
auto shader = command_processor->active_vertex_shader();
if (shader) {
const auto& texture_bindings =
shader->translated_shader()->texture_bindings();
const auto& texture_bindings = shader->texture_bindings();
if (!texture_bindings.empty()) {
for (const auto& texture_binding : texture_bindings) {
DrawTextureInfo(texture_binding);
@ -1467,8 +1463,7 @@ void TraceViewer::DrawStateUI() {
if (ImGui::CollapsingHeader("Textures")) {
auto shader = command_processor->active_pixel_shader();
if (shader) {
const auto& texture_bindings =
shader->translated_shader()->texture_bindings();
const auto& texture_bindings = shader->texture_bindings();
if (!texture_bindings.empty()) {
for (const auto& texture_binding : texture_bindings) {
DrawTextureInfo(texture_binding);

View File

@ -85,13 +85,12 @@ class TraceViewer {
void DrawBlendMode(uint32_t src_blend, uint32_t dest_blend,
uint32_t blend_op);
void DrawTextureInfo(const TranslatedShader::TextureBinding& texture_binding);
void DrawFailedTextureInfo(
const TranslatedShader::TextureBinding& texture_binding,
const char* message);
void DrawTextureInfo(const Shader::TextureBinding& texture_binding);
void DrawFailedTextureInfo(const Shader::TextureBinding& texture_binding,
const char* message);
void DrawVertexFetcher(Shader* shader,
const TranslatedShader::VertexBinding& vertex_binding,
const Shader::VertexBinding& vertex_binding,
const xenos::xe_gpu_vertex_fetch_t* fetch);
};

View File

@ -361,25 +361,23 @@ namespace shader_playground {
(shaderCode[6] << 8) | (shaderCode[7] << 0);
int wordOffset = byteOffset / 4;
uint[] swappedCode = new uint[(shaderCode.Length - wordOffset) / sizeof(uint)];
Buffer.BlockCopy(shaderCode, wordOffset * 4, swappedCode, 0, shaderCode.Length - wordOffset * 4);
for (int i = 0; i < swappedCode.Length; ++i) {
swappedCode[i] = SwapBytes(swappedCode[i]);
}
uint[] shaderDwords = new uint[(shaderCode.Length - wordOffset) / sizeof(uint)];
Buffer.BlockCopy(shaderCode, wordOffset * 4, shaderDwords, 0, shaderCode.Length - wordOffset * 4);
var sb = new StringBuilder();
sb.Append("const uint32_t shader_dwords[] = {");
for (int i = 0; i < swappedCode.Length; ++i) {
sb.AppendFormat("0x{0:X8}, ", swappedCode[i]);
for (int i = 0; i < shaderDwords.Length; ++i) {
sb.AppendFormat("0x{0:X8}, ", SwapByte(shaderDwords[i]));
}
sb.Append("};" + Environment.NewLine);
sb.Append("shader_type = ShaderType::" + (shaderType == "vs" ? "kVertex" : "kPixel") + ";" + Environment.NewLine);
UpdateTextBox(wordsTextBox, sb.ToString(), true);
wordsTextBox.SelectAll();
return swappedCode;
return shaderDwords;
}
uint SwapBytes(uint x) {
uint SwapByte(uint x) {
return ((x & 0x000000ff) << 24) +
((x & 0x0000ff00) << 8) +
((x & 0x00ff0000) >> 8) +