/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #ifndef XENIA_GPU_UCODE_H_ #define XENIA_GPU_UCODE_H_ #include "xenia/gpu/xenos.h" // The XNA Game Studio 3.1 contains Graphics.ShaderCompiler.AssembleFromSource, // which, for TargetPlatform.Xbox360, can validate and assemble Xbox 360 shader // microcode from Xbox 360 and Direct3D 9 shader assembly, returning the binary, // as well as validation warnings and errors and the disassembly via the warning // output. It is the primary source of information about the binary encoding of // the instructions, as well as valid usage of instruction parameters and // sequences. // https://www.microsoft.com/en-us/download/details.aspx?id=39 // (XNAGS31_setup.exe) // Xenia provides a tool, tools/shader-playground, that invokes the assembler, // displays the binary and the disassembly from the official assembler, and also // shows the disassembly generated by Xenia, and passes it back to the assembler // to validate Xenia's microcode parsing and disassembly by checking if // reassembling the disassembly results in the same binary. // // The behavior and the parameters of some of the instructions were previously // documented on MSDN in the XNA Game Studio programming guide: // http://web.archive.org/web/20081211005537/http://msdn.microsoft.com/en-us/library/bb313877.aspx // // A great amount of documentation, such as the R400 sequencer specification and // the official emulator code, was made available during the LG Electronics, // Inc. v. ATI Technologies ULC "Multi-thread Graphics Processing System" patent // dispute IPR2015-00325, with the motion to seal having been denied due to "a // strong public policy interest in making all information filed in an inter // partes review publicly available". Most of the documents attached, however, // cover early versions - the development process - of the R400 architecture, so // there are some differences from the final Xenos GPU (DOT2ADDv is defined // differently, for example, and MUL/ADD/SUB_CONST are missing). // https://portal.unifiedpatents.com/ptab/case/IPR2015-00325 // // Also, the R600, while having a different 5-scalar, as opposed to vec4|scalar, // parallelism model and instruction encodings and targeting Direct3D 10 rather // that 9, inherits a lot of instructions and architectural concepts from the // R400. // https://www.x.org/docs/AMD/old/r600isa.pdf // https://developer.amd.com/wordpress/media/2012/10/r600isa.pdf // https://developer.amd.com/wordpress/media/2012/10/R600_Instruction_Set_Architecture.pdf // Parts of this code also come from the freedreno project: // https://github.com/freedreno/freedreno/blob/master/includes/instr-a2xx.h /* * Copyright (c) 2012 Rob Clark * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ // Only 32-bit types (uint32_t, int32_t or enums with uint32_t / int32_t as the // underlying type) are allowed in the bit fields here, as Visual C++ restarts // packing when a field requires different alignment than the previous one. namespace xe { namespace gpu { namespace ucode { // Defines control flow opcodes used to schedule instructions. enum class ControlFlowOpcode : uint32_t { // No-op - used to fill space. kNop = 0, // Executes fetch or ALU instructions. kExec = 1, // Executes fetch or ALU instructions then ends execution. kExecEnd = 2, // Conditionally executes based on a bool const. // PredicateClean = false. kCondExec = 3, // Conditionally executes based on a bool const then ends execution. // PredicateClean = false. // According to the IPR2015-00325 sequencer specification, execution ends only // if the condition is actually met (unlike on the R600, where execution ends // unconditionally if END_OF_PROGRAM is set in the control flow instruction). // 4D5307ED has many shaders (used, in particular, in the Press Start screen // background) with if / else in its tail done via cexece - cexece b130, then // cexece !b130, and then an empty exece (if the condition was ignored, the // second cexece would have never been reached). Also, the validator reports // "Shader will try to execute instruction 3.0, but last possible instruction // is 2.1" for a shader that contains just one cexece without an exece. kCondExecEnd = 4, // Conditionally executes based on the current predicate. // Since 64 vertices or pixels are processed by each sequencer in the Xenos // hardware, the actual condition is AND of the predicate values for all // active (and not killed) invocations for (!p0) exec, and OR of them for // (p0) exec - if any of the invocations passes the predicate check, all of // them will enter the exec. This is according to the IPR2015-00325 sequencer // specification. Because of this, the compiler makes the ALU and fetch // instructions themselves inside a predicated exec predicated as well. The // validator also reports mismatch between the control flow predication and // ALU / fetch predication. kCondExecPred = 5, // Conditionally executes based on the current predicate then ends execution. // According to the IPR2015-00325 sequencer specification, execution ends only // if the condition is actually met for any of the invocations (unlike on the // R600, where execution ends unconditionally if END_OF_PROGRAM is set in the // control flow instruction). kCondExecPredEnd = 6, // Starts a loop that must be terminated with kLoopEnd, with depth of up to 4. kLoopStart = 7, // Continues or breaks out of a loop started with kLoopStart. According to the // IPR2015-00325 sequencer specification, the incrementing of the loop // iteration happens before the count and the predicated break checks. kLoopEnd = 8, // Conditionally calls a function, with depth of up to 4. // A return address is pushed to the stack to be used by a kReturn. kCondCall = 9, // Returns from the current function as called by kCondCall. // This is a no-op if not in a function. kReturn = 10, // Conditionally jumps to an arbitrary address based on a bool const. kCondJmp = 11, // Allocates output values. kAlloc = 12, // Conditionally executes based on a bool const. // PredicateClean = true. // This is cexec with a bool constant (kCondExec, can be verified by comparing // the XNA disassembler output with cexec containing and not containing a setp // instruction), not a kCondExecPred. kCondExec doesn't have a predicate clean // field (the space is occupied by the bool constant index), while // kCondExecPred itself does. This is unlike what the IPR2015-00325 sequencer // specification says about the Conditional_Execute_Predicates_No_Stall // instruction using this opcode (in the specification, this is described as // behaving like kCondExecPred with PredicateClean = false, but the // specification is likely highly outdated - it doesn't even have predicate // clean fields in exec instructions overall). kCondExecPredClean = 13, // Conditionally executes based on a bool const then ends execution. // PredicateClean = true. kCondExecPredCleanEnd = 14, // Hints that no more vertex fetches will be performed. kMarkVsFetchDone = 15, }; // Returns true if the given control flow opcode executes ALU or fetch // instructions. constexpr bool IsControlFlowOpcodeExec(ControlFlowOpcode opcode) { return opcode == ControlFlowOpcode::kExec || opcode == ControlFlowOpcode::kExecEnd || opcode == ControlFlowOpcode::kCondExec || opcode == ControlFlowOpcode::kCondExecEnd || opcode == ControlFlowOpcode::kCondExecPred || opcode == ControlFlowOpcode::kCondExecPredEnd || opcode == ControlFlowOpcode::kCondExecPredClean || opcode == ControlFlowOpcode::kCondExecPredCleanEnd; } // Returns true if the given control flow opcode terminates the shader after // executing. constexpr bool DoesControlFlowOpcodeEndShader(ControlFlowOpcode opcode) { return opcode == ControlFlowOpcode::kExecEnd || opcode == ControlFlowOpcode::kCondExecEnd || opcode == ControlFlowOpcode::kCondExecPredEnd || opcode == ControlFlowOpcode::kCondExecPredCleanEnd; } // See the description of ControlFlowOpcode::kCondExecPredClean. constexpr bool DoesControlFlowCondExecHaveCleanPredicate( ControlFlowOpcode opcode) { return opcode == ControlFlowOpcode::kCondExecPredClean || opcode == ControlFlowOpcode::kCondExecPredCleanEnd; } // Determines whether addressing is based on a0 or aL. enum class AddressingMode : uint32_t { // Indexing into register sets is done based on aL. // This allows forms like c[aL + 5]. kRelative = 0, // Indexing into register sets is done based on a0. // This allows forms like c[a0 + 5]. kAbsolute = 1, }; // Defines the type of a ControlFlowOpcode::kAlloc instruction. // The allocation is just a size reservation and there may be multiple in a // shader. enum class AllocType : uint32_t { // ? kNone = 0, // Vertex shader exports a position. kVsPosition = 1, // Vertex shader exports interpolators. kVsInterpolators = 2, // Pixel shader exports colors. kPsColors = 2, // Memory export. kMemory = 3, }; // Instruction data for ControlFlowOpcode::kExec and kExecEnd. struct ControlFlowExecInstruction { ControlFlowOpcode opcode() const { return opcode_; } AddressingMode addressing_mode() const { return address_mode_; } // Address of the instructions to execute. uint32_t address() const { return address_; } // Number of instructions being executed. uint32_t count() const { return count_; } // Sequence bits, 2 per instruction. // [0] - ALU (0) or fetch (1), [1] - serialize. uint32_t sequence() const { return sequence_; } bool is_predicate_clean() const { return is_predicate_clean_ == 1; } // ? bool is_yield() const { return is_yield_ == 1; } private: // Word 0: (32 bits) uint32_t address_ : 12; uint32_t count_ : 3; uint32_t is_yield_ : 1; uint32_t sequence_ : 12; uint32_t vc_hi_ : 4; // Vertex cache? // Word 1: (16 bits) uint32_t vc_lo_ : 2; uint32_t : 7; // According to the description of Conditional_Execute_Predicates_No_Stall in // the IPR2015-00325 sequencer specification, the sequencer's control flow // logic will not wait for the predicate to be updated (apparently after this // exec). The compiler specifies PredicateClean=false for the exec if the // instructions inside it modify the predicate (but if the predicate set is // only a refinement of the current predicate, like in case of a nested `if`, // PredicateClean=true may still be set according to the IPR2015-00325 // sequencer specification, because the optimization would still work). uint32_t is_predicate_clean_ : 1; uint32_t : 1; AddressingMode address_mode_ : 1; ControlFlowOpcode opcode_ : 4; }; static_assert_size(ControlFlowExecInstruction, sizeof(uint32_t) * 2); // Instruction data for ControlFlowOpcode::kCondExec, kCondExecEnd, // kCondExecPredClean and kCondExecPredCleanEnd. struct ControlFlowCondExecInstruction { ControlFlowOpcode opcode() const { return opcode_; } AddressingMode addressing_mode() const { return address_mode_; } // Address of the instructions to execute. uint32_t address() const { return address_; } // Number of instructions being executed. uint32_t count() const { return count_; } // Sequence bits, 2 per instruction. // [0] - ALU (0) or fetch (1), [1] - serialize. uint32_t sequence() const { return sequence_; } // Constant index used as the conditional. uint32_t bool_address() const { return bool_address_; } // Required condition value of the comparision (true or false). bool condition() const { return condition_ == 1; } // ? bool is_yield() const { return is_yield_ == 1; } private: // Word 0: (32 bits) uint32_t address_ : 12; uint32_t count_ : 3; uint32_t is_yield_ : 1; uint32_t sequence_ : 12; uint32_t vc_hi_ : 4; // Vertex cache? // Word 1: (16 bits) uint32_t vc_lo_ : 2; uint32_t bool_address_ : 8; uint32_t condition_ : 1; AddressingMode address_mode_ : 1; ControlFlowOpcode opcode_ : 4; }; static_assert_size(ControlFlowCondExecInstruction, sizeof(uint32_t) * 2); // Instruction data for ControlFlowOpcode::kCondExecPred and kCondExecPredEnd. struct ControlFlowCondExecPredInstruction { ControlFlowOpcode opcode() const { return opcode_; } AddressingMode addressing_mode() const { return address_mode_; } // Address of the instructions to execute. uint32_t address() const { return address_; } // Number of instructions being executed. uint32_t count() const { return count_; } // Sequence bits, 2 per instruction. // [0] - ALU (0) or fetch (1), [1] - serialize. uint32_t sequence() const { return sequence_; } bool is_predicate_clean() const { return is_predicate_clean_ == 1; } // Required condition value of the comparision (true or false). bool condition() const { return condition_ == 1; } // ? bool is_yield() const { return is_yield_ == 1; } private: // Word 0: (32 bits) uint32_t address_ : 12; uint32_t count_ : 3; uint32_t is_yield_ : 1; uint32_t sequence_ : 12; uint32_t vc_hi_ : 4; // Vertex cache? // Word 1: (16 bits) uint32_t vc_lo_ : 2; uint32_t : 7; uint32_t is_predicate_clean_ : 1; uint32_t condition_ : 1; AddressingMode address_mode_ : 1; ControlFlowOpcode opcode_ : 4; }; static_assert_size(ControlFlowCondExecPredInstruction, sizeof(uint32_t) * 2); // Instruction data for ControlFlowOpcode::kLoopStart. struct ControlFlowLoopStartInstruction { ControlFlowOpcode opcode() const { return opcode_; } AddressingMode addressing_mode() const { return address_mode_; } // Target address to jump to when skipping the loop (normally points to the // instruction right after the `endloop` instruction). uint32_t address() const { return address_; } // Whether to reuse the current aL instead of reset it to loop start. bool is_repeat() const { return is_repeat_; } // Integer constant register that holds the loop parameters // (xenos::LoopConstant). uint32_t loop_id() const { return loop_id_; } private: // Word 0: (32 bits) uint32_t address_ : 13; uint32_t is_repeat_ : 1; uint32_t : 2; uint32_t loop_id_ : 5; uint32_t : 11; // Word 1: (16 bits) uint32_t : 11; AddressingMode address_mode_ : 1; ControlFlowOpcode opcode_ : 4; }; static_assert_size(ControlFlowLoopStartInstruction, sizeof(uint32_t) * 2); // Instruction data for ControlFlowOpcode::kLoopEnd. struct ControlFlowLoopEndInstruction { ControlFlowOpcode opcode() const { return opcode_; } AddressingMode addressing_mode() const { return address_mode_; } // Target address of the start of the loop body (normally points to the // instruction right after the `loop` instruction). uint32_t address() const { return address_; } // Integer constant register that holds the loop parameters // (xenos::LoopConstant). uint32_t loop_id() const { return loop_id_; } // Break from the loop if the predicate in all 64 invocations matches the // expected value. bool is_predicated_break() const { return is_predicated_break_; } // Required condition value of the comparision (true or false). bool condition() const { return condition_ == 1; } private: // Word 0: (32 bits) uint32_t address_ : 13; uint32_t : 3; uint32_t loop_id_ : 5; uint32_t is_predicated_break_ : 1; uint32_t : 10; // Word 1: (16 bits) uint32_t : 10; uint32_t condition_ : 1; AddressingMode address_mode_ : 1; ControlFlowOpcode opcode_ : 4; }; static_assert_size(ControlFlowLoopEndInstruction, sizeof(uint32_t) * 2); // Instruction data for ControlFlowOpcode::kCondCall. struct ControlFlowCondCallInstruction { ControlFlowOpcode opcode() const { return opcode_; } AddressingMode addressing_mode() const { return address_mode_; } // Target address. uint32_t address() const { return address_; } // Unconditional call - ignores condition/predication. bool is_unconditional() const { return is_unconditional_; } // Whether the call is predicated (or conditional). bool is_predicated() const { return is_predicated_; } // Constant index used as the conditional. uint32_t bool_address() const { return bool_address_; } // Required condition value of the comparision (true or false). bool condition() const { return condition_ == 1; } private: // Word 0: (32 bits) uint32_t address_ : 13; uint32_t is_unconditional_ : 1; uint32_t is_predicated_ : 1; uint32_t : 17; // Word 1: (16 bits) uint32_t : 2; uint32_t bool_address_ : 8; uint32_t condition_ : 1; AddressingMode address_mode_ : 1; ControlFlowOpcode opcode_ : 4; }; static_assert_size(ControlFlowCondCallInstruction, sizeof(uint32_t) * 2); // Instruction data for ControlFlowOpcode::kReturn. struct ControlFlowReturnInstruction { ControlFlowOpcode opcode() const { return opcode_; } AddressingMode addressing_mode() const { return address_mode_; } private: // Word 0: (32 bits) uint32_t : 32; // Word 1: (16 bits) uint32_t : 11; AddressingMode address_mode_ : 1; ControlFlowOpcode opcode_ : 4; }; static_assert_size(ControlFlowReturnInstruction, sizeof(uint32_t) * 2); // Instruction data for ControlFlowOpcode::kCondJmp. struct ControlFlowCondJmpInstruction { ControlFlowOpcode opcode() const { return opcode_; } AddressingMode addressing_mode() const { return address_mode_; } // Target address. uint32_t address() const { return address_; } // Unconditional jump - ignores condition/predication. bool is_unconditional() const { return is_unconditional_; } // Whether the jump is predicated (or conditional). bool is_predicated() const { return is_predicated_; } // Constant index used as the conditional. uint32_t bool_address() const { return bool_address_; } // Required condition value of the comparision (true or false). bool condition() const { return condition_ == 1; } private: // Word 0: (32 bits) uint32_t address_ : 13; uint32_t is_unconditional_ : 1; uint32_t is_predicated_ : 1; uint32_t : 17; // Word 1: (16 bits) uint32_t : 1; uint32_t direction_ : 1; uint32_t bool_address_ : 8; uint32_t condition_ : 1; AddressingMode address_mode_ : 1; ControlFlowOpcode opcode_ : 4; }; static_assert_size(ControlFlowCondJmpInstruction, sizeof(uint32_t) * 2); // Instruction data for ControlFlowOpcode::kAlloc. struct ControlFlowAllocInstruction { ControlFlowOpcode opcode() const { return opcode_; } // The total number of the given type allocated by this instruction. uint32_t size() const { return size_; } // Unconditional jump - ignores condition/predication. AllocType alloc_type() const { return alloc_type_; } private: // Word 0: (32 bits) uint32_t size_ : 3; uint32_t : 29; // Word 1: (16 bits) uint32_t : 8; uint32_t is_unserialized_ : 1; AllocType alloc_type_ : 2; uint32_t : 1; ControlFlowOpcode opcode_ : 4; }; static_assert_size(ControlFlowAllocInstruction, sizeof(uint32_t) * 2); union ControlFlowInstruction { ControlFlowOpcode opcode() const { return opcode_value; } ControlFlowExecInstruction exec; // kExec* ControlFlowCondExecInstruction cond_exec; // kCondExec* ControlFlowCondExecPredInstruction cond_exec_pred; // kCondExecPred* ControlFlowLoopStartInstruction loop_start; // kLoopStart ControlFlowLoopEndInstruction loop_end; // kLoopEnd ControlFlowCondCallInstruction cond_call; // kCondCall ControlFlowReturnInstruction ret; // kReturn ControlFlowCondJmpInstruction cond_jmp; // kCondJmp ControlFlowAllocInstruction alloc; // kAlloc struct { uint32_t dword_0; uint32_t dword_1; }; struct { uint32_t unused_0 : 32; uint32_t unused_1 : 12; ControlFlowOpcode opcode_value : 4; }; }; static_assert_size(ControlFlowInstruction, sizeof(uint32_t) * 2); inline void UnpackControlFlowInstructions(const uint32_t* dwords, ControlFlowInstruction* out_ab) { uint32_t dword_0 = dwords[0]; uint32_t dword_1 = dwords[1]; uint32_t dword_2 = dwords[2]; out_ab[0].dword_0 = dword_0; out_ab[0].dword_1 = dword_1 & 0xFFFF; out_ab[1].dword_0 = (dword_1 >> 16) | (dword_2 << 16); out_ab[1].dword_1 = dword_2 >> 16; } enum class FetchOpcode : uint32_t { kVertexFetch = 0, // http://web.archive.org/web/20090514012026/http://msdn.microsoft.com/en-us/library/bb313957.aspx // // Parameters: // - UnnormalizedTextureCoords = false (default) / true. // Only taken into account if AddressU (1D) / AddressU/V (2D) / AddressU/V/W // (3D/cube) are all set to a non-wrapping mode. To access 1D textures that // are wider than 8192 texels, unnormalized texture coordinates must be // used. // - MagFilter = point / linear / keep (default). // - MinFilter = point / linear / keep (default). // - MipFilter = point / linear / basemap (undocumented, but assembled) / keep // (default). // - VolMagFilter (3D only) - "filter used when the volume is magnified" // ("volume" as opposed to "texture") - point / linear / keep (default). // - VolMinFilter (3D only) - point / linear / keep (default). // - AnisoFilter = disabled / max1to1 / max2to1 / max4to1 / max8to1 / // max16to1 / keep (default). // - UseComputedLOD = false / true (default). // - UseRegisterLOD = false (default) / true. // - LODBias = -4.0...3.9375, in 1/16 increments. the default is 0. // - OffsetX - value added to the x-component of the texel address right // before sampling = -8.0...7.5, in 1/2 increments, the default is 0. // - OffsetY (2D, 3D and cube only) - similar to OffsetX. // - OffsetZ (3D and cube only) - similar to OffsetX. // - FetchValidOnly - performance booster, whether the data should be fetched // only for pixels inside the current primitive in a 2x2 quad (must be set // to false if the result itself is used to calculate gradients) = false / // true (default). // // Coordinates: // - 1D: U (normalized or unnormalized) // - 2D: U, V (normalized or unnormalized) // - 3D (used for both 3D and stacked 2D texture): U, V, W (normalized or // unnormalized - same for both 3D W and stack layer; also VolMagFilter / // VolMinFilter between stack layers is supported, used for color correction // in 454107DC). // - Cube: SC, TC (between 1 and 2 for normalized), face ID (0.0 to 5.0), the // cube vector ALU instruction is used to calculate them. // https://gpuopen.com/learn/fetching-from-cubes-and-octahedrons/ // "The 1.5 constant is designed such that the output face coordinate (v4 and // v5 in the above example) range is {1.0 <= x < 2.0} which has an advantage // in bit encoding compared to {0.0 <= x < 1.0} in that the upper mantissa // bits are constant throughout the entire output range." // // The total LOD for a sample is additive and is based on what is enabled. // // For cube maps, according to what texCUBEgrad compiles to in a modified // HLSL shader of 455607D1 and to XNA assembler output for PC SM3 texldd, // register gradients are in cube space (not in SC/TC space, unlike the // coordinates themselves). This isn't true for the GCN, however. // // TODO(Triang3l): Find if gradients are unnormalized for cube maps if // coordinates are unnormalized. Since texldd doesn't perform any // transformation for gradients (unlike for the coordinates themselves), // gradients are probably in cube space, which is -MA...MA, and LOD // calculation involves gradients in this space, so probably gradients // shouldn't be unnormalized. // // Adreno has only been supporting seamless cube map sampling since 3xx, so // the Xenos likely doesn't support seamless sampling: // https://developer.qualcomm.com/qfile/28557/80-nu141-1_b_adreno_opengl_es_developer_guide.pdf // // Offsets are likely applied at the LOD at which the texture is sampled (not // sure if to the higher-quality or to both - though "right before sampling" // probably means to both - in Direct3D 10, it's recommended to only use // offsets at integer mip levels, otherwise "you may get results that do not // translate well to hardware". kTextureFetch = 1, // Gets the fraction of border color that would be blended into the texture // data at the specified coordinates into the X component of the destination. // http://web.archive.org/web/20090512001222/http://msdn.microsoft.com/en-us/library/bb313945.aspx // // According to MSDN, this may take all the parameters that tfetch takes. kGetTextureBorderColorFrac = 16, // Gets the LOD for all of the pixels in the quad at the specified coordinates // into the X component of the destination. // http://web.archive.org/web/20090511233056/http://msdn.microsoft.com/en-us/library/bb313949.aspx // // According to MSDN, the only valid parameters for this are // UnnormalizedTextureCoords, AnisoFilter, VolMagFilter and VolMinFilter. // However, while XNA assembler rejects LODBias, it assembles UseComputedLOD / // UseRegisterLOD / UseRegisterGradients for it. It's unlikely that it takes // the LOD bias into account, because a getCompTexLOD + tfetch combination // with biases in both would result in double biasing (though not sure whether // grad_exp_adjust_h/v apply - in a getCompTexLOD + tfetch with // UseComputedLOD=false pair, gradient exponent adjustment is more logical to // be applied here). MipFilter also can't be overriden, the XNA assembler does // not assemble this instruction at all with MipFilter, so it's possible that // the mip filtering mode has no effect on the result (possibly should be // treated as linear - so fractional biasing can be done before rounding). // // Valid only in pixel shaders. Since the documentation says "for all of the // pixels in the quad" (with explicit gradients, this may diverge), and this // instruction doesn't assemble at all in vertex shaders, even with // UseRegisterGradients=true, it's possible that only implicit gradients may // be used in this instruction. // // Used with AnisoFilter=max16to1 in one place in the Source Engine. // // Not sure if the LOD should be clamped - probably not, considering an // out-of-range LOD passed from getCompTexLOD to setTexLOD may be biased back // into the range later. kGetTextureComputedLod = 17, // Source is 2-component. XZ = ddx(source.xy), YW = ddy(source.xy). // TODO(Triang3l): Verify whether it's coarse or fine (on Adreno 200, for // instance). This is using the texture unit, where the LOD is computed for // the whole quad (according to the Direct3D 11.3 specification), so likely // coarse; ddx / ddy from the Shader Model 4 era is also compiled by FXC to // deriv_rtx/rty_coarse when targeting Shader Model 5, and on TeraScale, // coarse / fine selection only appeared on Direct3D 11 GPUs. kGetTextureGradients = 18, // Gets the weights used in a bilinear fetch. // http://web.archive.org/web/20090511230938/http://msdn.microsoft.com/en-us/library/bb313953.aspx // X - horizontal lerp factor. // Y - vertical lerp factor. // Z - depth lerp factor. // W - mip lerp factor. // // According to MSDN, this may take all the parameters that tfetch takes. // // Takes filtering mode into account - in some games, used explicitly with // MagFilter=linear, MinFilter=linear. Source Engine explicitly uses this with // UseComputedLOD=false while only using XY of the result. Offsets and LOD // biasing also apply. Likely the factors are at the higher-quality LOD of the // pair used for filtering, though not checked. // // For cube maps, the factors are probably in SC/TC space (not sure what the // depth lerp factor means in case of them), since apparently there's no // seamless cube map sampling on the Xenos. kGetTextureWeights = 19, // Source is 1-component. kSetTextureLod = 24, // Source is 3-component. kSetTextureGradientsHorz = 25, // Source is 3-component. kSetTextureGradientsVert = 26, }; enum class FetchDestinationSwizzle { // The component indices are absolute (not relative to the component itself, // unlike in ALU operation sources). kX = 0, kY = 1, kZ = 2, kW = 3, k0 = 4, k1 = 5, // Keep the current value of the destination register (don't write). kKeep = 7, }; constexpr FetchDestinationSwizzle GetFetchDestinationComponentSwizzle( uint32_t swizzle, uint32_t component) { return FetchDestinationSwizzle((swizzle >> (3 * component)) & 0b111); } struct alignas(uint32_t) VertexFetchInstruction { FetchOpcode opcode() const { return data_.opcode_value; } // Whether the jump is predicated (or conditional). bool is_predicated() const { return data_.is_predicated; } // Required condition value of the comparision (true or false). bool predicate_condition() const { return data_.pred_condition == 1; } // Vertex fetch constant index [0-95]. // Applicable only to vfetch_full (the address from vfetch_full is reused in // vfetch_mini). uint32_t fetch_constant_index() const { return data_.const_index * 3 + data_.const_index_sel; } uint32_t dest() const { return data_.dst_reg; } uint32_t dest_swizzle() const { return data_.dst_swiz; } bool is_dest_relative() const { return data_.dst_reg_am; } // The source is applicable only to vfetch_full (the address from vfetch_full // is reused in vfetch_mini). uint32_t src() const { return data_.src_reg; } uint32_t src_swizzle() const { return data_.src_swiz; } bool is_src_relative() const { return data_.src_reg_am; } uint32_t prefetch_count() const { return data_.prefetch_count; } bool is_mini_fetch() const { return data_.is_mini_fetch == 1; } xenos::VertexFormat data_format() const { return data_.format; } // [-32, 31] int exp_adjust() const { return data_.exp_adjust; } bool is_signed() const { return data_.fomat_comp_all == 1; } bool is_normalized() const { return data_.num_format_all == 0; } xenos::SignedRepeatingFractionMode signed_rf_mode() const { return data_.signed_rf_mode_all; } // If true, the floating-point index is rounded to the nearest integer (likely // as floor(index + 0.5) because rounding to the nearest even makes no sense // for addressing, both 1.5 and 2.5 would be 2). // Otherwise, it's floored (rounded towards negative infinity). // Applicable only to vfetch_full (the address from vfetch_full is reused in // vfetch_mini). // http://web.archive.org/web/20090914055358/http://msdn.microsoft.com/en-us/library/bb313960.aspx bool is_index_rounded() const { return data_.is_index_rounded == 1; } // Dword stride, [0, 255]. // Applicable only to vfetch_full (the address from vfetch_full is reused in // vfetch_mini). uint32_t stride() const { return data_.stride; } // Dword offset, [-4194304, 4194303]. int32_t offset() const { return data_.offset; } private: struct Data { struct { FetchOpcode opcode_value : 5; uint32_t src_reg : 6; uint32_t src_reg_am : 1; uint32_t dst_reg : 6; uint32_t dst_reg_am : 1; uint32_t must_be_one : 1; uint32_t const_index : 5; uint32_t const_index_sel : 2; // Prefetch count minus 1. uint32_t prefetch_count : 3; // Absolute, one component. uint32_t src_swiz : 2; }; struct { uint32_t dst_swiz : 12; uint32_t fomat_comp_all : 1; uint32_t num_format_all : 1; xenos::SignedRepeatingFractionMode signed_rf_mode_all : 1; uint32_t is_index_rounded : 1; xenos::VertexFormat format : 6; uint32_t reserved2 : 2; int32_t exp_adjust : 6; uint32_t is_mini_fetch : 1; uint32_t is_predicated : 1; }; struct { uint32_t stride : 8; int32_t offset : 23; uint32_t pred_condition : 1; }; }; Data data_; }; static_assert_size(VertexFetchInstruction, sizeof(uint32_t) * 3); struct alignas(uint32_t) TextureFetchInstruction { FetchOpcode opcode() const { return data_.opcode_value; } // Whether the jump is predicated (or conditional). bool is_predicated() const { return data_.is_predicated; } // Required condition value of the comparision (true or false). bool predicate_condition() const { return data_.pred_condition == 1; } // Texture fetch constant index [0-31]. uint32_t fetch_constant_index() const { return data_.const_index; } uint32_t dest() const { return data_.dst_reg; } uint32_t dest_swizzle() const { return data_.dst_swiz; } bool is_dest_relative() const { return data_.dst_reg_am; } uint32_t src() const { return data_.src_reg; } uint32_t src_swizzle() const { return data_.src_swiz; } bool is_src_relative() const { return data_.src_reg_am; } xenos::FetchOpDimension dimension() const { return data_.dimension; } bool fetch_valid_only() const { return data_.fetch_valid_only == 1; } bool unnormalized_coordinates() const { return data_.tx_coord_denorm == 1; } bool has_mag_filter() const { return data_.mag_filter != xenos::TextureFilter::kUseFetchConst; } xenos::TextureFilter mag_filter() const { return data_.mag_filter; } bool has_min_filter() const { return data_.min_filter != xenos::TextureFilter::kUseFetchConst; } xenos::TextureFilter min_filter() const { return data_.min_filter; } bool has_mip_filter() const { return data_.mip_filter != xenos::TextureFilter::kUseFetchConst; } xenos::TextureFilter mip_filter() const { return data_.mip_filter; } bool has_aniso_filter() const { return data_.aniso_filter != xenos::AnisoFilter::kUseFetchConst; } xenos::AnisoFilter aniso_filter() const { return data_.aniso_filter; } bool has_vol_mag_filter() const { return data_.vol_mag_filter != xenos::TextureFilter::kUseFetchConst; } xenos::TextureFilter vol_mag_filter() const { return data_.vol_mag_filter; } bool has_vol_min_filter() const { return data_.vol_min_filter != xenos::TextureFilter::kUseFetchConst; } xenos::TextureFilter vol_min_filter() const { return data_.vol_min_filter; } bool use_computed_lod() const { return data_.use_comp_lod == 1; } bool use_register_lod() const { return data_.use_reg_lod == 1; } bool use_register_gradients() const { return data_.use_reg_gradients == 1; } xenos::SampleLocation sample_location() const { return data_.sample_location; } float lod_bias() const { // http://web.archive.org/web/20090514012026/http://msdn.microsoft.com:80/en-us/library/bb313957.aspx return data_.lod_bias * (1.0f / 16.0f); } float offset_x() const { return data_.offset_x * 0.5f; } float offset_y() const { return data_.offset_y * 0.5f; } float offset_z() const { return data_.offset_z * 0.5f; } private: struct Data { struct { FetchOpcode opcode_value : 5; uint32_t src_reg : 6; uint32_t src_reg_am : 1; uint32_t dst_reg : 6; uint32_t dst_reg_am : 1; uint32_t fetch_valid_only : 1; uint32_t const_index : 5; uint32_t tx_coord_denorm : 1; // Absolute, three components. uint32_t src_swiz : 6; }; struct { uint32_t dst_swiz : 12; xenos::TextureFilter mag_filter : 2; xenos::TextureFilter min_filter : 2; xenos::TextureFilter mip_filter : 2; xenos::AnisoFilter aniso_filter : 3; xenos::ArbitraryFilter arbitrary_filter : 3; xenos::TextureFilter vol_mag_filter : 2; xenos::TextureFilter vol_min_filter : 2; uint32_t use_comp_lod : 1; uint32_t use_reg_lod : 1; uint32_t unk : 1; uint32_t is_predicated : 1; }; struct { uint32_t use_reg_gradients : 1; xenos::SampleLocation sample_location : 1; int32_t lod_bias : 7; uint32_t unused : 5; xenos::FetchOpDimension dimension : 2; int32_t offset_x : 5; int32_t offset_y : 5; int32_t offset_z : 5; uint32_t pred_condition : 1; }; }; Data data_; }; static_assert_size(TextureFetchInstruction, sizeof(uint32_t) * 3); union alignas(uint32_t) FetchInstruction { public: FetchOpcode opcode() const { return data_.opcode_value; } // Whether the jump is predicated (or conditional). bool is_predicated() const { return data_.is_predicated; } // Required condition value of the comparision (true or false). bool predicate_condition() const { return data_.pred_condition == 1; } uint32_t dest() const { return data_.dst_reg; } uint32_t dest_swizzle() const { return data_.dst_swiz; } bool is_dest_relative() const { return data_.dst_reg_am; } uint32_t src() const { return data_.src_reg; } bool is_src_relative() const { return data_.src_reg_am; } // For FetchOpcode::kVertexFetch. const VertexFetchInstruction& vertex_fetch() const { return vertex_fetch_; } // For operations other than FetchOpcode::kVertexFetch. const TextureFetchInstruction& texture_fetch() const { return texture_fetch_; } private: struct Data { struct { FetchOpcode opcode_value : 5; uint32_t src_reg : 6; uint32_t src_reg_am : 1; uint32_t dst_reg : 6; uint32_t dst_reg_am : 1; // Specific to vertex or texture fetch. uint32_t : 1; // [0-31], points to one tf# or three vf# constants. uint32_t const_index : 5; // Specific to vertex or texture fetch. uint32_t : 7; }; struct { uint32_t dst_swiz : 12; // Specific to vertex or texture fetch. uint32_t : 19; uint32_t is_predicated : 1; }; struct { // Specific to vertex or texture fetch. uint32_t : 31; uint32_t pred_condition : 1; }; }; Data data_; VertexFetchInstruction vertex_fetch_; TextureFetchInstruction texture_fetch_; }; static_assert_size(FetchInstruction, sizeof(uint32_t) * 3); // What follows is largely a mash up of the microcode assembly naming and the // R600 docs that have a near 1:1 with the instructions available in the Xenos // GPU, and Adreno 2xx instruction names found in Freedreno. Some of the // behavior has been experimentally verified. Some has been guessed. Some // instructions are implemented in the Exhibit 2092 - sq_alu of IPR2015-00325, // however, the code provided there is early and incomplete. // // Conventions: // - All temporary registers are vec4s. // - Most scalar ALU operations work with one or two components of the source // register or the float constant passed as the third operand of the whole // co-issued ALU operation, denoted by `a` (the left-hand operand) and `b` // (the right-hand operand). // `a` is the [(3 + src3_swizzle[6:7]) & 3] component (W - alpha). // `b` is the [(0 + src3_swizzle[0:1]) & 3] component (X - red). // - mulsc, addsc, subsc scalar ALU operations accept two operands - a float // constant with the `a` (W) swizzle (addressed by the third operand index and // addressing mode) being the left-hand operand, and a temporary register with // the `b` (X) swizzle with the index constructed from: // - [0:0] = scalar_opcode[0:0] // - [1:1] = src3_sel[0:0] // - [2:5] = src3_swizzle[2:5] // abs_constants and third source's negation are applied to both the constant // and the temporary register. // - Some scalar ALU instructions don't have operands. // - Scalar ALU operations replicate the result into all masked components. // - Overall, the WXYZ order is pretty commonly used in the Exhibit 2092 - // sq_alu of IPR2015-00325, this is where the AB = WX order of scalar operands // likely comes from. Vector predicate instructions also involve the W and X // components, and in IPR2015-00325 sq_alu, individual components in the // emulated vector instructions are handled in the WXYZ order. However, max4's // "greater than the rest" check order is RGBA (XYZW) there. dp4, though, sums // the products in WXYZ order in IPR2015-00325 sq_alu (but in XYZW order on // MSDN). // - ps is the previous result of a scalar ALU operation. It is not modified // when the instruction that would write it fails its predication check. // - Direct3D 9 rules (like in GCN v_*_legacy_f32 instructions) for // multiplication (+-0 or denormal * anything = +0) wherever it's present // (mul, mad, dp, etc.) and for NaN in min/max. It's very important to respect // this rule for multiplication, as games often rely on it in vector // normalization (rcp and mul), Infinity * 0 resulting in NaN breaks a lot of // things in games - causes white screen in 4D5307E6, white specular on // characters in 545407F2. The result is always positive zero in this case, no // matter what the signs of the other operands are, according to R5xx // Acceleration section 8.7.5 "Legacy multiply behavior" and testing on // Adreno 200. This means that the following need to be taken into account // (according to 8.7.2 "ALU Non-Transcendental Floating Point"): // - +0 * -0 is -0 with IEEE conformance, however, with this legacy SM3 // handling, it should result in +0. // - +0 + -0 is +0, so multiply-add should not be replaced with conditional // move of the third operand in case of zero multiplicands, because the term // may be -0, while the result should be +0 in this case. // http://developer.amd.com/wordpress/media/2013/10/R5xx_Acceleration_v1.5.pdf // Multiply-add also appears to be not fused; the SM3 behavior instruction on // GCN is called v_mad_legacy_f32, not v_fma_legacy_f32 (in 2012-2020, before // RDNA 2, which removed v_mad_f32 as well) - shader translators should not // use instructions that may be interpreted by the host GPU as fused // multiply-add. // For analysis of shaders and skipping instructions that write nothing. enum AluOpChangedState { kAluOpChangedStateNone = 0, kAluOpChangedStateAddressRegister = 1 << 0, kAluOpChangedStatePredicate = 1 << 1, kAluOpChangedStatePixelKill = 1 << 2, }; enum class AluScalarOpcode : uint32_t { // Floating-Point Add // adds/ADDs dest, src0.ab // dest.xyzw = src0.a + src0.b; kAdds = 0, // Floating-Point Add (with Previous) // adds_prev/ADD_PREVs dest, src0.a // dest.xyzw = src0.a + ps; kAddsPrev = 1, // Floating-Point Multiply // muls/MULs dest, src0.ab // dest.xyzw = src0.a * src0.b; kMuls = 2, // Floating-Point Multiply (with Previous) // muls_prev/MUL_PREVs dest, src0.a // dest.xyzw = src0.a * ps; kMulsPrev = 3, // Scalar Multiply Emulating LIT Operation // muls_prev2/MUL_PREV2s dest, src0.ab // dest.xyzw = // ps == -FLT_MAX || !isfinite(ps) || !isfinite(src0.b) || src0.b <= 0 // ? -FLT_MAX : src0.a * ps; kMulsPrev2 = 4, // Floating-Point Maximum // maxs/MAXs dest, src0.ab // dest.xyzw = src0.a >= src0.b ? src0.a : src0.b; kMaxs = 5, // Floating-Point Minimum // mins/MINs dest, src0.ab // dest.xyzw = src0.a < src0.b ? src0.a : src0.b; kMins = 6, // Floating-Point Set If Equal // seqs/SETEs dest, src0.a // dest.xyzw = src0.a == 0.0 ? 1.0 : 0.0; kSeqs = 7, // Floating-Point Set If Greater Than // sgts/SETGTs dest, src0.a // dest.xyzw = src0.a > 0.0 ? 1.0 : 0.0; kSgts = 8, // Floating-Point Set If Greater Than Or Equal // sges/SETGTEs dest, src0.a // dest.xyzw = src0.a >= 0.0 ? 1.0 : 0.0; kSges = 9, // Floating-Point Set If Not Equal // snes/SETNEs dest, src0.a // dest.xyzw = src0.a != 0.0 ? 1.0 : 0.0; kSnes = 10, // Floating-Point Fractional // frcs/FRACs dest, src0.a // dest.xyzw = src0.a - floor(src0.a); kFrcs = 11, // Floating-Point Truncate // truncs/TRUNCs dest, src0.a // dest.xyzw = src0.a >= 0 ? floor(src0.a) : -floor(-src0.a); kTruncs = 12, // Floating-Point Floor // floors/FLOORs dest, src0.a // dest.xyzw = floor(src0.a); kFloors = 13, // Scalar Base-2 Exponent, IEEE // exp/EXP_IEEE dest, src0.a // dest.xyzw = src0.a == 0.0 ? 1.0 : pow(2, src0.a); kExp = 14, // Scalar Base-2 Log // logc/LOG_CLAMP dest, src0.a // float t = src0.a == 1.0 ? 0.0 : log(src0.a) / log(2.0); // dest.xyzw = t == -INF ? -FLT_MAX : t; kLogc = 15, // Scalar Base-2 IEEE Log // log/LOG_IEEE dest, src0.a // dest.xyzw = src0.a == 1.0 ? 0.0 : log(src0.a) / log(2.0); kLog = 16, // Scalar Reciprocal, Clamp to Maximum // rcpc/RECIP_CLAMP dest, src0.a // float t = src0.a == 1.0 ? 1.0 : 1.0 / src0.a; // if (t == -INF) t = -FLT_MAX; // else if (t == INF) t = FLT_MAX; // dest.xyzw = t; kRcpc = 17, // Scalar Reciprocal, Clamp to Zero // Mimicking the behavior of the fixed-function pipeline. // rcpf/RECIP_FF dest, src0.a // float t = src0.a == 1.0 ? 1.0 : 1.0 / src0.a; // if (t == -INF) t = -0.0; // else if (t == INF) t = 0.0; // dest.xyzw = t; kRcpf = 18, // Scalar Reciprocal, IEEE Approximation // rcp/RECIP_IEEE dest, src0.a // dest.xyzw = src0.a == 1.0 ? 1.0 : 1.0 / src0.a; kRcp = 19, // Scalar Reciprocal Square Root, Clamp to Maximum // rsqc/RECIPSQ_CLAMP dest, src0.a // float t = src0.a == 1.0 ? 1.0 : 1.0 / sqrt(src0.a); // if (t == -INF) t = -FLT_MAX; // else if (t == INF) t = FLT_MAX; // dest.xyzw = t; kRsqc = 20, // Scalar Reciprocal Square Root, Clamp to Zero // rsqf/RECIPSQ_FF dest, src0.a // float t = src0.a == 1.0 ? 1.0 : 1.0 / sqrt(src0.a); // if (t == -INF) t = -0.0; // else if (t == INF) t = 0.0; // dest.xyzw = t; kRsqf = 21, // Scalar Reciprocal Square Root, IEEE Approximation // rsq/RECIPSQ_IEEE dest, src0.a // dest.xyzw = src0.a == 1.0 ? 1.0 : 1.0 / sqrt(src0.a); kRsq = 22, // Floating-Point Maximum with Copy To Integer in AR // maxas dest, src0.ab // movas/MOVAs dest, src0.aa // a0 = (int)clamp(floor(src0.a + 0.5), -256.0, 255.0); // dest.xyzw = src0.a >= src0.b ? src0.a : src0.b; kMaxAs = 23, // Floating-Point Maximum with Copy Truncated To Integer in AR // maxasf dest, src0.ab // movasf/MOVA_FLOORs dest, src0.aa // a0 = (int)clamp(floor(src0.a), -256.0, 255.0); // dest.xyzw = src0.a >= src0.b ? src0.a : src0.b; kMaxAsf = 24, // Floating-Point Subtract // subs/SUBs dest, src0.ab // dest.xyzw = src0.a - src0.b; kSubs = 25, // Floating-Point Subtract (with Previous) // subs_prev/SUB_PREVs dest, src0.a // dest.xyzw = src0.a - ps; kSubsPrev = 26, // Floating-Point Predicate Set If Equal // setp_eq/PRED_SETEs dest, src0.a // if (src0.a == 0.0) { // dest.xyzw = 0.0; // p0 = 1; // } else { // dest.xyzw = 1.0; // p0 = 0; // } kSetpEq = 27, // Floating-Point Predicate Set If Not Equal // setp_ne/PRED_SETNEs dest, src0.a // if (src0.a != 0.0) { // dest.xyzw = 0.0; // p0 = 1; // } else { // dest.xyzw = 1.0; // p0 = 0; // } kSetpNe = 28, // Floating-Point Predicate Set If Greater Than // setp_gt/PRED_SETGTs dest, src0.a // if (src0.a > 0.0) { // dest.xyzw = 0.0; // p0 = 1; // } else { // dest.xyzw = 1.0; // p0 = 0; // } kSetpGt = 29, // Floating-Point Predicate Set If Greater Than Or Equal // setp_ge/PRED_SETGTEs dest, src0.a // if (src0.a >= 0.0) { // dest.xyzw = 0.0; // p0 = 1; // } else { // dest.xyzw = 1.0; // p0 = 0; // } kSetpGe = 30, // Predicate Counter Invert // setp_inv/PRED_SET_INVs dest, src0.a // if (src0.a == 1.0) { // dest.xyzw = 0.0; // p0 = 1; // } else { // if (src0.a == 0.0) { // dest.xyzw = 1.0; // } else { // dest.xyzw = src0.a; // } // p0 = 0; // } kSetpInv = 31, // Predicate Counter Pop // setp_pop/PRED_SET_POPs dest, src0.a // if (src0.a - 1.0 <= 0.0) { // dest.xyzw = 0.0; // p0 = 1; // } else { // dest.xyzw = src0.a - 1.0; // p0 = 0; // } kSetpPop = 32, // Predicate Counter Clear // setp_clr/PRED_SET_CLRs dest // dest.xyzw = FLT_MAX; // p0 = 0; kSetpClr = 33, // Predicate Counter Restore // setp_rstr/PRED_SET_RESTOREs dest, src0.a // if (src0.a == 0.0) { // dest.xyzw = 0.0; // p0 = 1; // } else { // dest.xyzw = src0.a; // p0 = 0; // } kSetpRstr = 34, // Floating-Point Pixel Kill If Equal // kills_eq/KILLEs dest, src0.a // if (src0.a == 0.0) { // dest.xyzw = 1.0; // discard; // } else { // dest.xyzw = 0.0; // } kKillsEq = 35, // Floating-Point Pixel Kill If Greater Than // kills_gt/KILLGTs dest, src0.a // if (src0.a > 0.0) { // dest.xyzw = 1.0; // discard; // } else { // dest.xyzw = 0.0; // } kKillsGt = 36, // Floating-Point Pixel Kill If Greater Than Or Equal // kills_ge/KILLGTEs dest, src0.a // if (src0.a >= 0.0) { // dest.xyzw = 1.0; // discard; // } else { // dest.xyzw = 0.0; // } kKillsGe = 37, // Floating-Point Pixel Kill If Not Equal // kills_ne/KILLNEs dest, src0.a // if (src0.a != 0.0) { // dest.xyzw = 1.0; // discard; // } else { // dest.xyzw = 0.0; // } kKillsNe = 38, // Floating-Point Pixel Kill If One // kills_one/KILLONEs dest, src0.a // if (src0.a == 1.0) { // dest.xyzw = 1.0; // discard; // } else { // dest.xyzw = 0.0; // } kKillsOne = 39, // Scalar Square Root, IEEE Aproximation // sqrt/SQRT_IEEE dest, src0.a // dest.xyzw = sqrt(src0.a); kSqrt = 40, // 0 and 1 are the same instruction - one bit of the register index is stored // in the opcode field. // mulsc/MUL_CONST_0 dest, src0.a, src1.a kMulsc0 = 42, // mulsc/MUL_CONST_1 dest, src0.a, src1.a kMulsc1 = 43, // addsc/ADD_CONST_0 dest, src0.a, src1.a kAddsc0 = 44, // addsc/ADD_CONST_1 dest, src0.a, src1.a kAddsc1 = 45, // subsc/SUB_CONST_0 dest, src0.a, src1.a kSubsc0 = 46, // subsc/SUB_CONST_1 dest, src0.a, src1.a kSubsc1 = 47, // Scalar Sin // sin/SIN dest, src0.a // dest.xyzw = sin(src0.a); kSin = 48, // Scalar Cos // cos/COS dest, src0.a // dest.xyzw = cos(src0.a); kCos = 49, // retain_prev/RETAIN_PREV dest // dest.xyzw = ps; kRetainPrev = 50, }; struct AluScalarOpcodeInfo { const char* name; // 0 - no operands. // 1 - one single-component (W) or two-component (WX) r# or c#. // 2 - c#.w and r#.x. uint32_t operand_count; // If operand_count is 1, whether both W and X of the operand are used rather // than only W. bool single_operand_is_two_component; // Note that all scalar instructions except for retain_prev modify the // previous scalar register, so they must be executed even if they don't write // any result and don't perform any other state changes. AluOpChangedState changed_state; }; // 6 scalar opcode bits - 64 entries. extern const AluScalarOpcodeInfo kAluScalarOpcodeInfos[64]; inline const AluScalarOpcodeInfo& GetAluScalarOpcodeInfo( AluScalarOpcode opcode) { assert_true(uint32_t(opcode) < xe::countof(kAluScalarOpcodeInfos)); return kAluScalarOpcodeInfos[uint32_t(opcode)]; } enum class AluVectorOpcode : uint32_t { // Per-Component Floating-Point Add // add/ADDv dest, src0, src1 // dest.x = src0.x + src1.x; // dest.y = src0.y + src1.y; // dest.z = src0.z + src1.z; // dest.w = src0.w + src1.w; kAdd = 0, // Per-Component Floating-Point Multiply // mul/MULv dest, src0, src1 // dest.x = src0.x * src1.x; // dest.y = src0.y * src1.y; // dest.z = src0.z * src1.z; // dest.w = src0.w * src1.w; kMul = 1, // Per-Component Floating-Point Maximum // max/MAXv dest, src0, src1 // dest.x = src0.x >= src1.x ? src0.x : src1.x; // dest.y = src0.x >= src1.y ? src0.y : src1.y; // dest.z = src0.x >= src1.z ? src0.z : src1.z; // dest.w = src0.x >= src1.w ? src0.w : src1.w; kMax = 2, // Per-Component Floating-Point Minimum // min/MINv dest, src0, src1 // dest.x = src0.x < src1.x ? src0.x : src1.x; // dest.y = src0.x < src1.y ? src0.y : src1.y; // dest.z = src0.x < src1.z ? src0.z : src1.z; // dest.w = src0.x < src1.w ? src0.w : src1.w; kMin = 3, // Per-Component Floating-Point Set If Equal // seq/SETEv dest, src0, src1 // dest.x = src0.x == src1.x ? 1.0 : 0.0; // dest.y = src0.y == src1.y ? 1.0 : 0.0; // dest.z = src0.z == src1.z ? 1.0 : 0.0; // dest.w = src0.w == src1.w ? 1.0 : 0.0; kSeq = 4, // Per-Component Floating-Point Set If Greater Than // sgt/SETGTv dest, src0, src1 // dest.x = src0.x > src1.x ? 1.0 : 0.0; // dest.y = src0.y > src1.y ? 1.0 : 0.0; // dest.z = src0.z > src1.z ? 1.0 : 0.0; // dest.w = src0.w > src1.w ? 1.0 : 0.0; kSgt = 5, // Per-Component Floating-Point Set If Greater Than Or Equal // sge/SETGTEv dest, src0, src1 // dest.x = src0.x >= src1.x ? 1.0 : 0.0; // dest.y = src0.y >= src1.y ? 1.0 : 0.0; // dest.z = src0.z >= src1.z ? 1.0 : 0.0; // dest.w = src0.w >= src1.w ? 1.0 : 0.0; kSge = 6, // Per-Component Floating-Point Set If Not Equal // sne/SETNEv dest, src0, src1 // dest.x = src0.x != src1.x ? 1.0 : 0.0; // dest.y = src0.y != src1.y ? 1.0 : 0.0; // dest.z = src0.z != src1.z ? 1.0 : 0.0; // dest.w = src0.w != src1.w ? 1.0 : 0.0; kSne = 7, // Per-Component Floating-Point Fractional // frc/FRACv dest, src0 // dest.x = src0.x - floor(src0.x); // dest.y = src0.y - floor(src0.y); // dest.z = src0.z - floor(src0.z); // dest.w = src0.w - floor(src0.w); kFrc = 8, // Per-Component Floating-Point Truncate // trunc/TRUNCv dest, src0 // dest.x = src0.x >= 0 ? floor(src0.x) : -floor(-src0.x); // dest.y = src0.y >= 0 ? floor(src0.y) : -floor(-src0.y); // dest.z = src0.z >= 0 ? floor(src0.z) : -floor(-src0.z); // dest.w = src0.w >= 0 ? floor(src0.w) : -floor(-src0.w); kTrunc = 9, // Per-Component Floating-Point Floor // floor/FLOORv dest, src0 // dest.x = floor(src0.x); // dest.y = floor(src0.y); // dest.z = floor(src0.z); // dest.w = floor(src0.w); kFloor = 10, // Per-Component Floating-Point Multiply-Add // mad/MULADDv dest, src0, src1, src2 // dest.x = src0.x * src1.x + src2.x; // dest.y = src0.y * src1.y + src2.y; // dest.z = src0.z * src1.z + src2.z; // dest.w = src0.w * src1.w + src2.w; // According to SQ_ALU::multiply_add (used in the isHardwareAccurate case) // from IPR2015-00325 sq_alu, this is FMA - rounding to single-precision only // after the addition. kMad = 11, // Per-Component Floating-Point Conditional Move If Equal // cndeq/CNDEv dest, src0, src1, src2 // dest.x = src0.x == 0.0 ? src1.x : src2.x; // dest.y = src0.y == 0.0 ? src1.y : src2.y; // dest.z = src0.z == 0.0 ? src1.z : src2.z; // dest.w = src0.w == 0.0 ? src1.w : src2.w; kCndEq = 12, // Per-Component Floating-Point Conditional Move If Greater Than Or Equal // cndge/CNDGTEv dest, src0, src1, src2 // dest.x = src0.x >= 0.0 ? src1.x : src2.x; // dest.y = src0.y >= 0.0 ? src1.y : src2.y; // dest.z = src0.z >= 0.0 ? src1.z : src2.z; // dest.w = src0.w >= 0.0 ? src1.w : src2.w; kCndGe = 13, // Per-Component Floating-Point Conditional Move If Greater Than // cndgt/CNDGTv dest, src0, src1, src2 // dest.x = src0.x > 0.0 ? src1.x : src2.x; // dest.y = src0.y > 0.0 ? src1.y : src2.y; // dest.z = src0.z > 0.0 ? src1.z : src2.z; // dest.w = src0.w > 0.0 ? src1.w : src2.w; kCndGt = 14, // Four-Element Dot Product // dp4/DOT4v dest, src0, src1 // dest.xyzw = src0.x * src1.x + src0.y * src1.y + src0.z * src1.z + // src0.w * src1.w; kDp4 = 15, // Three-Element Dot Product // dp3/DOT3v dest, src0, src1 // dest.xyzw = src0.x * src1.x + src0.y * src1.y + src0.z * src1.z; kDp3 = 16, // Two-Element Dot Product and Add // dp2add/DOT2ADDv dest, src0, src1, src2 // dest.xyzw = src0.x * src1.x + src0.y * src1.y + src2.x; // IPR2015-00325 sq_alu may be an outdated and unreliable reference (Sequencer // Parts Development folder history lists a few changes regarding the swizzle // in dot2add, sq_alu though implements the instruction as // src0.x * src1.x + src0.z * src1.z + src2.y, but MSDN specifies the correct // order as provided in the beginning of this comment, further proven by // assembling PC shader assembly using XNA, with Shader Model 2 dp2add being // translated directly into Xenos dp2add without additional swizzling). // http://web.archive.org/web/20100705150552/http://msdn.microsoft.com/en-us/library/bb313922.aspx kDp2Add = 17, // Cube Map // cube/CUBEv dest, src0, src1 // dest.x = T cube coordinate; // dest.y = S cube coordinate; // dest.z = 2.0 * major axis; // dest.w = FaceID; // https://developer.amd.com/wordpress/media/2012/12/AMD_Southern_Islands_Instruction_Set_Architecture.pdf // if (abs(z) >= abs(x) && abs(z) >= abs(y)) { // tc = -y; // sc = z < 0.0 ? -x : x; // ma = 2.0 * z; // id = z < 0.0 ? 5.0 : 4.0; // } else if (abs(y) >= abs(x)) { // tc = y < 0.0 ? -z : z; // sc = x; // ma = 2.0 * y; // id = y < 0.0 ? 3.0 : 2.0; // } else { // tc = -y; // sc = x < 0.0 ? z : -z; // ma = 2.0 * x; // id = x < 0.0 ? 1.0 : 0.0; // } // Expects src0.zzxy and src1.yxzz swizzles. // FaceID is D3DCUBEMAP_FACES: // https://msdn.microsoft.com/en-us/library/windows/desktop/bb172528(v=vs.85).aspx // Used like: // cube r0, source.zzxy, source.yxz // rcp r0.z, r0_abs.z // mad r0.xy, r0, r0.zzzw, 1.5f // tfetchCube r0, r0.yxw, tf0 // http://web.archive.org/web/20100705154143/http://msdn.microsoft.com/en-us/library/bb313921.aspx // On GCN, the sequence is the same, so GCN documentation can be used as a // reference (tfetchCube doesn't accept the ST as if the texture was a 2D // array in XY exactly, to get texture array ST, 1 must be subtracted from its // XY inputs). // https://gpuopen.com/learn/fetching-from-cubes-and-octahedrons/ // "The 1.5 constant is designed such that the output face coordinate (v4 and // v5 in the above example) range is {1.0 <= x < 2.0} which has an advantage // in bit encoding compared to {0.0 <= x < 1.0} in that the upper mantissa // bits are constant throughout the entire output range." kCube = 18, // Four-Element Maximum // max4/MAX4v dest, src0 // According to IPR2015-00325 sq_alu: // if (src0.x > src0.y && src0.x > src0.z && src0.x > src0.w) { // dest.xyzw = src0.x; // } else if (src0.y > src0.z && src0.y > src0.w) { // dest.xyzw = src0.y; // } else if (src0.z > src0.w) { // dest.xyzw = src0.z; // } else { // dest.xyzw = src0.w; // } // However, the comparisons may be >= actually - the XNA documentation on // MSDN, as well as R600 and GCN documentation, describe `max` as being // implemented via >= rather than >. `max4` is documented vaguely, without the // exact calculations for each component - MSDN describes it as max(xyzw), and // in the R600 documentation it's max(wzyx). There's also a case more similar // to `max4` where there also is a discrepancy between IPR2015-00325 sq_alu // and the GCN documentation - `cube` has max3 in zyx priority order, and a >= // comparison is used for this purpose on the GCN, but in IPR2015-00325 sq_alu // it's implemented via >. It's possible that in an early version of the R400, // the comparison was >, but was later changed to >=, but this is merely a // guess. kMax4 = 19, // Floating-Point Predicate Counter Increment If Equal // setp_eq_push/PRED_SETE_PUSHv dest, src0, src1 // if (src0.w == 0.0 && src1.w == 0.0) { // p0 = 1; // } else { // p0 = 0; // } // if (src0.x == 0.0 && src1.x == 0.0) { // dest.xyzw = 0.0; // } else { // dest.xyzw = src0.x + 1.0; // } kSetpEqPush = 20, // Floating-Point Predicate Counter Increment If Not Equal // setp_ne_push/PRED_SETNE_PUSHv dest, src0, src1 // if (src0.w == 0.0 && src1.w != 0.0) { // p0 = 1; // } else { // p0 = 0; // } // if (src0.x == 0.0 && src1.x != 0.0) { // dest.xyzw = 0.0; // } else { // dest.xyzw = src0.x + 1.0; // } kSetpNePush = 21, // Floating-Point Predicate Counter Increment If Greater Than // setp_gt_push/PRED_SETGT_PUSHv dest, src0, src1 // if (src0.w == 0.0 && src1.w > 0.0) { // p0 = 1; // } else { // p0 = 0; // } // if (src0.x == 0.0 && src1.x > 0.0) { // dest.xyzw = 0.0; // } else { // dest.xyzw = src0.x + 1.0; // } kSetpGtPush = 22, // Floating-Point Predicate Counter Increment If Greater Than Or Equal // setp_ge_push/PRED_SETGTE_PUSHv dest, src0, src1 // if (src0.w == 0.0 && src1.w >= 0.0) { // p0 = 1; // } else { // p0 = 0; // } // if (src0.x == 0.0 && src1.x >= 0.0) { // dest.xyzw = 0.0; // } else { // dest.xyzw = src0.x + 1.0; // } kSetpGePush = 23, // Floating-Point Pixel Kill If Equal // kill_eq/KILLEv dest, src0, src1 // if (src0.x == src1.x || // src0.y == src1.y || // src0.z == src1.z || // src0.w == src1.w) { // dest.xyzw = 1.0; // discard; // } else { // dest.xyzw = 0.0; // } kKillEq = 24, // Floating-Point Pixel Kill If Greater Than // kill_gt/KILLGTv dest, src0, src1 // if (src0.x > src1.x || // src0.y > src1.y || // src0.z > src1.z || // src0.w > src1.w) { // dest.xyzw = 1.0; // discard; // } else { // dest.xyzw = 0.0; // } kKillGt = 25, // Floating-Point Pixel Kill If Equal // kill_ge/KILLGTEv dest, src0, src1 // if (src0.x >= src1.x || // src0.y >= src1.y || // src0.z >= src1.z || // src0.w >= src1.w) { // dest.xyzw = 1.0; // discard; // } else { // dest.xyzw = 0.0; // } kKillGe = 26, // Floating-Point Pixel Kill If Equal // kill_ne/KILLNEv dest, src0, src1 // if (src0.x != src1.x || // src0.y != src1.y || // src0.z != src1.z || // src0.w != src1.w) { // dest.xyzw = 1.0; // discard; // } else { // dest.xyzw = 0.0; // } kKillNe = 27, // dst/DSTv dest, src0, src1 // dest.x = 1.0; // dest.y = src0.y * src1.y; // dest.z = src0.z; // dest.w = src1.w; kDst = 28, // Per-Component Floating-Point Maximum with Copy To Integer in AR // maxa dest, src0, src1 // This is a combined max + mova/MOVAv. // a0 = (int)clamp(floor(src0.w + 0.5), -256.0, 255.0); // dest.x = src0.x >= src1.x ? src0.x : src1.x; // dest.y = src0.x >= src1.y ? src0.y : src1.y; // dest.z = src0.x >= src1.z ? src0.z : src1.z; // dest.w = src0.x >= src1.w ? src0.w : src1.w; // The MSDN documentation specifies clamp as: // if (!(SQResultF >= -256.0)) { // SQResultF = -256.0; // } // if (SQResultF > 255.0) { // SQResultF = 255.0; // } // http://web.archive.org/web/20100705151335/http://msdn.microsoft.com:80/en-us/library/bb313931.aspx // However, using NaN as an address would be unusual. kMaxA = 29, }; struct AluVectorOpcodeInfo { const char* name; uint32_t operand_components_used[3]; AluOpChangedState changed_state; uint32_t GetOperandCount() const { if (!operand_components_used[2]) { if (!operand_components_used[1]) { if (!operand_components_used[0]) { return 0; } return 1; } return 2; } return 3; } }; // 5 vector opcode bits - 32 entries. extern const AluVectorOpcodeInfo kAluVectorOpcodeInfos[32]; inline const AluVectorOpcodeInfo& GetAluVectorOpcodeInfo( AluVectorOpcode opcode) { assert_true(uint32_t(opcode) < xe::countof(kAluVectorOpcodeInfos)); return kAluVectorOpcodeInfos[uint32_t(opcode)]; } // Whether each component of a source operand is needed for the instruction if // executed with the specified write mask, and thus can't be thrown away or be // undefined in translation. For per-component operations, for example, only the // components specified in the write mask are needed, but there are instructions // with special behavior for certain components. inline uint32_t GetAluVectorOpNeededSourceComponents( AluVectorOpcode vector_opcode, uint32_t src_index, uint32_t used_result_components) { assert_not_zero(src_index); assert_zero(used_result_components & ~uint32_t(0b1111)); uint32_t components = used_result_components; switch (vector_opcode) { case AluVectorOpcode::kDp4: case AluVectorOpcode::kMax4: components = used_result_components ? 0b1111 : 0; break; case AluVectorOpcode::kDp3: components = used_result_components ? 0b0111 : 0; break; case AluVectorOpcode::kDp2Add: components = used_result_components ? (src_index == 3 ? 0b0001 : 0b0011) : 0; break; case AluVectorOpcode::kCube: components = used_result_components ? 0b1111 : 0; break; case AluVectorOpcode::kSetpEqPush: case AluVectorOpcode::kSetpNePush: case AluVectorOpcode::kSetpGtPush: case AluVectorOpcode::kSetpGePush: components = used_result_components ? 0b1001 : 0b1000; break; case AluVectorOpcode::kKillEq: case AluVectorOpcode::kKillGt: case AluVectorOpcode::kKillGe: case AluVectorOpcode::kKillNe: components = 0b1111; break; // kDst is per-component, but not all components are used. // operand_components_used will filter out the unused ones. case AluVectorOpcode::kMaxA: if (src_index == 1) { components |= 0b1000; } break; default: break; } return components & GetAluVectorOpcodeInfo(vector_opcode) .operand_components_used[src_index - 1]; } // eM# (kExportData) register count. constexpr uint32_t kMaxMemExportElementCount = 5; enum class ExportRegister : uint32_t { kVSInterpolator0 = 0, kVSInterpolator1, kVSInterpolator2, kVSInterpolator3, kVSInterpolator4, kVSInterpolator5, kVSInterpolator6, kVSInterpolator7, kVSInterpolator8, kVSInterpolator9, kVSInterpolator10, kVSInterpolator11, kVSInterpolator12, kVSInterpolator13, kVSInterpolator14, kVSInterpolator15, kVSPosition = 62, // See R6xx/R7xx registers for details (USE_VTX_POINT_SIZE, USE_VTX_EDGE_FLAG, // USE_VTX_KILL_FLAG). // X - PSIZE (gl_PointSize). // According to tests and GL_AMD_program_binary_Z400 disassembly on an // Adreno 200 device: // * This is the full width and height of the point sprite (not half - // gl_PointSize goes directly to oPts.x). // * Clamped to PA_SU_POINT_MINMAX as a signed integer in rasterization: // * -NaN - min // * -Infinity - min // * -Normal - min // * -0 (0x80000000 - the smallest signed integer) - min // * +0 - min // * +Infinity - max // * +NaN - max // Y - EDGEFLAG (glEdgeFlag) for PrimitiveType::kPolygon wireframe/point // drawing. // Z - KILLVERTEX flag (used in 4D5307ED for grass), set for killing // primitives based on PA_CL_CLIP_CNTL::VTX_KILL_OR condition if bits 0:30 // of this export value (the sign bit is ignored according to the // IPR2015-00325 sequencer specification) are not zero. kVSPointSizeEdgeFlagKillVertex = 63, kPSColor0 = 0, kPSColor1, kPSColor2, kPSColor3, // In X. kPSDepth = 61, // Memory export: index.?y?? * 0100 + xe_gpu_memexport_stream_t.xyzw. kExportAddress = 32, // Memory export: values for texels [index+0], [index+1], ..., [index+4]. kExportData0 = 33, kExportData1, kExportData2, kExportData3, kExportData4, }; struct alignas(uint32_t) AluInstruction { // Raw accessors. // Whether data is being exported (or written to local registers). bool is_export() const { return data_.export_data == 1; } // Whether the jump is predicated (or conditional). bool is_predicated() const { return data_.is_predicated; } // Required condition value of the comparision (true or false). bool predicate_condition() const { return data_.pred_condition == 1; } bool abs_constants() const { return data_.abs_constants == 1; } bool is_const_0_addressed() const { return data_.const_0_rel_abs == 1; } bool is_const_1_addressed() const { return data_.const_1_rel_abs == 1; } bool is_const_address_register_relative() const { return data_.const_address_register_relative == 1; } AluVectorOpcode vector_opcode() const { return data_.vector_opc; } uint32_t vector_write_mask() const { return data_.vector_write_mask; } uint32_t vector_dest() const { return data_.vector_dest; } bool is_vector_dest_relative() const { return data_.vector_dest_rel == 1; } bool vector_clamp() const { return data_.vector_clamp == 1; } AluScalarOpcode scalar_opcode() const { return data_.scalar_opc; } uint32_t scalar_write_mask() const { return data_.scalar_write_mask; } uint32_t scalar_dest() const { return data_.scalar_dest; } bool is_scalar_dest_relative() const { return data_.scalar_dest_rel == 1; } bool scalar_clamp() const { return data_.scalar_clamp == 1; } static constexpr uint32_t src_temp_reg(uint32_t src_reg) { return src_reg & 0x3F; } static constexpr bool is_src_temp_relative(uint32_t src_reg) { return (src_reg & 0x40) != 0; } static constexpr bool is_src_temp_value_absolute(uint32_t src_reg) { return (src_reg & 0x80) != 0; } // Full register index for constants, packed structure for temporary // registers (unpack using src_temp_reg, is_src_temp_relative, // is_src_temp_value_absolute). uint32_t src_reg(size_t i) const { switch (i) { case 1: return data_.src1_reg; case 2: return data_.src2_reg; case 3: return data_.src3_reg; default: assert_unhandled_case(i); return 0; } } bool src_is_temp(size_t i) const { switch (i) { case 1: return bool(data_.src1_sel); case 2: return bool(data_.src2_sel); case 3: return bool(data_.src3_sel); default: assert_unhandled_case(i); return 0; } } // Whether the specified operand is actually a constant is disregarded in this // function so its scope is limited to just parsing the structure's layout - // to decide whether to use relative addressing for the operand as a whole, // check externally whether the operand is actually a constant first. // // For the constant operand in mulsc, addsc, subsc, this should be called for // the operand index 3. Note that the XNA disassembler takes the addressing // mode for the constant scalar operand unconditionally from const_1_rel_abs, // and ignores the +aL for it unless the scalar operation is co-issued with a // vector operation reading from a constant. However, the XNA assembler treats // the constant scalar operand as a constant in the third operand, and places // the addressing mode for it in const_0_rel_abs if no other constants are // used in the whole ALU instruction. The validator also doesn't report // anything if +aL is used when the constant scalar operand is the only // constant in the instruction (and explicitly calls it the third constant in // the error message in case both vector operands are constants, and different // addressing modes are used for the second vector operand and the constant // scalar operand). Passing the disassembly produced by XNA back to the // assembler results in different microcode in this case. This indicates that // most likely there's a bug in the XNA disassembler, and that the addressing // mode for the constant scalar operand should actually be taken the same way // as for the third vector operand - from const_0_rel_abs if there are no // constant vector operands, or from const_1_rel_abs if there is at least one. bool src_const_is_addressed(size_t i) const { // "error X7100: When three constants are used in one instruction, the // second and third constant must either both be non-relative, or both be // relative." // Whether to use const_0_rel_abs or const_1_rel_abs is essentially // min(sum of whether the previous operands are constants, 1). switch (i) { case 1: return bool(data_.const_0_rel_abs); case 2: return bool(src_is_temp(1) ? data_.const_0_rel_abs : data_.const_1_rel_abs); case 3: return bool((src_is_temp(1) && src_is_temp(2)) ? data_.const_0_rel_abs : data_.const_1_rel_abs); default: assert_unhandled_case(i); return false; } } uint32_t src_swizzle(size_t i) const { switch (i) { case 1: return data_.src1_swiz; case 2: return data_.src2_swiz; case 3: return data_.src3_swiz; default: assert_unhandled_case(i); return 0; } } bool src_negate(size_t i) const { switch (i) { case 1: return data_.src1_reg_negate == 1; case 2: return data_.src2_reg_negate == 1; case 3: return data_.src3_reg_negate == 1; default: assert_unhandled_case(i); return 0; } } uint32_t scalar_const_reg_op_src_temp_reg() const { return (uint32_t(data_.scalar_opc) & 1) | (data_.src3_sel << 1) | (data_.src3_swiz & 0x3C); } // Helpers. // Returns the absolute component index calculated from the relative swizzle // in an ALU instruction. static constexpr uint32_t GetSwizzledComponentIndex( uint32_t swizzle, uint32_t component_index) { return ((swizzle >> (2 * component_index)) + component_index) & 3; } // Note that even if the export component is unused (like W of the vertex // shader misc register, YZW of pixel shader depth), it must still not be // excluded - that may make disassembly not reassemblable if there are // constant 0 writes in the export, like, oPts.x000 will be assembled, but // oPts.x00_ will not, even though W has no effect on anything. uint32_t GetVectorOpResultWriteMask() const { uint32_t mask = vector_write_mask(); if (is_export()) { mask &= ~scalar_write_mask(); } return mask; } uint32_t GetScalarOpResultWriteMask() const { uint32_t mask = scalar_write_mask(); if (is_export()) { mask &= ~vector_write_mask(); } return mask; } uint32_t GetConstant0WriteMask() const { if (!is_export() || !is_scalar_dest_relative()) { return 0b0000; } return 0b1111 & ~(vector_write_mask() | scalar_write_mask()); } uint32_t GetConstant1WriteMask() const { if (!is_export()) { return 0b0000; } return vector_write_mask() & scalar_write_mask(); } private: struct Data { struct { // If exporting, both vector and scalar operations use the vector // destination (which can't be relative in this case). // Not very important note: If both scalar and vector operations exporting // something have empty write mask, the XNA assembler forces vector_dest // to 0 (interpolator 0 or color 0) directly in the microcode. uint32_t vector_dest : 6; uint32_t vector_dest_rel : 1; uint32_t abs_constants : 1; uint32_t scalar_dest : 6; uint32_t scalar_dest_rel : 1; // Exports have different write masking (export is done to vector_dest by // both the vector and the scalar operation, and exports can write // constant 0 and 1). For each component: // - vector_write_mask 0, scalar_write_mask 0: // - scalar_dest_rel 0 - unchanged. // - scalar_dest_rel 1 - constant 0 (all components must be written). // - vector_write_mask 1, scalar_write_mask 0 - from vector operation. // - vector_write_mask 0, scalar_write_mask 1 - from scalar operation. // - vector_write_mask 1, scalar_write_mask 1 - constant 1. uint32_t export_data : 1; uint32_t vector_write_mask : 4; uint32_t scalar_write_mask : 4; uint32_t vector_clamp : 1; uint32_t scalar_clamp : 1; AluScalarOpcode scalar_opc : 6; }; struct { // Swizzles are component-relative. uint32_t src3_swiz : 8; uint32_t src2_swiz : 8; uint32_t src1_swiz : 8; uint32_t src3_reg_negate : 1; uint32_t src2_reg_negate : 1; uint32_t src1_reg_negate : 1; uint32_t pred_condition : 1; uint32_t is_predicated : 1; // Temporary registers can have only absolute and aL-relative indices, not // a0-relative. uint32_t const_address_register_relative : 1; uint32_t const_1_rel_abs : 1; uint32_t const_0_rel_abs : 1; }; struct { uint32_t src3_reg : 8; uint32_t src2_reg : 8; uint32_t src1_reg : 8; AluVectorOpcode vector_opc : 5; uint32_t src3_sel : 1; uint32_t src2_sel : 1; uint32_t src1_sel : 1; }; }; Data data_; }; static_assert_size(AluInstruction, sizeof(uint32_t) * 3); } // namespace ucode } // namespace gpu } // namespace xe #endif // XENIA_GPU_UCODE_H_