Files
Xenia-Canary/src/xenia/gpu/hlsl_shader_translator.cc

1792 lines
65 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2018 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/gpu/hlsl_shader_translator.h"
#include <algorithm>
#include "xenia/base/assert.h"
namespace xe {
namespace gpu {
using namespace ucode;
constexpr uint32_t kMaxInterpolators = 16;
#define EmitSource(...) source_inner_.AppendFormat(__VA_ARGS__)
#define EmitSourceDepth(...) \
source_inner_.Append(depth_prefix_); \
source_inner_.AppendFormat(__VA_ARGS__)
HlslShaderTranslator::HlslShaderTranslator() {}
HlslShaderTranslator::~HlslShaderTranslator() = default;
void HlslShaderTranslator::Reset() {
ShaderTranslator::Reset();
source_inner_.Reset();
depth_ = 0;
depth_prefix_[0] = 0;
cf_wrote_pc_ = false;
cf_exec_pred_ = false;
cf_exec_pred_cond_ = false;
writes_depth_ = false;
texture_srv_count_ = 0;
sampler_count_ = 0;
cube_used_ = false;
}
void HlslShaderTranslator::EmitTranslationError(const char* message) {
ShaderTranslator::EmitTranslationError(message);
EmitSourceDepth("// TRANSLATION ERROR: %s\n", message);
}
void HlslShaderTranslator::EmitUnimplementedTranslationError() {
ShaderTranslator::EmitUnimplementedTranslationError();
EmitSourceDepth("// UNIMPLEMENTED TRANSLATION\n");
}
void HlslShaderTranslator::Indent() {
depth_prefix_[depth_] = ' ';
depth_prefix_[depth_ + 1] = ' ';
depth_prefix_[depth_ + 2] = 0;
depth_ += 2;
}
void HlslShaderTranslator::Unindent() {
depth_ -= 2;
depth_prefix_[depth_] = 0;
}
void HlslShaderTranslator::StartTranslation() {
// Main function level (1).
Indent();
// Do while PC != 0xFFFF level (2).
Indent();
// Switch level (3).
Indent();
EmitSourceDepth("case 0u:\n");
}
std::vector<uint8_t> HlslShaderTranslator::CompleteTranslation() {
// Add the declarations, the prologue and the epilogue knowing what is needed.
StringBuffer source;
// Common preprocessor statements.
// 3557 is the "loop only executes for 1 iteration" warning caused by the
// control flow loop design.
source.Append(
"#pragma warning(disable : 3557)\n"
"\n"
"#define XE_FLT_MAX 3.402823466e+38\n"
"\n");
// Cubemap sampling. XeCubeTo2D emulates the cube vector ALU instruction that
// gives (t, s, 2 * major axis, face index), XeCubeTo3D reverts its effects
// in tfetchCube because sampling a cubemap as an array doesn't work properly
// with mipmaps (at the edges, there's a jump of S and T between 0 and 1, and
// the entire texture becomes 1x1 in size).
//
// If X is the major axis:
// T is -Y
// S is -Z for positive X, +Z for negative X
// Face is 0 for positive X, 1 for negative X
// If Y is the major axis:
// T is +Z for positive Y, -Z for negative Y
// S is +X
// Face is 2 for positive Y, 3 for negative Y
// If Z is the major axis:
// T is -Y
// S is +X for positive Z, -X for negative Z
// Face is 4 for positive Z, 5 for negative Z
// From T and S, abs(2 * MA) needs to be subtracted also.
//
// The undo function accepts (s, t, face index).
if (cube_used_) {
source.Append(
"float4 XeCubeTo2D(float3 xe_cube_3d) {\n"
" float3 xe_cube_3d_abs = abs(xe_cube_3d);\n"
" float4 xe_cube_2d;\n"
" if (xe_cube_3d_abs.x >= xe_cube_3d_abs.y &&\n"
" xe_cube_3d_abs.x >= xe_cube_3d_abs.z) {\n"
" xe_cube_2d.xy = -xe_cube_3d.yz;\n"
" xe_cube_2d.y *= sign(xe_cube_3d.x);\n"
" xe_cube_2d.z = xe_cube_3d.x;\n"
" xe_cube_2d.w = 0.0;\n"
" } else if (xe_cube_3d_abs.y >= xe_cube_3d_abs.z) {\n"
" xe_cube_2d.xyz = xe_cube_3d.zxy;\n"
" xe_cube_2d.x *= sign(xe_cube_3d.y);\n"
" xe_cube_2d.w = 2.0;\n"
" } else {\n"
" xe_cube_2d.x = -xe_cube_3d.y;\n"
" xe_cube_2d.yz = xe_cube_3d.xz;\n"
" xe_cube_2d.y *= sign(xe_cube_3d.z);\n"
" xe_cube_2d.w = 4.0;\n"
" }\n"
" xe_cube_2d.w += saturate(-sign(xe_cube_2d.y));\n"
" xe_cube_2d.z *= 2.0;\n"
" xe_cube_2d.xy -= abs(xe_cube_2d.zz);\n"
" return xe_cube_2d;\n"
"}\n"
"\n"
"float3 XeCubeTo3D(float3 xe_cube_2d) {\n"
" xe_cube_2d.xy = (xe_cube_2d.xy * 2.0) + 1.0;\n"
" float3 xe_cube_3d;\n"
" uint xe_cube_face_index = uint(xe_cube_2d.z);\n"
" float xe_cube_ma_sign =\n"
" -(float(xe_cube_face_index & 1u) * 2.0 - 1.0);\n"
" uint xe_cube_ma_index = xe_cube_face_index >> 1u;\n"
" if (xe_cube_ma_index == 0u) {\n"
" xe_cube_3d.x = xe_cube_2d.z;\n"
" xe_cube_3d.yz = -xe_cube_2d.yx;\n"
" xe_cube_3d.xz *= xe_cube_ma_sign;\n"
" } else if (xe_cube_ma_index == 1u) {\n"
" xe_cube_3d = xe_cube_2d.xzy;\n"
" xe_cube_3d.yz *= xe_cube_ma_sign;\n"
" } else {\n"
" xe_cube_3d.xz = xe_cube_2d.xz * xe_cube_ma_sign;\n"
" xe_cube_3d.y = -xe_cube_2d.y;\n"
" }\n"
" return xe_cube_3d;\n"
"}\n"
"\n");
}
// Common declarations.
// Only up to 14 constant buffers can be used on binding tiers 1 and 2.
// Bool and loop constants are quadrupled to allow dynamic indexing (constant
// registers are vectors).
source.Append(
"cbuffer xe_system_constants : register(b0) {\n"
" float3 xe_mul_rcp_w;\n"
" uint xe_vertex_base_index;\n"
" float3 xe_ndc_scale;\n"
" uint xe_vertex_index_endian;\n"
" float3 xe_ndc_offset;\n"
" float xe_pixel_half_pixel_offset;\n"
" float2 xe_ssaa_inv_scale;\n"
" uint xe_pixel_pos_reg;\n"
" uint4 xe_color_output_map;\n"
"};\n"
"\n"
"cbuffer xe_loop_bool_constants : register(b1) {\n"
" uint4 xe_bool_constants[8];\n"
" uint4 xe_loop_constants[32];\n"
"};\n"
"\n"
"cbuffer xe_fetch_constants : register(b2) {\n"
" uint4 xe_fetch[48];\n"
"};\n"
"\n"
"struct XeFloatConstantPage {\n"
" float4 c[32];\n"
"};\n"
"ConstantBuffer<XeFloatConstantPage> "
"xe_float_constants[8] : register(b3);\n"
"\n");
// Textures and samplers.
for (uint32_t i = 0; i < texture_srv_count_; ++i) {
const TextureSRV& srv = texture_srvs_[i];
const char* srv_type_dimension;
const char* srv_name_suffix;
switch (srv.dimension) {
case TextureDimension::k3D:
srv_type_dimension = "3D";
srv_name_suffix = "3d";
break;
case TextureDimension::kCube:
srv_type_dimension = "Cube";
srv_name_suffix = "cube";
break;
default:
srv_type_dimension = "2DArray";
srv_name_suffix = "2d";
break;
}
source.AppendFormat(
"Texture%s<float4> xe_texture%u_%s : register(t%u, space0);\n",
srv_type_dimension, srv.fetch_constant, srv_name_suffix, i);
}
for (uint32_t i = 0; i < sampler_count_; ++i) {
source.AppendFormat("SamplerState xe_sampler%u : register(s%u);\n",
sampler_fetch_constants_[i], i);
}
if (texture_srv_count_ != 0 || sampler_count_ != 0) {
source.Append("\n");
}
if (is_vertex_shader()) {
// Vertex fetching, output and prologue.
// Endian register (2nd word of the fetch constant) is 00 for no swap, 01
// for 8-in-16, 10 for 8-in-32 (a combination of 8-in-16 and 16-in-32), and
// 11 for 16-in-32. This means we can check bits 0 ^ 1 to see if we need to
// do a 8-in-16 swap, and bit 1 to see if a 16-in-32 swap is needed.
// Vertex element is a temporary integer value for fetches.
// -1 point size means the geometry shader will use the global setting by
// default.
source.AppendFormat(
"ByteAddressBuffer xe_shared_memory : register(t0, space1);\n"
"\n"
"#define XE_BYTE_SWAP_OVERLOAD(XeByteSwapType) \\\n"
"XeByteSwapType XeByteSwap(XeByteSwapType v, uint endian) { \\\n"
" [flatten] if (((endian ^ (endian >> 1u)) & 1u) != 0u) { \\\n"
" v = ((v & 0x00FF00FFu) << 8u) | ((v & 0xFF00FF00u) >> 8u); \\\n"
" } \\\n"
" [flatten] if ((endian & 2u) != 0u) { \\\n"
" v = (v << 16u) | (v >> 16u); \\\n"
" } \\\n"
" return v; \\\n"
"}\n"
"XE_BYTE_SWAP_OVERLOAD(uint)\n"
"XE_BYTE_SWAP_OVERLOAD(uint2)\n"
"XE_BYTE_SWAP_OVERLOAD(uint3)\n"
"XE_BYTE_SWAP_OVERLOAD(uint4)\n"
"\n"
"struct XeVertexShaderOutput {\n"
" float4 position : SV_Position;\n"
" float4 interpolators[%u] : TEXCOORD;\n"
" float point_size : PSIZE;\n"
"};\n"
"\n"
"XeVertexShaderOutput main(uint xe_vertex_index_be : SV_VertexID) {\n"
" float4 xe_r[%u];\n"
" uint xe_vertex_index =\n"
" XeByteSwap(xe_vertex_index_be, xe_vertex_index_endian) +\n"
" xe_vertex_base_index;\n"
" uint4 xe_vertex_element;\n"
" xe_r[0].r = float(xe_vertex_index);\n"
" XeVertexShaderOutput xe_output;\n"
" xe_output.position = float4(0.0, 0.0, 0.0, 1.0);\n"
" xe_output.point_size = -1.0;\n",
kMaxInterpolators, register_count());
for (uint32_t i = 0; i < kMaxInterpolators; ++i) {
source.AppendFormat(" xe_output.interpolators[%u] = (0.0).xxxx;\n", i);
}
// TODO(Triang3l): Reset interpolators to zero if really needed.
} else if (is_pixel_shader()) {
// Pixel shader inputs, outputs and prologue.
// If the shader writes to depth, it needs to define
// XE_PIXEL_SHADER_WRITES_DEPTH in the beginning of the final output.
source.AppendFormat(
"struct XePixelShaderInput {\n"
" float4 position : SV_Position;\n"
" float4 interpolators[%u] : TEXCOORD;\n"
"};\n"
"\n"
"struct XePixelShaderOutput {\n"
" float4 colors[4] : SV_Target;\n"
"%s"
"};\n"
"\n"
"XePixelShaderOutput main(XePixelShaderInput xe_input) {\n"
" float4 xe_r[%u];\n"
" XePixelShaderOutput xe_output;\n"
" float4 xe_color_output[4];\n"
" xe_color_output[0] = (0.0).xxxx;\n"
" xe_color_output[1] = (0.0).xxxx;\n"
" xe_color_output[2] = (0.0).xxxx;\n"
" xe_color_output[3] = (0.0).xxxx;\n",
kMaxInterpolators, writes_depth_ ? " float depth : SV_Depth;\n" : "",
register_count());
// Initialize SV_Depth if using it.
if (writes_depth_) {
source.Append(" xe_output.depth = xe_input.position.z;\n");
}
// Copy interpolants to the first registers.
uint32_t interpolator_register_count =
std::min(register_count(), kMaxInterpolators);
for (uint32_t i = 0; i < interpolator_register_count; ++i) {
source.AppendFormat(" xe_r[%u] = xe_input.interpolators[%u];\n", i, i);
}
// TODO(Triang3l): ps_param_gen.
}
// Common main function variables and prologue.
source.Append(
// Dynamic index for source operands (mainly for float and bool constants
// since they are indexed in two parts).
" uint xe_src_index;\n"
// Sources for instructions.
" float4 xe_src0, xe_src1, xe_src2;\n"
// Previous vector result (used as a scratch).
" float4 xe_pv = float4(0.0, 0.0, 0.0, 0.0);\n"
// Previous scalar result (used for RETAIN_PREV).
" float xe_ps = 0.0;\n"
// Predicate temp, clause-local. Initially false like cf_exec_pred_cond_.
" bool xe_p0 = false;\n"
// Address register when using absolute addressing.
" int xe_a0 = 0;\n"
// Loop index stack - .x is the active loop, shifted right to yzw on push.
" int4 xe_aL = int4(0, 0, 0, 0);\n"
// Loop counter stack, .x is the active loop.
// Represents number of times remaining to loop.
" uint4 xe_loop_count = uint4(0u, 0u, 0u, 0u);\n"
// Coordinates for texture fetches.
" float3 xe_texture_coords;\n"
// In 3D texture fetch instructions, whether the current texture is 3D as
// opposed to a 2D array.
" bool xe_texture_is_3d;\n"
// Size shift and offset for 3D or array textures.
" uint4 xe_tfetch3d_size_bits;\n"
// Explicit LOD for texture fetches.
" float xe_texture_lod = 0.0;\n"
// Explicit gradients for texture fetches.
" float3 xe_texture_grad_h = float3(0.0, 0.0, 0.0);\n"
" float3 xe_texture_grad_v = float3(0.0, 0.0, 0.0);\n"
// Master loop and switch for flow control.
" uint xe_pc = 0u;\n"
"\n"
" do {\n"
" switch (xe_pc) {\n");
// Translated code.
source.Append(source_inner_.GetString());
// Epilogue.
if (!cf_wrote_pc_) {
source.Append(
" xe_pc = 0xFFFFu;\n"
" break;\n");
}
source.Append(
" default:\n"
" xe_pc = 0xFFFFu;\n"
" break;\n"
" }\n"
" } while (xe_pc != 0xFFFFu);\n");
if (is_vertex_shader()) {
// Restore the original W if the shader has already taken its reciprocal,
// and restore the original XYZ if the shader has divided them by W. Also
// normalize the coordinates to the viewport if the shader has returned
// unnormalized ones (for rectangle lists, for instance) and apply the half-
// pixel offset.
source.Append(
" [flatten] if (xe_mul_rcp_w.z == 0.0) {\n"
" xe_output.position.w = rcp(xe_output.position.w);\n"
" }\n"
" xe_output.position.xyz *=\n"
" lerp((1.0).xxx, xe_output.position.www, xe_mul_rcp_w.xxy);\n"
" xe_output.position.xyz =\n"
" xe_output.position.xyz * xe_ndc_scale +\n"
" xe_ndc_offset * xe_output.position.www;\n");
} else if (is_pixel_shader()) {
// Remap guest color outputs to host render targets because null render
// target descriptors are broken.
source.Append(
" xe_output.colors[0] = xe_color_output[xe_color_output_map.r];\n"
" xe_output.colors[1] = xe_color_output[xe_color_output_map.g];\n"
" xe_output.colors[2] = xe_color_output[xe_color_output_map.b];\n"
" xe_output.colors[3] = xe_color_output[xe_color_output_map.a];\n");
}
// TODO(Triang3l): Window offset, half pixel offset, alpha test, gamma.
source.Append(
" return xe_output;\n"
"}\n");
return source.ToBytes();
}
void HlslShaderTranslator::ProcessLabel(uint32_t cf_index) {
// 0 is always added in the beginning.
if (cf_index != 0) {
if (!cf_wrote_pc_) {
EmitSourceDepth(" xe_pc = %uu;\n", cf_index);
EmitSourceDepth(" break;\n");
}
EmitSourceDepth("case %uu:\n", cf_index);
cf_wrote_pc_ = false;
}
}
void HlslShaderTranslator::ProcessControlFlowNopInstruction(uint32_t cf_index) {
EmitSourceDepth("// cnop\n");
}
void HlslShaderTranslator::ProcessControlFlowInstructionBegin(
uint32_t cf_index) {
if (cf_wrote_pc_) {
// In case there are instructions after setting the PC and breaking (if
// there's an `if` setting the PC, there's an `else` setting the PC as well
// for falling through).
EmitSourceDepth("case %uu:\n", cf_index);
cf_wrote_pc_ = false;
}
Indent();
}
void HlslShaderTranslator::ProcessControlFlowInstructionEnd(uint32_t cf_index) {
if (!cf_wrote_pc_) {
EmitSourceDepth("// Falling through to L%u\n", cf_index + 1);
}
Unindent();
}
void HlslShaderTranslator::ProcessExecInstructionBegin(
const ParsedExecInstruction& instr) {
EmitSourceDepth("// ");
instr.Disassemble(&source_inner_);
cf_exec_pred_ = false;
switch (instr.type) {
case ParsedExecInstruction::Type::kUnconditional:
EmitSourceDepth("{\n");
break;
case ParsedExecInstruction::Type::kConditional:
EmitSourceDepth("if ((xe_bool_constants[%u].x & (1u << %uu)) %c= 0u) {\n",
instr.bool_constant_index >> 5,
instr.bool_constant_index & 31,
instr.condition ? '!' : '=');
break;
case ParsedExecInstruction::Type::kPredicated:
cf_exec_pred_ = true;
cf_exec_pred_cond_ = instr.condition;
EmitSourceDepth("if (%cxe_p0) {\n", instr.condition ? ' ' : '!');
break;
}
Indent();
}
void HlslShaderTranslator::ProcessExecInstructionEnd(
const ParsedExecInstruction& instr) {
Unindent();
EmitSourceDepth("}\n");
if (instr.is_end) {
EmitSourceDepth("xe_pc = 0xFFFFu;\n");
EmitSourceDepth("break;\n");
cf_wrote_pc_ = true;
}
}
void HlslShaderTranslator::ProcessLoopStartInstruction(
const ParsedLoopStartInstruction& instr) {
EmitSourceDepth("// ");
instr.Disassemble(&source_inner_);
// Setup counter.
EmitSourceDepth("xe_loop_count.yzw = xe_loop_count.xyz;\n");
EmitSourceDepth("xe_loop_count.x = xe_loop_constants[%u].x & 0xFFu;\n",
instr.loop_constant_index);
// Setup relative indexing.
EmitSourceDepth("xe_aL = xe_aL.xxyz;\n");
if (!instr.is_repeat) {
// Push new loop starting index if not reusing the current one.
EmitSourceDepth("xe_aL.x = int((xe_loop_constants[%u].x >> 8u) & 0xFFu);\n",
instr.loop_constant_index);
}
// Quick skip loop if zero count.
EmitSourceDepth("if (xe_loop_count.x == 0u) {\n");
EmitSourceDepth(" xe_pc = %uu; // Skip loop to L%u\n",
instr.loop_skip_address, instr.loop_skip_address);
EmitSourceDepth("} else {\n");
EmitSourceDepth(" xe_pc = %uu; // Fallthrough to loop body L%u\n",
instr.dword_index + 1, instr.dword_index + 1);
EmitSourceDepth("}\n");
EmitSourceDepth("break;\n");
cf_wrote_pc_ = true;
}
void HlslShaderTranslator::ProcessLoopEndInstruction(
const ParsedLoopEndInstruction& instr) {
EmitSourceDepth("// ");
instr.Disassemble(&source_inner_);
// Decrement loop counter, and if we are done break out.
EmitSourceDepth("if (--xe_loop_count.x == 0u");
if (instr.is_predicated_break) {
// If the predicate condition is met we 'break;' out of the loop.
// Need to restore stack and fall through to the next cf.
EmitSource(" || %cxe_p0) {\n", instr.predicate_condition ? ' ' : '!');
} else {
EmitSource(") {\n");
}
Indent();
// Loop completed - pop and fall through to next cf.
EmitSourceDepth("xe_loop_count.xyz = xe_loop_count.yzw;\n");
EmitSourceDepth("xe_loop_count.w = 0u;\n");
EmitSourceDepth("xe_aL.xyz = xe_aL.yzw;\n");
EmitSourceDepth("xe_aL.w = 0;\n");
EmitSourceDepth("xe_pc = %uu; // Exit loop to L%u\n", instr.dword_index + 1,
instr.dword_index + 1);
Unindent();
EmitSourceDepth("} else {\n");
Indent();
// Still looping. Adjust index and jump back to body.
EmitSourceDepth("xe_aL.x += int(xe_loop_constants[%u].x << 8u) >> 24;\n",
instr.loop_constant_index);
EmitSourceDepth("xe_pc = %uu; // Loop back to body L%u\n",
instr.loop_body_address, instr.loop_body_address);
Unindent();
EmitSourceDepth("}\n");
EmitSourceDepth("break;\n");
cf_wrote_pc_ = true;
}
void HlslShaderTranslator::ProcessCallInstruction(
const ParsedCallInstruction& instr) {
EmitSourceDepth("// ");
instr.Disassemble(&source_inner_);
EmitUnimplementedTranslationError();
}
void HlslShaderTranslator::ProcessReturnInstruction(
const ParsedReturnInstruction& instr) {
EmitSourceDepth("// ");
instr.Disassemble(&source_inner_);
EmitUnimplementedTranslationError();
}
void HlslShaderTranslator::ProcessJumpInstruction(
const ParsedJumpInstruction& instr) {
EmitSourceDepth("// ");
instr.Disassemble(&source_inner_);
bool conditional = false;
switch (instr.type) {
case ParsedJumpInstruction::Type::kUnconditional:
break;
case ParsedJumpInstruction::Type::kConditional:
EmitSourceDepth("if ((xe_bool_constants[%u].x & (1u << %uu)) %c= 0u) {\n",
instr.bool_constant_index >> 5,
instr.bool_constant_index & 31,
instr.condition ? '!' : '=');
conditional = true;
break;
case ParsedJumpInstruction::Type::kPredicated:
EmitSourceDepth("if (%cxe_p0) {\n", instr.condition ? ' ' : '!');
conditional = true;
break;
}
if (conditional) {
Indent();
}
EmitSourceDepth("xe_pc = %uu; // L%u\n", instr.target_address,
instr.target_address);
if (conditional) {
Unindent();
uint32_t next_address = instr.dword_index + 1;
EmitSourceDepth("} else {\n");
EmitSourceDepth(" xe_pc = %uu; // Fallthrough to L%u\n", next_address,
next_address);
EmitSourceDepth("}\n");
}
EmitSourceDepth("break;\n");
cf_wrote_pc_ = true;
}
void HlslShaderTranslator::ProcessAllocInstruction(
const ParsedAllocInstruction& instr) {
EmitSourceDepth("// ");
instr.Disassemble(&source_inner_);
}
bool HlslShaderTranslator::BeginPredicatedInstruction(
bool is_predicated, bool predicate_condition) {
if (is_predicated &&
(!cf_exec_pred_ || cf_exec_pred_cond_ != predicate_condition)) {
EmitSourceDepth("if (%cxe_p0) {\n", predicate_condition ? ' ' : '!');
Indent();
return true;
}
return false;
}
void HlslShaderTranslator::EndPredicatedInstruction(bool conditional_emitted) {
if (conditional_emitted) {
Unindent();
EmitSourceDepth("}\n");
}
}
void HlslShaderTranslator::EmitLoadOperand(size_t src_index,
const InstructionOperand& op) {
// If indexing dynamically, emit the index because float and bool constants
// need to be indexed in two parts.
// Also verify we are not using vertex/texture fetch constants here.
uint32_t storage_index_max;
switch (op.storage_source) {
case InstructionStorageSource::kRegister:
storage_index_max = 127;
break;
case InstructionStorageSource::kConstantFloat:
case InstructionStorageSource::kConstantBool:
storage_index_max = 255;
break;
case InstructionStorageSource::kConstantInt:
storage_index_max = 31;
break;
default:
assert_always();
return;
}
if (op.storage_addressing_mode ==
InstructionStorageAddressingMode::kAddressAbsolute) {
EmitSourceDepth("xe_src_index = uint(%u + xe_a0) & %uu;\n",
op.storage_index, storage_index_max);
} else if (op.storage_addressing_mode ==
InstructionStorageAddressingMode::kAddressRelative) {
EmitSourceDepth("xe_src_index = uint(%u + xe_aL.x) & %uu;\n",
op.storage_index, storage_index_max);
}
// Negation and abs are store modifiers, so they're applied after swizzling.
EmitSourceDepth("xe_src%u = ", uint32_t(src_index));
if (op.is_negated) {
EmitSource("-");
}
if (op.is_absolute_value) {
EmitSource("abs");
}
EmitSource("(");
if (op.storage_addressing_mode == InstructionStorageAddressingMode::kStatic) {
switch (op.storage_source) {
case InstructionStorageSource::kRegister:
EmitSource("xe_r[%u]", op.storage_index);
break;
case InstructionStorageSource::kConstantFloat:
EmitSource("xe_float_constants[%u].c[%u]", op.storage_index >> 5,
op.storage_index & 31);
break;
case InstructionStorageSource::kConstantInt:
EmitSource("xe_loop_constants[%u].x", op.storage_index);
break;
case InstructionStorageSource::kConstantBool:
EmitSource("float((xe_bool_constants[%u].x >> %uu) & 1u)",
op.storage_index >> 5, op.storage_index & 31);
break;
default:
assert_always();
break;
}
} else {
switch (op.storage_source) {
case InstructionStorageSource::kRegister:
EmitSource("xe_r[xe_src_index]");
break;
case InstructionStorageSource::kConstantFloat:
EmitSource(
"xe_float_constants[xe_src_index >> 5u].c[xe_src_index & 31u]");
break;
case InstructionStorageSource::kConstantInt:
EmitSource("xe_loop_constants[xe_src_index].x");
break;
case InstructionStorageSource::kConstantBool:
EmitSource(
"float((xe_bool_constants[xe_src_index >> 5u].x >> "
"(xe_src_index & 31u)) & 1u)");
break;
default:
assert_always();
break;
}
}
EmitSource(")");
// Integer and bool constants are scalar, can't swizzle them.
if (op.storage_source == InstructionStorageSource::kConstantInt ||
op.storage_source == InstructionStorageSource::kConstantBool) {
EmitSource(".xxxx");
} else {
if (!op.is_standard_swizzle()) {
EmitSource(".");
// For 1 component stores it will be .aaaa, for 2 components it's .abbb.
for (int i = 0; i < op.component_count; ++i) {
EmitSource("%c", GetCharForSwizzle(op.components[i]));
}
for (int i = op.component_count; i < 4; ++i) {
EmitSource("%c",
GetCharForSwizzle(op.components[op.component_count - 1]));
}
}
}
EmitSource(";\n");
}
void HlslShaderTranslator::EmitStoreResult(const InstructionResult& result,
bool source_is_scalar) {
bool storage_is_scalar =
result.storage_target == InstructionStorageTarget::kPointSize ||
result.storage_target == InstructionStorageTarget::kDepth;
if (storage_is_scalar) {
if (!result.write_mask[0]) {
return;
}
} else {
if (!result.has_any_writes()) {
return;
}
}
bool storage_is_array = false;
switch (result.storage_target) {
case InstructionStorageTarget::kRegister:
EmitSourceDepth("xe_r");
storage_is_array = true;
break;
case InstructionStorageTarget::kInterpolant:
EmitSourceDepth("xe_output.interpolators");
storage_is_array = true;
break;
case InstructionStorageTarget::kPosition:
EmitSourceDepth("xe_output.position");
break;
case InstructionStorageTarget::kPointSize:
EmitSourceDepth("xe_output.point_size");
break;
case InstructionStorageTarget::kColorTarget:
EmitSourceDepth("xe_color_output");
storage_is_array = true;
break;
case InstructionStorageTarget::kDepth:
EmitSourceDepth("xe_output.depth");
writes_depth_ = true;
break;
default:
case InstructionStorageTarget::kNone:
return;
}
if (storage_is_array) {
switch (result.storage_addressing_mode) {
case InstructionStorageAddressingMode::kStatic:
EmitSource("[%u]", result.storage_index);
break;
case InstructionStorageAddressingMode::kAddressAbsolute:
EmitSource("[%u + xe_a0]", result.storage_index);
break;
case InstructionStorageAddressingMode::kAddressRelative:
EmitSource("[%u + xe_aL.x]", result.storage_index);
break;
}
}
if (storage_is_scalar) {
EmitSource(" = ");
switch (result.components[0]) {
case SwizzleSource::k0:
EmitSource("0.0");
break;
case SwizzleSource::k1:
EmitSource("1.0");
break;
default:
if (result.is_clamped) {
EmitSource("saturate(");
}
if (source_is_scalar) {
EmitSource("xe_ps");
} else {
EmitSource("xe_pv.%c", GetCharForSwizzle(result.components[0]));
}
if (result.is_clamped) {
EmitSource(")");
}
break;
}
} else {
bool has_const_writes = false;
uint32_t component_write_count;
if (result.is_standard_swizzle()) {
component_write_count = 4;
} else {
component_write_count = 0;
EmitSource(".");
for (uint32_t i = 0; i < 4; ++i) {
if (result.write_mask[i]) {
if (result.components[i] == SwizzleSource::k0 ||
result.components[i] == SwizzleSource::k1) {
has_const_writes = true;
}
++component_write_count;
EmitSource("%c", GetCharForSwizzle(GetSwizzleFromComponentIndex(i)));
}
}
}
EmitSource(" = ");
if (result.is_clamped) {
EmitSource("saturate(");
}
if (has_const_writes) {
if (component_write_count > 1) {
EmitSource("float%u(", component_write_count);
}
bool has_written = false;
for (uint32_t i = 0; i < 4; ++i) {
if (result.write_mask[i]) {
if (has_written) {
EmitSource(", ");
}
has_written = true;
switch (result.components[i]) {
case SwizzleSource::k0:
EmitSource("0.0");
break;
case SwizzleSource::k1:
EmitSource("1.0");
break;
default:
if (source_is_scalar) {
EmitSource("xe_ps");
} else {
EmitSource("xe_pv.%c", GetCharForSwizzle(result.components[i]));
}
break;
}
}
}
if (component_write_count > 1) {
EmitSource(")");
}
} else {
if (source_is_scalar) {
EmitSource("xe_ps");
if (component_write_count > 1) {
EmitSource(".x");
if (component_write_count > 2) {
EmitSource("x");
if (component_write_count > 3) {
EmitSource("x");
}
}
}
} else {
EmitSource("xe_pv");
if (!result.is_standard_swizzle()) {
EmitSource(".");
for (uint32_t i = 0; i < 4; ++i) {
if (result.write_mask[i]) {
EmitSource("%c", GetCharForSwizzle(result.components[i]));
}
}
}
}
}
if (result.is_clamped) {
EmitSource(")");
}
}
EmitSource(";\n");
}
void HlslShaderTranslator::ProcessVertexFetchInstruction(
const ParsedVertexFetchInstruction& instr) {
EmitSourceDepth("// ");
instr.Disassemble(&source_inner_);
if (instr.operand_count < 2 ||
instr.operands[1].storage_source !=
InstructionStorageSource::kVertexFetchConstant) {
assert_always();
return;
}
bool conditional_emitted = BeginPredicatedInstruction(
instr.is_predicated, instr.predicate_condition);
// Load the element from the shared memory as uints and swap.
EmitLoadOperand(0, instr.operands[0]);
uint32_t vfetch_index = instr.operands[1].storage_index;
const char* load_swizzle;
const char* load_function_suffix;
switch (instr.attributes.data_format) {
case VertexFormat::k_16_16_16_16:
case VertexFormat::k_16_16_16_16_FLOAT:
case VertexFormat::k_32_32:
case VertexFormat::k_32_32_FLOAT:
load_swizzle = ".xy";
load_function_suffix = "2";
break;
case VertexFormat::k_32_32_32_FLOAT:
load_swizzle = ".xyz";
load_function_suffix = "3";
break;
case VertexFormat::k_32_32_32_32:
case VertexFormat::k_32_32_32_32_FLOAT:
load_swizzle = "";
load_function_suffix = "4";
break;
default:
load_swizzle = ".x";
load_function_suffix = "";
break;
}
EmitSourceDepth("xe_vertex_element%s = XeByteSwap(xe_shared_memory.Load%s(\n",
load_swizzle, load_function_suffix);
EmitSourceDepth(" (xe_fetch[%uu].%c & 0x1FFFFFFCu)", vfetch_index >> 1,
(vfetch_index & 1) ? 'z' : 'x');
if (instr.attributes.stride != 0) {
EmitSource(" + uint(xe_src0.x) * %uu", instr.attributes.stride * 4);
}
if (instr.attributes.offset != 0) {
EmitSource(" + %uu", instr.attributes.offset * 4);
}
EmitSource("),\n");
EmitSourceDepth(" xe_fetch[%uu].%c);\n", vfetch_index >> 1,
(vfetch_index & 1) ? 'w' : 'y');
// Convert to the target format.
uint32_t component_count = 4;
switch (instr.attributes.data_format) {
case VertexFormat::k_8_8_8_8:
EmitSourceDepth("xe_vertex_element = (xe_vertex_element.xxxx >>\n");
EmitSourceDepth(" uint4(0u, 8u, 16u, 24u)) & 255u;\n");
if (instr.attributes.is_signed) {
EmitSourceDepth(
"xe_pv = float4(int4(xe_vertex_element << 24u) >> 24);\n");
} else {
EmitSourceDepth("xe_pv = float4(xe_vertex_element);\n");
}
if (!instr.attributes.is_integer) {
if (instr.attributes.is_signed) {
EmitSourceDepth("xe_pv = max(xe_pv * (1.0 / 127.0), (-1.0).xxxx);\n");
} else {
EmitSourceDepth("xe_pv *= 1.0 / 255.0;\n");
}
}
break;
case VertexFormat::k_2_10_10_10:
EmitSourceDepth("xe_vertex_element = (xe_vertex_element.xxxx >>\n");
EmitSourceDepth(
" uint4(0u, 10u, 20u, 30u)) & uint4((1023u).xxx, 3u);\n");
if (instr.attributes.is_signed) {
EmitSourceDepth("xe_pv = float4(int4(xe_vertex_element <<\n");
EmitSourceDepth(" uint4((22u).xxx, 30u)) >> int4((22).xxx, 30));\n");
} else {
EmitSourceDepth("xe_pv = float4(xe_vertex_element);\n");
}
if (!instr.attributes.is_integer) {
if (instr.attributes.is_signed) {
EmitSourceDepth("xe_pv = max(xe_pv * float4((1.0 / 511.0).xxx, 1.0), "
"(-1.0).xxxx);\n");
} else {
EmitSourceDepth("xe_pv *= float4((1.0 / 1023.0).xxx, 1.0 / 3.0);\n");
}
}
break;
case VertexFormat::k_10_11_11:
component_count = 3;
EmitSourceDepth("xe_vertex_element.xyz = (xe_vertex_element.xxx >>\n");
EmitSourceDepth(
" uint3(0u, 11u, 22u)) & uint3(2047u, 2047u, 1023u);\n");
if (instr.attributes.is_signed) {
EmitSourceDepth("xe_pv.xyz = float3(int3(xe_vertex_element.xyz <<\n");
EmitSourceDepth(" uint3(21u, 21u, 22u)) >> int3(21, 21, 22));\n");
} else {
EmitSourceDepth("xe_pv.xyz = float3(xe_vertex_element.xyz);\n");
}
if (!instr.attributes.is_integer) {
if (instr.attributes.is_signed) {
EmitSourceDepth("xe_pv.xyz = max(xe_pv.xyz *\n");
EmitSourceDepth(
" float3((1.0 / 1023.0).xx, 1.0 / 511.0), (-1.0).xxx);\n");
} else {
EmitSourceDepth(
"xe_pv.xyz *= float3((1.0 / 2047.0).xx, 1.0 / 1023.0);\n");
}
}
EmitSourceDepth("xe_pv.w = 1.0;\n");
break;
case VertexFormat::k_11_11_10:
component_count = 3;
EmitSourceDepth("xe_vertex_element.xyz = (xe_vertex_element.xxx >>\n");
EmitSourceDepth(
" uint3(0u, 10u, 21u)) & uint3(1023u, 2047u, 2047u);\n");
if (instr.attributes.is_signed) {
EmitSourceDepth("xe_pv.xyz = float3(int3(xe_vertex_element.xyz <<\n");
EmitSourceDepth(" uint3(22u, 21u, 21u)) >> int3(22, 21, 21));\n");
} else {
EmitSourceDepth("xe_pv.xyz = float3(xe_vertex_element.xyz);\n");
}
if (!instr.attributes.is_integer) {
if (instr.attributes.is_signed) {
EmitSourceDepth("xe_pv.xyz = max(xe_pv.xyz *\n");
EmitSourceDepth(
" float3(1.0 / 511.0, (1.0 / 1023.0).xx), (-1.0).xxx);\n");
} else {
EmitSourceDepth(
"xe_pv.xyz *= float3(1.0 / 1023.0, (1.0 / 2047.0).xx);\n");
}
}
EmitSourceDepth("xe_pv.w = 1.0;\n");
break;
case VertexFormat::k_16_16:
component_count = 2;
EmitSourceDepth("xe_vertex_element.xy = (xe_vertex_element.xx >>\n");
EmitSourceDepth(" uint2(0u, 16u)) & 65535u;\n");
if (instr.attributes.is_signed) {
EmitSourceDepth(
"xe_pv.xy = float2(int2(xe_vertex_element.xy << 16u) >> 16);\n");
} else {
EmitSourceDepth("xe_pv.xy = float2(xe_vertex_element.xy);\n");
}
if (!instr.attributes.is_integer) {
if (instr.attributes.is_signed) {
EmitSourceDepth(
"xe_pv.xy = max(xe_pv.xy * (1.0 / 32767.0), (-1.0).xx);\n");
} else {
EmitSourceDepth("xe_pv.xy *= 1.0 / 65535.0;\n");
}
}
EmitSourceDepth("xe_pv.zw = float2(0.0, 1.0);\n");
break;
case VertexFormat::k_16_16_16_16:
EmitSourceDepth("xe_vertex_element = (xe_vertex_element.xxxx >>\n");
EmitSourceDepth(" uint4(0u, 16u, 0u, 16u)) & 65535u;\n");
if (instr.attributes.is_signed) {
EmitSourceDepth(
"xe_pv = float4(int4(xe_vertex_element << 16u) >> 16);\n");
} else {
EmitSourceDepth("xe_pv = float4(xe_vertex_element);\n");
}
if (!instr.attributes.is_integer) {
if (instr.attributes.is_signed) {
EmitSourceDepth(
"xe_pv = max(xe_pv * (1.0 / 32767.0), (-1.0).xxxx);\n");
} else {
EmitSourceDepth("xe_pv *= 1.0 / 65535.0;\n");
}
}
break;
case VertexFormat::k_16_16_FLOAT:
component_count = 2;
EmitSourceDepth("xe_vertex_element.xy = (xe_vertex_element.xx >>\n");
EmitSourceDepth(" uint2(0u, 16u)) & 65535u;\n");
EmitSourceDepth("xe_pv.xy = f16tof32(xe_vertex_element.xy);\n");
EmitSourceDepth("xe_pv.zw = float2(0.0, 1.0);\n");
break;
case VertexFormat::k_16_16_16_16_FLOAT:
EmitSourceDepth("xe_vertex_element = (xe_vertex_element.xxxx >>\n");
EmitSourceDepth(" uint4(0u, 16u, 0u, 16u)) & 65535u;\n");
EmitSourceDepth("xe_pv = f16tof32(xe_vertex_element);\n");
break;
case VertexFormat::k_32:
component_count = 1;
if (instr.attributes.is_signed) {
EmitSourceDepth("xe_pv.x = float(int(xe_vertex_element.x));\n");
} else {
EmitSourceDepth("xe_pv.x = float(xe_vertex_element.x);\n");
}
if (!instr.attributes.is_integer) {
EmitSourceDepth("xe_pv.x *= asfloat(0x%Xu);\n",
instr.attributes.is_signed ? 0x30000000 : 0x2F800000);
}
EmitSourceDepth("xe_pv.yzw = float3(0.0, 0.0, 1.0);\n");
break;
case VertexFormat::k_32_32:
component_count = 2;
if (instr.attributes.is_signed) {
EmitSourceDepth("xe_pv.xy = float2(int2(xe_vertex_element.xy));\n");
} else {
EmitSourceDepth("xe_pv.xy = float2(xe_vertex_element.xy);\n");
}
if (!instr.attributes.is_integer) {
EmitSourceDepth("xe_pv.xy *= asfloat(0x%Xu);\n",
instr.attributes.is_signed ? 0x30000000 : 0x2F800000);
}
EmitSourceDepth("xe_pv.zw = float2(0.0, 1.0);\n");
break;
case VertexFormat::k_32_32_32_32:
if (instr.attributes.is_signed) {
EmitSourceDepth("xe_pv = float4(int4(xe_vertex_element));\n");
} else {
EmitSourceDepth("xe_pv = float4(xe_vertex_element);\n");
}
if (!instr.attributes.is_integer) {
EmitSourceDepth("xe_pv *= asfloat(0x%Xu);\n",
instr.attributes.is_signed ? 0x30000000 : 0x2F800000);
}
break;
case VertexFormat::k_32_FLOAT:
component_count = 1;
EmitSourceDepth("xe_pv.x = asfloat(xe_vertex_element.x);\n");
EmitSourceDepth("xe_pv.yzw = float3(0.0, 0.0, 1.0);\n");
break;
case VertexFormat::k_32_32_FLOAT:
component_count = 2;
EmitSourceDepth("xe_pv.xy = asfloat(xe_vertex_element.xy);\n");
EmitSourceDepth("xe_pv.zw = float2(0.0, 1.0);\n");
break;
case VertexFormat::k_32_32_32_32_FLOAT:
EmitSourceDepth("xe_pv = asfloat(xe_vertex_element);\n");
break;
case VertexFormat::k_32_32_32_FLOAT:
component_count = 3;
EmitSourceDepth("xe_pv.xyz = asfloat(xe_vertex_element.xyz);\n");
EmitSourceDepth("xe_pv.w = 1.0;\n");
break;
}
// TODO(Triang3l): Check if the exponent bias needs to be added to unfetched
// components as well (and check if W should actually be 1 in this case).
if (instr.attributes.exp_adjust != 0) {
EmitSourceDepth("xe_pv");
if (component_count != 4) {
EmitSource(".x");
if (component_count >= 2) {
EmitSource("y");
if (component_count >= 3) {
EmitSource("z");
}
}
}
EmitSource(" *= asfloat(0x%Xu);\n",
0x3F800000 + (instr.attributes.exp_adjust << 23));
}
EmitStoreResult(instr.result, false);
EndPredicatedInstruction(conditional_emitted);
}
uint32_t HlslShaderTranslator::AddTextureSRV(uint32_t fetch_constant,
TextureDimension dimension) {
if (dimension == TextureDimension::k1D) {
// 1D textures are treated as 2D.
dimension = TextureDimension::k2D;
}
for (uint32_t i = 0; i < texture_srv_count_; ++i) {
const TextureSRV& srv = texture_srvs_[i];
if (srv.fetch_constant == fetch_constant && srv.dimension == dimension) {
return i;
}
}
TextureSRV& new_srv = texture_srvs_[texture_srv_count_];
new_srv.fetch_constant = fetch_constant;
new_srv.dimension = dimension;
return texture_srv_count_++;
}
uint32_t HlslShaderTranslator::AddSampler(uint32_t fetch_constant) {
for (uint32_t i = 0; i < sampler_count_; ++i) {
if (sampler_fetch_constants_[i] == fetch_constant) {
return i;
}
}
sampler_fetch_constants_[sampler_count_] = fetch_constant;
return sampler_count_++;
}
void HlslShaderTranslator::ProcessTextureFetchInstruction(
const ParsedTextureFetchInstruction& instr) {
EmitSourceDepth("// ");
instr.Disassemble(&source_inner_);
bool conditional_emitted = BeginPredicatedInstruction(
instr.is_predicated, instr.predicate_condition);
for (size_t i = 0; i < instr.operand_count; ++i) {
if (instr.operands[i].storage_source !=
InstructionStorageSource::kTextureFetchConstant) {
EmitLoadOperand(i, instr.operands[i]);
}
}
bool store_result = true;
uint32_t tfetch_index = instr.operands[1].storage_index;
// Fetch constants are laid out like:
// tf0[0] tf0[1] tf0[2] tf0[3]
// tf0[4] tf0[5] tf1[0] tf1[1]
// tf1[2] tf1[3] tf1[4] tf1[5]
uint32_t tfetch_pair_offset = (tfetch_index >> 1) * 3;
if (instr.opcode == FetchOpcode::kGetTextureGradients) {
EmitSourceDepth("xe_pv = float4(ddx(xe_src0.xy), ddy(xe_src0.xy)).xzyw;\n");
// Exponent bias is in dword 4 bits 22:26 and 27:31.
char tfetch_grad_exp_component = (tfetch_index & 1) ? 'z' : 'x';
EmitSourceDepth("xe_pv *= exp2(float(int(xe_fetch[%uu].%c%c <<\n",
tfetch_pair_offset + 1 + (tfetch_index & 1),
tfetch_grad_exp_component, tfetch_grad_exp_component);
EmitSourceDepth(" uint2(5u, 0u)) >> 27)).xyxy;\n");
} else if (instr.opcode == FetchOpcode::kSetTextureLod) {
EmitSourceDepth("xe_texture_lod = xe_src0.x;\n");
store_result = false;
} else if (instr.opcode == FetchOpcode::kSetTextureGradientsHorz) {
EmitSourceDepth("xe_texture_grad_h = xe_src0.xyz;\n");
store_result = false;
} else if (instr.opcode == FetchOpcode::kSetTextureGradientsVert) {
EmitSourceDepth("xe_texture_grad_v = xe_src0.xyz;\n");
store_result = false;
} else if (instr.opcode == FetchOpcode::kGetTextureBorderColorFrac) {
EmitUnimplementedTranslationError();
EmitSourceDepth("xe_pv = (0.0).xxxx;\n");
} else {
AddTextureSRV(tfetch_index, instr.dimension);
if (instr.dimension == TextureDimension::k3D) {
// 3D or 2D array is selected dynamically.
AddTextureSRV(tfetch_index, TextureDimension::k2D);
}
// TODO(Triang3l): Filter and LOD bias overrides.
AddSampler(tfetch_index);
// Treat 1D and 2D textures as 2D arrays, also make unnormalized and, if
// needed, apply the offset. Size is in dword 2.
uint32_t tfetch_size_index = tfetch_pair_offset + (tfetch_index & 1) * 2;
char tfetch_size_component = (tfetch_index & 1) ? 'x' : 'z';
switch (instr.dimension) {
case TextureDimension::k1D:
EmitSourceDepth("xe_texture_coords = float3(xe_src0.x, 0.0, 0.0);\n");
if (instr.attributes.unnormalized_coordinates) {
if (instr.attributes.offset_x != 0.0f) {
EmitSourceDepth("xe_texture_coords.x += %.1f;\n",
instr.attributes.offset_x);
}
EmitSourceDepth("xe_texture_coords.x /=\n");
EmitSourceDepth(" float((xe_fetch[%uu].%c & 8191u) + 1u);\n",
tfetch_size_index, tfetch_size_component);
} else if (instr.attributes.offset_x != 0.0f) {
EmitSourceDepth("xe_texture_coords.x += %.1f /\n",
instr.attributes.offset_x);
EmitSourceDepth(" float((xe_fetch[%uu].%c & 8191u) + 1u);\n",
tfetch_size_index, tfetch_size_component);
}
break;
case TextureDimension::k2D:
case TextureDimension::kCube:
// Cubemap coordinates are similar to array texture coordinates on the
// Xbox 360, not a 3D direction, so offset is applied on a plane.
if (instr.dimension == TextureDimension::kCube) {
EmitSourceDepth("xe_texture_coords = xe_src0.xyz;\n");
} else {
EmitSourceDepth("xe_texture_coords = float3(xe_src0.xy, 0.0);\n");
}
if (instr.attributes.unnormalized_coordinates) {
if (instr.attributes.offset_x != 0.0f ||
instr.attributes.offset_y != 0.0f) {
EmitSourceDepth("xe_texture_coords.xy += float2(%.1f, %.1f);\n",
instr.attributes.offset_x,
instr.attributes.offset_y);
}
EmitSourceDepth("xe_texture_coords.xy /=\n");
EmitSourceDepth(" float2(((xe_fetch[%uu].%c%c >>\n",
tfetch_size_index, tfetch_size_component,
tfetch_size_component);
EmitSourceDepth(" uint2(0u, 13u)) & 8191u) + 1u);\n");
} else if (instr.attributes.offset_x != 0.0f ||
instr.attributes.offset_y != 0.0f) {
EmitSourceDepth("xe_texture_coords.xy += float2(%.1f, %.1f) /\n",
instr.attributes.offset_x, instr.attributes.offset_y);
EmitSourceDepth(" float2(((xe_fetch[%uu].%c%c >>\n",
tfetch_size_index, tfetch_size_component,
tfetch_size_component);
EmitSourceDepth(" uint2(0u, 13u)) & 8191u) + 1u);\n");
}
if (instr.dimension == TextureDimension::kCube) {
// Convert to 3D direction.
EmitSourceDepth(
"xe_texture_coords = XeCubeTo3D(xe_texture_coords);\n");
cube_used_ = true;
}
break;
case TextureDimension::k3D:
// Both 3D textures and 2D arrays have their Z coordinate normalized
// according to the "Next-Generation Graphics Programming on Xbox 360"
// presentation, however, on PC, array elements have unnormalized
// indices. We first take the normalized coordinates, but then multiply
// Z by the number of array slices.
EmitSourceDepth(
"xe_texture_is_3d = (xe_fetch[%uu].%c & 0x600u) == 0x400u;\n",
tfetch_pair_offset + 1 + (tfetch_index & 1),
(tfetch_index & 1) ? 'w' : 'y');
EmitSourceDepth("xe_texture_coords = xe_src0.xyz;\n");
if (instr.attributes.unnormalized_coordinates) {
if (instr.attributes.offset_x != 0.0f ||
instr.attributes.offset_y != 0.0f ||
instr.attributes.offset_z != 0.0f) {
EmitSourceDepth("xe_texture_coords += float3(%.1f, %.1f, %.1f);\n",
instr.attributes.offset_x,
instr.attributes.offset_y,
instr.attributes.offset_z);
}
EmitSourceDepth("xe_tfetch3d_size_bits = xe_texture_is_3d ?\n");
EmitSourceDepth(
" uint4(0u, 11u, 22u, 2047u) : uint4(0u, 13u, 26u, 8191u);\n");
EmitSourceDepth("xe_texture_coords /=\n");
EmitSourceDepth(" float3(((xe_fetch[%uu].%c%c%c >>\n",
tfetch_size_index, tfetch_size_component,
tfetch_size_component, tfetch_size_component);
EmitSourceDepth(" xe_tfetch3d_size_bits.xyz) &\n");
EmitSourceDepth(" xe_tfetch3d_size_bits.w) + 1u);\n");
} else if (instr.attributes.offset_x != 0.0f ||
instr.attributes.offset_y != 0.0f ||
instr.attributes.offset_z != 0.0f) {
EmitSourceDepth("xe_tfetch3d_size_bits = xe_texture_is_3d ?\n");
EmitSourceDepth(
" uint4(0u, 11u, 22u, 2047u) : uint4(0u, 13u, 26u, 8191u);\n");
EmitSourceDepth("xe_texture_coords += float3(%.1f, %.1f, %.1f) /\n",
instr.attributes.offset_x, instr.attributes.offset_y,
instr.attributes.offset_z);
EmitSourceDepth(" float3(((xe_fetch[%uu].%c%c%c >>\n",
tfetch_size_index, tfetch_size_component,
tfetch_size_component, tfetch_size_component);
EmitSourceDepth(" xe_tfetch3d_size_bits.xyz) &\n");
EmitSourceDepth(" xe_tfetch3d_size_bits.w) + 1u);\n");
}
// Unnormalize Z if sampling an array.
EmitSourceDepth("if (!xe_texture_is_3d) {\n");
EmitSourceDepth(
" xe_texture_coords.z *= float((xe_fetch[%uu].%c >> 26u) + 1u);\n",
tfetch_size_index, tfetch_size_component);
EmitSourceDepth("};\n");
break;
default:
assert_unhandled_case(instr.dimension);
}
if (instr.opcode == FetchOpcode::kTextureFetch) {
switch (instr.dimension) {
case TextureDimension::k3D:
EmitSourceDepth("[branch] if (xe_texture_is_3d) {\n");
if (instr.attributes.use_register_lod) {
EmitSourceDepth(
" xe_pv = xe_texture%u_3d.SampleLevel(xe_sampler%u,\n",
tfetch_index, tfetch_index);
EmitSourceDepth(" xe_texture_coords, xe_texture_lod);\n");
} else if (instr.attributes.use_register_gradients) {
EmitSourceDepth(
" xe_pv = xe_texture%u_3d.SampleGrad(xe_sampler%u,\n",
tfetch_index, tfetch_index);
EmitSourceDepth(" xe_texture_coords, xe_texture_grad_h,\n");
EmitSourceDepth(" xe_texture_grad_v);\n");
} else {
EmitSourceDepth(" xe_pv = xe_texture%u_3d.Sample(xe_sampler%u,\n",
tfetch_index, tfetch_index);
EmitSourceDepth(" xe_texture_coords);\n");
}
EmitSourceDepth("} else {\n");
if (instr.attributes.use_register_lod) {
EmitSourceDepth(
" xe_pv = xe_texture%u_2d.SampleLevel(xe_sampler%u,\n",
tfetch_index, tfetch_index);
EmitSourceDepth(" xe_texture_coords, xe_texture_lod);\n");
} else if (instr.attributes.use_register_gradients) {
EmitSourceDepth(
" xe_pv = xe_texture%u_2d.SampleGrad(xe_sampler%u,\n",
tfetch_index, tfetch_index);
EmitSourceDepth(" xe_texture_coords, xe_texture_grad_h.xy,\n");
EmitSourceDepth(" xe_texture_grad_v.xy);\n");
} else {
EmitSourceDepth(" xe_pv = xe_texture%u_2d.Sample(xe_sampler%u,\n",
tfetch_index, tfetch_index);
EmitSourceDepth(" xe_texture_coords);\n");
}
EmitSourceDepth("}\n");
break;
case TextureDimension::kCube:
// TODO(Triang3l): Investigate how explicit gradients should work with
// cubemaps (if they should work at all) because due to 2D->3D
// coordinate conversion, you probably can't just sent the gradients
// to SampleGrad.
if (instr.attributes.use_register_lod) {
EmitSourceDepth(
"xe_pv = xe_texture%u_cube.SampleLevel(xe_sampler%u,\n",
tfetch_index, tfetch_index);
EmitSourceDepth(" xe_texture_coords, xe_texture_lod);\n");
} else {
EmitSourceDepth("xe_pv = xe_texture%u_cube.Sample(xe_sampler%u,\n",
tfetch_index, tfetch_index);
EmitSourceDepth(" xe_texture_coords);\n");
}
break;
default:
if (instr.attributes.use_register_lod) {
EmitSourceDepth(
"xe_pv = xe_texture%u_2d.SampleLevel(xe_sampler%u,\n",
tfetch_index, tfetch_index);
EmitSourceDepth(" xe_texture_coords, xe_texture_lod);\n");
} else if (instr.attributes.use_register_gradients) {
EmitSourceDepth(
"xe_pv = xe_texture%u_2d.SampleGrad(xe_sampler%u,\n",
tfetch_index, tfetch_index);
EmitSourceDepth(" xe_texture_coords, xe_texture_grad_h.xy,\n");
EmitSourceDepth(" xe_texture_grad_v.xy);\n");
} else {
EmitSourceDepth("xe_pv = xe_texture%u_2d.Sample(xe_sampler%u,\n",
tfetch_index, tfetch_index);
EmitSourceDepth(" xe_texture_coords);\n");
}
break;
}
// Apply exponent bias from dword 3.
EmitSourceDepth(
"xe_pv *= exp2(float(int(xe_fetch[%uu].%c << 13u) >> 26));\n",
tfetch_pair_offset + (tfetch_index & 1) * 2,
(tfetch_index & 1) ? 'y' : 'w');
} else if (instr.opcode == FetchOpcode::kGetTextureComputedLod) {
// TODO(Triang3l): Add FetchOpcode::kGetTextureComputedLod via
// CalculateLevelOfDetail.
EmitUnimplementedTranslationError();
EmitSourceDepth("xe_pv = (0.0).xxxx;\n");
} else if (instr.opcode == FetchOpcode::kGetTextureWeights) {
// TODO(Triang3l): Add FetchOpcode::kGetTextureWeights.
EmitUnimplementedTranslationError();
EmitSourceDepth("xe_pv = (0.0).xxxx;\n");
}
}
if (store_result) {
EmitStoreResult(instr.result, false);
}
EndPredicatedInstruction(conditional_emitted);
}
void HlslShaderTranslator::ProcessVectorAluInstruction(
const ParsedAluInstruction& instr) {
bool conditional_emitted = BeginPredicatedInstruction(
instr.is_predicated, instr.predicate_condition);
for (size_t i = 0; i < instr.operand_count; ++i) {
EmitLoadOperand(i, instr.operands[i]);
}
switch (instr.vector_opcode) {
case AluVectorOpcode::kAdd:
EmitSourceDepth("xe_pv = xe_src0 + xe_src1;\n");
break;
case AluVectorOpcode::kMul:
EmitSourceDepth("xe_pv = xe_src0 * xe_src1;\n");
break;
case AluVectorOpcode::kMax:
EmitSourceDepth("xe_pv = max(xe_src0, xe_src1);\n");
break;
case AluVectorOpcode::kSeq:
EmitSourceDepth("xe_pv = float4(xe_src0 == xe_src1);\n");
break;
case AluVectorOpcode::kSgt:
EmitSourceDepth("xe_pv = float4(xe_src0 > xe_src1);\n");
break;
case AluVectorOpcode::kSge:
EmitSourceDepth("xe_pv = float4(xe_src0 >= xe_src1);\n");
break;
case AluVectorOpcode::kSne:
EmitSourceDepth("xe_pv = float4(xe_src0 != xe_src1);\n");
break;
case AluVectorOpcode::kFrc:
EmitSourceDepth("xe_pv = frac(xe_src0);\n");
break;
case AluVectorOpcode::kTrunc:
EmitSourceDepth("xe_pv = trunc(xe_src0);\n");
break;
case AluVectorOpcode::kFloor:
EmitSourceDepth("xe_pv = floor(xe_src0);\n");
break;
case AluVectorOpcode::kMad:
EmitSourceDepth("xe_pv = xe_src0 * xe_src1 + xe_src2;\n");
break;
case AluVectorOpcode::kCndEq:
EmitSourceDepth(
"xe_pv = lerp(xe_src2, xe_src1, float4(xe_src0 == (0.0).xxxx));\n");
break;
case AluVectorOpcode::kCndGe:
EmitSourceDepth(
"xe_pv = lerp(xe_src2, xe_src1, float4(xe_src0 >= (0.0).xxxx));\n");
break;
case AluVectorOpcode::kCndGt:
EmitSourceDepth(
"xe_pv = lerp(xe_src2, xe_src1, float4(xe_src0 > (0.0).xxxx));\n");
break;
case AluVectorOpcode::kDp4:
EmitSourceDepth("xe_pv = dot(xe_src0, xe_src1).xxxx;\n");
break;
case AluVectorOpcode::kDp3:
EmitSourceDepth("xe_pv = dot(xe_src0.xyz, xe_src1.xyz).xxxx;\n");
break;
case AluVectorOpcode::kDp2Add:
EmitSourceDepth(
"xe_pv = (dot(xe_src0.xy, xe_src1.xy) + xe_src2.x).xxxx;\n");
break;
case AluVectorOpcode::kCube:
EmitSourceDepth("xe_pv = XeCubeTo2D(xe_src0.xyz);\n");
cube_used_ = true;
break;
case AluVectorOpcode::kMax4:
EmitSourceDepth("xe_pv.xy = max(xe_src0.xy, xe_src0.zw);\n");
EmitSourceDepth("xe_pv.xxxx = max(xe_pv.x, xe_pv.y);\n");
break;
case AluVectorOpcode::kSetpEqPush:
cf_exec_pred_ = false;
EmitSourceDepth("xe_p0 = xe_src0.w == 0.0 && xe_src1.w == 0.0;\n");
EmitSourceDepth("xe_pv = (xe_src0.x == 0.0 && xe_src1.x == 0.0 ? "
"0.0 : xe_src0.x + 1.0).xxxx;\n");
break;
case AluVectorOpcode::kSetpNePush:
cf_exec_pred_ = false;
EmitSourceDepth("xe_p0 = xe_src0.w == 0.0 && xe_src1.w != 0.0;\n");
EmitSourceDepth("xe_pv = (xe_src0.x == 0.0 && xe_src1.x != 0.0 ? "
"0.0 : xe_src0.x + 1.0).xxxx;\n");
break;
case AluVectorOpcode::kSetpGtPush:
cf_exec_pred_ = false;
EmitSourceDepth("xe_p0 = xe_src0.w == 0.0 && xe_src1.w > 0.0;\n");
EmitSourceDepth("xe_pv = (xe_src0.x == 0.0 && xe_src1.x > 0.0 ? "
"0.0 : xe_src0.x + 1.0).xxxx;\n");
break;
case AluVectorOpcode::kSetpGePush:
cf_exec_pred_ = false;
EmitSourceDepth("xe_p0 = xe_src0.w == 0.0 && xe_src1.w >= 0.0;\n");
EmitSourceDepth("xe_pv = (xe_src0.x == 0.0 && xe_src1.x >= 0.0 ? "
"0.0 : xe_src0.x + 1.0).xxxx;\n");
break;
case AluVectorOpcode::kKillEq:
EmitSourceDepth("xe_pv.xxxx = float(any(xe_src0 == xe_src1));\n");
EmitSourceDepth("clip(-xe_pv.x);\n");
break;
case AluVectorOpcode::kKillGt:
EmitSourceDepth("xe_pv.xxxx = float(any(xe_src0 > xe_src1));\n");
EmitSourceDepth("clip(-xe_pv.x);\n");
break;
case AluVectorOpcode::kKillGe:
EmitSourceDepth("xe_pv.xxxx = float(any(xe_src0 >= xe_src1));\n");
EmitSourceDepth("clip(-xe_pv.x);\n");
break;
case AluVectorOpcode::kKillNe:
EmitSourceDepth("xe_pv.xxxx = float(any(xe_src0 != xe_src1));\n");
EmitSourceDepth("clip(-xe_pv.x);\n");
break;
case AluVectorOpcode::kDst:
EmitSourceDepth("xe_pv.x = 1.0;\n");
EmitSourceDepth("xe_pv.y = xe_src0.y * xe_src1.y;\n");
EmitSourceDepth("xe_pv.z = xe_src0.z;\n");
EmitSourceDepth("xe_pv.w = xe_src1.w;\n");
break;
case AluVectorOpcode::kMaxA:
EmitSourceDepth("xe_a0 = clamp(int(round(xe_src0.w)), -256, 255);\n");
EmitSourceDepth("xe_pv = max(xe_src0, xe_src1);\n");
break;
}
EmitStoreResult(instr.result, false);
EndPredicatedInstruction(conditional_emitted);
}
void HlslShaderTranslator::ProcessScalarAluInstruction(
const ParsedAluInstruction& instr) {
bool conditional_emitted = BeginPredicatedInstruction(
instr.is_predicated, instr.predicate_condition);
for (size_t i = 0; i < instr.operand_count; ++i) {
EmitLoadOperand(i, instr.operands[i]);
}
switch (instr.scalar_opcode) {
case AluScalarOpcode::kAdds:
EmitSourceDepth("xe_ps = xe_src0.x + xe_src0.y;\n");
break;
case AluScalarOpcode::kAddsPrev:
EmitSourceDepth("xe_ps += xe_src0.x;\n");
break;
case AluScalarOpcode::kMuls:
EmitSourceDepth("xe_ps = xe_src0.x * xe_src0.y;\n");
break;
case AluScalarOpcode::kMulsPrev:
EmitSourceDepth("xe_ps *= xe_src0.x;\n");
break;
case AluScalarOpcode::kMulsPrev2:
EmitSourceDepth(
"xe_ps = (xe_ps == -XE_FLT_MAX || (isinf(xe_ps) && xe_ps < 0.0)\n");
EmitSourceDepth(
" || isnan(xe_ps) || xe_src0.y <= 0.0 || isnan(xe_src0.y)) ?\n");
EmitSourceDepth(" -XE_FLT_MAX : xe_src0.x * xe_ps;\n");
break;
case AluScalarOpcode::kMaxs:
EmitSourceDepth("xe_ps = max(xe_src0.x, xe_src0.y);\n");
break;
case AluScalarOpcode::kMins:
EmitSourceDepth("xe_ps = max(xe_src0.x, xe_src0.y);\n");
break;
case AluScalarOpcode::kSeqs:
EmitSourceDepth("xe_ps = float(xe_src0.x == 0.0);\n");
break;
case AluScalarOpcode::kSgts:
EmitSourceDepth("xe_ps = float(xe_src0.x > 0.0);\n");
break;
case AluScalarOpcode::kSges:
EmitSourceDepth("xe_ps = float(xe_src0.x >= 0.0);\n");
break;
case AluScalarOpcode::kSnes:
EmitSourceDepth("xe_ps = float(xe_src0.x != 0.0);\n");
break;
case AluScalarOpcode::kFrcs:
EmitSourceDepth("xe_ps = frac(xe_src0.x);\n");
break;
case AluScalarOpcode::kTruncs:
EmitSourceDepth("xe_ps = trunc(xe_src0.x);\n");
break;
case AluScalarOpcode::kFloors:
EmitSourceDepth("xe_ps = floor(xe_src0.x);\n");
break;
case AluScalarOpcode::kExp:
EmitSourceDepth("xe_ps = exp2(xe_src0.x);\n");
break;
case AluScalarOpcode::kLogc:
EmitSourceDepth("xe_ps = log2(xe_src0.x);\n");
EmitSourceDepth(
"xe_ps = (isinf(xe_ps) && xe_ps < 0.0) ? -XE_FLT_MAX : ps;\n");
break;
case AluScalarOpcode::kLog:
EmitSourceDepth("xe_ps = log2(xe_src0.x);\n");
break;
case AluScalarOpcode::kRcpc:
EmitSourceDepth(
"xe_ps = clamp(rcp(xe_src0.x), -XE_FLT_MAX, XE_FLT_MAX);\n");
break;
case AluScalarOpcode::kRcpf:
EmitSourceDepth("xe_ps = rcp(xe_src0.x);\n");
EmitSourceDepth("xe_ps *= float(!isinf(xe_ps));\n");
break;
case AluScalarOpcode::kRcp:
EmitSourceDepth("xe_ps = rcp(xe_src0.x);\n");
break;
case AluScalarOpcode::kRsqc:
EmitSourceDepth(
"xe_ps = clamp(rsqrt(xe_src0.x), -XE_FLT_MAX, XE_FLT_MAX);\n");
break;
case AluScalarOpcode::kRsqf:
EmitSourceDepth("xe_ps = rsqrt(xe_src0.x);\n");
EmitSourceDepth("xe_ps *= float(!isinf(xe_ps));\n");
break;
case AluScalarOpcode::kRsq:
EmitSourceDepth("xe_ps = rsqrt(xe_src0.x);\n");
break;
case AluScalarOpcode::kMaxAs:
EmitSourceDepth("xe_a0 = clamp(int(round(xe_src0.x)), -256, 255);\n");
EmitSourceDepth("xe_ps = max(xe_src0.x, xe_src0.y);\n");
break;
case AluScalarOpcode::kMaxAsf:
EmitSourceDepth("xe_a0 = clamp(int(floor(xe_src0.x)), -256, 255);\n");
EmitSourceDepth("xe_ps = max(xe_src0.x, xe_src0.y);\n");
break;
case AluScalarOpcode::kSubs:
EmitSourceDepth("xe_ps = xe_src0.x - xe_src0.y;\n");
break;
case AluScalarOpcode::kSubsPrev:
EmitSourceDepth("xe_ps = xe_src0.x - xe_ps;\n");
break;
case AluScalarOpcode::kSetpEq:
cf_exec_pred_ = false;
EmitSourceDepth("xe_p0 = xe_src0.x == 0.0;\n");
EmitSourceDepth("xe_ps = float(!xe_p0);\n");
break;
case AluScalarOpcode::kSetpNe:
cf_exec_pred_ = false;
EmitSourceDepth("xe_p0 = xe_src0.x != 0.0;\n");
EmitSourceDepth("xe_ps = float(!xe_p0);\n");
break;
case AluScalarOpcode::kSetpGt:
cf_exec_pred_ = false;
EmitSourceDepth("xe_p0 = xe_src0.x > 0.0;\n");
EmitSourceDepth("xe_ps = float(!xe_p0);\n");
break;
case AluScalarOpcode::kSetpGe:
cf_exec_pred_ = false;
EmitSourceDepth("xe_p0 = xe_src0.x >= 0.0;\n");
EmitSourceDepth("xe_ps = float(!xe_p0);\n");
break;
case AluScalarOpcode::kSetpInv:
cf_exec_pred_ = false;
EmitSourceDepth("xe_p0 = xe_src0.x == 1.0;\n");
EmitSourceDepth(
"xe_ps = float(!xe_p0) * (xe_src0.x == 0.0 ? 1.0 : xe_src0.x);\n");
break;
case AluScalarOpcode::kSetpPop:
cf_exec_pred_ = false;
EmitSourceDepth("xe_ps = max(xe_src0.x - 1.0, 0.0);\n");
EmitSourceDepth("xe_p0 = xe_ps == 0.0;\n");
break;
case AluScalarOpcode::kSetpClr:
cf_exec_pred_ = false;
EmitSourceDepth("xe_ps = false;\n");
EmitSourceDepth("xe_p0 = XE_FLT_MAX;\n");
break;
case AluScalarOpcode::kSetpRstr:
cf_exec_pred_ = false;
EmitSourceDepth("xe_p0 = src0.x == 0.0;\n");
EmitSourceDepth("xe_ps = src0.x;\n");
break;
case AluScalarOpcode::kKillsEq:
EmitSourceDepth("xe_ps = float(xe_src0.x == 0.0);\n");
EmitSourceDepth("clip(-xe_ps);\n");
break;
case AluScalarOpcode::kKillsGt:
EmitSourceDepth("xe_ps = float(xe_src0.x > 0.0);\n");
EmitSourceDepth("clip(-xe_ps);\n");
break;
case AluScalarOpcode::kKillsGe:
EmitSourceDepth("xe_ps = float(xe_src0.x >= 0.0);\n");
EmitSourceDepth("clip(-xe_ps);\n");
break;
case AluScalarOpcode::kKillsNe:
EmitSourceDepth("xe_ps = float(xe_src0.x != 0.0);\n");
EmitSourceDepth("clip(-xe_ps);\n");
break;
case AluScalarOpcode::kKillsOne:
EmitSourceDepth("xe_ps = float(xe_src0.x == 1.0);\n");
EmitSourceDepth("clip(-xe_ps);\n");
break;
case AluScalarOpcode::kSqrt:
EmitSourceDepth("xe_ps = float(xe_src0.x);\n");
break;
case AluScalarOpcode::kMulsc0:
case AluScalarOpcode::kMulsc1:
EmitSourceDepth("xe_ps = xe_src0.x * xe_src1.x;\n");
break;
case AluScalarOpcode::kAddsc0:
case AluScalarOpcode::kAddsc1:
EmitSourceDepth("xe_ps = xe_src0.x + xe_src1.x;\n");
break;
case AluScalarOpcode::kSubsc0:
case AluScalarOpcode::kSubsc1:
EmitSourceDepth("xe_ps = xe_src0.x - xe_src1.x;\n");
break;
case AluScalarOpcode::kSin:
EmitSourceDepth("xe_ps = sin(xe_src0.x);\n");
break;
case AluScalarOpcode::kCos:
EmitSourceDepth("xe_ps = cos(xe_src0.x);\n");
break;
case AluScalarOpcode::kRetainPrev:
break;
}
EmitStoreResult(instr.result, true);
EndPredicatedInstruction(conditional_emitted);
}
void HlslShaderTranslator::ProcessAluInstruction(
const ParsedAluInstruction& instr) {
EmitSourceDepth("// ");
instr.Disassemble(&source_inner_);
switch (instr.type) {
case ParsedAluInstruction::Type::kNop:
break;
case ParsedAluInstruction::Type::kVector:
ProcessVectorAluInstruction(instr);
break;
case ParsedAluInstruction::Type::kScalar:
ProcessScalarAluInstruction(instr);
break;
}
}
} // namespace gpu
} // namespace xe