Repository cleanup & README.md. (#5)

* Put Unleashed Recompiled specific implementations behind a macro.

* Add README.md.

* Add LICENSE.md.

* Update README.md.

* Check for argument count & add UNLEASHED_RECOMP define when compiling shaders.
This commit is contained in:
Skyth (Asilkan) 2025-02-27 00:11:40 +03:00 committed by GitHub
parent 855a5a8c51
commit f2ae8ef9db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 230 additions and 7 deletions

21
LICENSE.md Normal file
View file

@ -0,0 +1,21 @@
# MIT License
Copyright (c) 2025 hedge-dev and contributors
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 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.

132
README.md Normal file
View file

@ -0,0 +1,132 @@
# XenosRecomp
XenosRecomp is a tool that converts Xbox 360 shader binaries to HLSL. The resulting files can be recompiled to DXIL and SPIR-V using the DirectX Shader Compiler (DXC) for use in Direct3D 12 (D3D12) and Vulkan.
The current implementation is designed around [Unleashed Recompiled](https://github.com/hedge-dev/UnleashedRecomp), a recompilation project that implements a translation layer for the renderer rather than emulating the Xbox 360 GPU. Unleashed Recompiled specific implementations are placed under the `UNLEASHED_RECOMP` preprocessor macro.
Users are expected to modify the recompiler to fit their needs. **Do not expect the recompiler to work out of the box.**
## Implementation Details
Several components of the recompiler are currently incomplete or missing. Unimplemented or inaccurate features exist mainly because they were either unnecessary for Unleashed Recompiled or did not cause visible issues.
### Shader Container
Xbox 360 shaders are stored in a container that includes constant buffer reflection data, definitions, interpolators, vertex declarations, instructions, and more. It has been reverse-engineered just enough for use in Unleashed Recompiled, but additional research may be needed for other games.
### Instructions
Vector/ALU instructions are converted directly and should work in most cases.
Issues might happen when instructions perform dynamic constant indexing on multiple operands.
Instructions that result in `INF` or `NaN` might not be handled correctly. Most operations are clamped to `FLT_MAX`, but their behavior has not been verified in all scenarios.
Dynamic register indexing is unimplemented. A possible solution is converting registers into an array that instructions dynamically index into, instead of treating them as separate local variables.
### Control Flow
Since HLSL does not support `goto`, control flow instructions are implemented using a `while` loop with a `switch` statement, where a local `pc` variable determines the currently executing block.
The current implementation has not been thoroughly tested, as Sonic Unleashed contains very few shaders with complex control flow. However, any issues should be relatively easy to fix if problematic cases can be found.
For shaders with simple control flow, the recompiler may choose to flatten it, removing the while loop and switch statements. This allows DXC to optimize the shader more efficiently.
### Constants
Both vertex and pixel shader stages use three constant buffers:
* Vertex shader constants: 4096 bytes (256 `float4` registers)
* Pixel shader constants: 3584 bytes (224 `float4` registers)
* Shared constants: Used specifically by Unleashed Recompiled
Vertex and pixel shader constants are copied directly from the guest render device, and shaders expect them in little-endian format.
Constant buffer registers are populated using reflection data embedded in the shader binaries. If this data is missing, the recompiler will not function. However, support can be added by defining a `float4` array that covers the entire register range.
Integer constants are unimplemented. If the target game requires them, you will need to make new constant buffer slots or append them to the existing ones.
Vertex and pixel shader boolean constants each contain 16 elements. These are packed into a 32-bit integer and stored in the shared constants buffer, where the Nth bit represents the value of the Nth boolean register. The Xbox 360 GPU supposedly supports up to 128 boolean registers, which may require increasing the size of the `g_Booleans` data type for other games.
All constant buffers are implemented as root constant buffers in D3D12, making them easy to upload to the GPU using a linear allocator. In Vulkan, the GPU virtual addresses of constant buffers are passed as push constants. Constants are accessed via preprocessor macros that load values from the GPU virtual addresses using `vk::RawBufferLoad`. These macros ensure the shader function body remains the same for both DXIL and SPIR-V.
Out-of-bounds dynamic constant accesses should return 0. However, since root constant buffers in D3D12 and raw buffer loads in Vulkan do not enforce this behavior, the shader developer must handle it. To solve this, each dynamic index access is clamped to the valid range, and out-of-bounds registers are forced to become 0.
### Vertex Fetch
A common approach to vertex fetching is passing vertex data as a shader resource view and building special shaders depending on the vertex declaration. Instead, Unleashed Recompiled converts vertex declarations into native D3D12/Vulkan input declarations, allowing vertex shaders to receive data as inputs. While this has its limitations, it removes the need for runtime shader permutation compilation based on vertex declarations.
Unleashed Recompiled endian swaps vertex data before uploading it to the GPU by treating buffers as arrays of 32-bit integers. This causes the element order for 8-bit and 16-bit vertex formats to be swizzled. While no visual errors have been observed for 8-bit formats, 16-bit formats get swizzled to YXWZ. This is corrected using a `g_SwappedTexcoords` variable in the shared constants buffer, where each bit indicates whether the corresponding `TEXCOORD` semantic requires re-swizzling. While this assumption holds for Sonic Unleashed, other games may require additional support for other semantics.
Xbox 360 supports the `R11G11B10` vertex format, which is unsupported on desktop hardware. The recompiler implements this by using a specialization constant that manually unpacks this format for `NORMAL`, `TANGENT` and `BINORMAL` semantics in the vertex shader. Similar to `TEXCOORD` swizzling, this assumes the format is only used for these semantics.
Certain semantics are forced to be `uint4` instead of `float4` for specific shaders in Sonic Unleashed. This is also something that needs to be handled manually for other games.
Instanced geometry is handled completely manually on the Xbox 360. In Sonic Unleashed, the index buffer is passed as a vertex stream, and shaders use it to arbitrarily fetch vertex data, relying on a `g_IndexCount` constant to determine the index of the current instance. Unleashed Recompiled handles this by expecting instanced data to be in the second vertex stream and the index buffer to be in the `POSITION1` semantic. This behavior is completely game specific and must be manually implemented for other games.
Vulkan vertex locations are currently hardcoded for Unleashed Recompiled, chosen based on Sonic Unleashed's shaders while taking the 16 location limit into account. A generic solution would assign unique locations per vertex shader and dynamically create vertex declarations at runtime.
Mini vertex fetch instructions and vertex fetch bindings are unimplemented.
### Textures & Samplers
Textures and samplers use a bindless approach. Descriptor indices are stored in the shared constant buffer, with separate indices for each texture type to prevent mismatches in the shader. 1D textures are unimplemented but could be added easily.
Several texture fetch features, such as specifying LOD levels or sampler filters, are unimplemented. Currently, only the pixel offset value is supported, which is primarily used for shadow mapping.
Some Xbox 360 sampler types may be unsupported on desktop hardware. These cases are unhandled and require specialized implementations in the recompiler.
Cube textures are normally sampled using the `cube` instruction, which computes the face index and 2D texture coordinates. This can be implemented on desktop hardware by sampling `Texture2DArray`, however this lacks linear filtering across cube edges. The recompiler instead stores an array of cube map directions locally. Each `cube` instruction stores a direction in this array, and the output register holds the direction index. When the shader performs a texture fetch, the direction is dynamically retrieved from the array and used in `TextureCube` sampling. DXC optimizes this array away, ensuring the final DXIL/SPIR-V shader uses the direction directly.
This approach works well for simple control flow but may cause issues with complex shaders where optimizations might fail, leading to the array actually being dynamically indexed. A proper solution could implement the `cube` instruction exactly as the hardware does, and then reverse this computation during texture sampling. I chose not to do this approach in the end, as DXC was unable to optimize away redundant computations due to the lossy nature of the calculation.
### Specialization Constants
The recompiler implements several specialization constants, primarily as enhancements for Unleashed Recompiled. Currently, these are simple flags that enable or disable specific shader behaviors. The generic ones include:
- A flag indicating that the `NORMAL`, `TANGENT`, and `BINORMAL` semantics use the `R11G11B10` vertex format, enabling manual unpacking in the vertex shader.
- A flag indicating that the pixel shader performs alpha testing. Since modern desktop hardware lacks a fixed function pipeline for alpha testing, this flag inserts a "less than alpha threshold" check at the end of the pixel shader. Additional comparison types may need to be implemented depending on the target game.
While specialization constants are straightforward to implement in SPIR-V, DXIL lacks native support for them. This is solved by compiling shaders as libraries with a declared, but unimplemented function that returns the specialization constant value. At runtime, Unleashed Recompiled generates an implementation of this function, compiles it into a library, and links it with the shader to produce a final specialized shader binary. For more details on this technique, [check out this article](https://therealmjp.github.io/posts/dxil-linking/).
### Other Unimplemented Features
* Memory export.
* Point size.
* Possibly more that I am not aware of.
## Usage
Shaders can be directly converted to HLSL by providing the input file path, output HLSL file path, and the path to the `shader_common.h` file located in the XenosRecomp project directory:
```
XenosRecomp [input shader file path] [output HLSL file path] [header file path]
```
### Shader Cache
Alternatively, the recompiler can process an entire directory by scanning for shader binaries within the specified path. In this mode, valid shaders are converted and recompiled into a DXIL/SPIR-V cache, formatted for use with Unleashed Recompiled. This cache is then exported as a .cpp file for direct embedding into the executable:
```
XenosRecomp [input directory path] [output .cpp file path] [header file path]
```
At runtime, shaders are mapped to their recompiled versions using a 64-bit XXH3 hash lookup. This scanning method is particularly useful for games that store embedded shaders within executables or uncompressed archive formats.
SPIR-V shaders are compressed using smol-v to improve zstd compression efficiency, while DXIL shaders are compressed as-is.
## Building
The project requires CMake 3.20 and a C++ compiler with C++17 support to build. While compilers other than Clang might work, they have not been tested. Since the repository includes submodules, ensure you clone it recursively.
The project uses [vcpkg](https://github.com/microsoft/vcpkg) to integrate DXC. To set it up, define the `VCPKG_ROOT` environment variable to point to the vcpkg root directory. Alternatively, on Windows, you can install vcpkg via the Visual Studio Installer and open the project using Visual Studio's CMake integration.
## Special Thanks
This recompiler would not have been possible without the [Xenia](https://github.com/xenia-project/xenia) emulator. Nearly every aspect of the development was guided by referencing Xenia's shader translator and research.
## Final Words
I hope this recompiler proves useful in some way to help with your own recompilation efforts! While the implementation isn't as generic as I hoped it would be, the optimization opportunities from game specific implementations were too significant to ignore and paid off in the end.
If you find and fix mistakes in the recompiler or successfully implement missing features in a generic way, contributions would be greatly appreciated.

View file

@ -54,6 +54,10 @@ IDxcBlob* DxcCompiler::compile(const std::string& shaderSource, bool compilePixe
args[argCount++] = L"-Qstrip_debug"; args[argCount++] = L"-Qstrip_debug";
#ifdef UNLEASHED_RECOMP
args[argCount++] = L"-DUNLEASHED_RECOMP";
#endif
IDxcResult* result = nullptr; IDxcResult* result = nullptr;
HRESULT hr = dxcCompiler->Compile(&source, args, argCount, nullptr, IID_PPV_ARGS(&result)); HRESULT hr = dxcCompiler->Compile(&source, args, argCount, nullptr, IID_PPV_ARGS(&result));

View file

@ -31,6 +31,14 @@ struct RecompiledShader
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
#ifndef XENOS_RECOMP_INPUT
if (argc < 4)
{
printf("Usage: XenosRecomp [input path] [output path] [shader common header file path]");
return 0;
}
#endif
const char* input = const char* input =
#ifdef XENOS_RECOMP_INPUT #ifdef XENOS_RECOMP_INPUT
XENOS_RECOMP_INPUT XENOS_RECOMP_INPUT

View file

@ -2,10 +2,13 @@
#define SHADER_COMMON_H_INCLUDED #define SHADER_COMMON_H_INCLUDED
#define SPEC_CONSTANT_R11G11B10_NORMAL (1 << 0) #define SPEC_CONSTANT_R11G11B10_NORMAL (1 << 0)
#define SPEC_CONSTANT_BICUBIC_GI_FILTER (1 << 1) #define SPEC_CONSTANT_ALPHA_TEST (1 << 1)
#define SPEC_CONSTANT_ALPHA_TEST (1 << 2)
#define SPEC_CONSTANT_ALPHA_TO_COVERAGE (1 << 3) #ifdef UNLEASHED_RECOMP
#define SPEC_CONSTANT_REVERSE_Z (1 << 4) #define SPEC_CONSTANT_BICUBIC_GI_FILTER (1 << 2)
#define SPEC_CONSTANT_ALPHA_TO_COVERAGE (1 << 3)
#define SPEC_CONSTANT_REVERSE_Z (1 << 4)
#endif
#if !defined(__cplusplus) || defined(__INTELLISENSE__) #if !defined(__cplusplus) || defined(__INTELLISENSE__)

View file

@ -74,6 +74,7 @@ struct DeclUsageLocation
uint32_t location; uint32_t location;
}; };
// NOTE: These are specialized Vulkan locations for Unleashed Recompiled. Change as necessary. Likely not going to work with other games.
static constexpr DeclUsageLocation USAGE_LOCATIONS[] = static constexpr DeclUsageLocation USAGE_LOCATIONS[] =
{ {
{ DeclUsage::Position, 0, 0 }, { DeclUsage::Position, 0, 0 },
@ -247,13 +248,18 @@ void ShaderRecompiler::recompile(const TextureFetchInstruction& instr, bool bicu
std::string constName; std::string constName;
const char* constNamePtr = nullptr; const char* constNamePtr = nullptr;
#ifdef UNLEASHED_RECOMP
bool subtractFromOne = false; bool subtractFromOne = false;
#endif
auto findResult = samplers.find(instr.constIndex); auto findResult = samplers.find(instr.constIndex);
if (findResult != samplers.end()) if (findResult != samplers.end())
{ {
constNamePtr = findResult->second; constNamePtr = findResult->second;
#ifdef UNLEASHED_RECOMP
subtractFromOne = hasMtxPrevInvViewProjection && strcmp(constNamePtr, "sampZBuffer") == 0; subtractFromOne = hasMtxPrevInvViewProjection && strcmp(constNamePtr, "sampZBuffer") == 0;
#endif
} }
else else
{ {
@ -261,6 +267,7 @@ void ShaderRecompiler::recompile(const TextureFetchInstruction& instr, bool bicu
constNamePtr = constName.c_str(); constNamePtr = constName.c_str();
} }
#ifdef UNLEASHED_RECOMP
if (instr.constIndex == 0 && instr.dimension == TextureDimension::Texture2D) if (instr.constIndex == 0 && instr.dimension == TextureDimension::Texture2D)
{ {
indent(); indent();
@ -268,6 +275,7 @@ void ShaderRecompiler::recompile(const TextureFetchInstruction& instr, bool bicu
printSrcRegister(2); printSrcRegister(2);
out += ");\n"; out += ");\n";
} }
#endif
indent(); indent();
print("r{}.", instr.dstRegister); print("r{}.", instr.dstRegister);
@ -278,8 +286,11 @@ void ShaderRecompiler::recompile(const TextureFetchInstruction& instr, bool bicu
{ {
case FetchOpcode::TextureFetch: case FetchOpcode::TextureFetch:
{ {
#ifdef UNLEASHED_RECOMP
if (subtractFromOne) if (subtractFromOne)
out += "1.0 - "; out += "1.0 - ";
#endif
out += "tfetch"; out += "tfetch";
break; break;
} }
@ -314,8 +325,11 @@ void ShaderRecompiler::recompile(const TextureFetchInstruction& instr, bool bicu
} }
out += dimension; out += dimension;
#ifdef UNLEASHED_RECOMP
if (bicubic) if (bicubic)
out += "Bicubic"; out += "Bicubic";
#endif
print("({0}_Texture{1}DescriptorIndex, {0}_SamplerDescriptorIndex, ", constNamePtr, dimension); print("({0}_Texture{1}DescriptorIndex, {0}_SamplerDescriptorIndex, ", constNamePtr, dimension);
printSrcRegister(componentCount); printSrcRegister(componentCount);
@ -447,12 +461,14 @@ void ShaderRecompiler::recompile(const AluInstruction& instr)
const char* constantName = reinterpret_cast<const char*>(constantTableData + findResult->second->name); const char* constantName = reinterpret_cast<const char*>(constantTableData + findResult->second->name);
if (findResult->second->registerCount > 1) if (findResult->second->registerCount > 1)
{ {
#ifdef UNLEASHED_RECOMP
if (hasMtxProjection && strcmp(constantName, "g_MtxProjection") == 0) if (hasMtxProjection && strcmp(constantName, "g_MtxProjection") == 0)
{ {
regFormatted = fmt::format("(iterationIndex == 0 ? mtxProjectionReverseZ[{0}] : mtxProjection[{0}])", regFormatted = fmt::format("(iterationIndex == 0 ? mtxProjectionReverseZ[{0}] : mtxProjection[{0}])",
reg - findResult->second->registerIndex); reg - findResult->second->registerIndex);
} }
else else
#endif
{ {
regFormatted = fmt::format("{}({}{})", constantName, regFormatted = fmt::format("{}({}{})", constantName,
reg - findResult->second->registerIndex, instr.const0Relative ? (instr.constAddressRegisterRelative ? " + a0" : " + aL") : ""); reg - findResult->second->registerIndex, instr.const0Relative ? (instr.constAddressRegisterRelative ? " + a0" : " + aL") : "");
@ -592,6 +608,7 @@ void ShaderRecompiler::recompile(const AluInstruction& instr)
case ExportRegister::VSPosition: case ExportRegister::VSPosition:
exportRegister = "oPos"; exportRegister = "oPos";
#ifdef UNLEASHED_RECOMP
if (hasMtxProjection) if (hasMtxProjection)
{ {
indent(); indent();
@ -602,6 +619,7 @@ void ShaderRecompiler::recompile(const AluInstruction& instr)
closeIfBracket = true; closeIfBracket = true;
} }
#endif
break; break;
@ -1097,8 +1115,10 @@ void ShaderRecompiler::recompile(const uint8_t* shaderData, const std::string_vi
out += "#ifdef __spirv__\n\n"; out += "#ifdef __spirv__\n\n";
#ifdef UNLEASHED_RECOMP
bool isMetaInstancer = false; bool isMetaInstancer = false;
bool hasIndexCount = false; bool hasIndexCount = false;
#endif
for (uint32_t i = 0; i < constantTableContainer->constantTable.constants; i++) for (uint32_t i = 0; i < constantTableContainer->constantTable.constants; i++)
{ {
@ -1107,6 +1127,7 @@ void ShaderRecompiler::recompile(const uint8_t* shaderData, const std::string_vi
const char* constantName = reinterpret_cast<const char*>(constantTableData + constantInfo->name); const char* constantName = reinterpret_cast<const char*>(constantTableData + constantInfo->name);
#ifdef UNLEASHED_RECOMP
if (!isPixelShader) if (!isPixelShader)
{ {
if (strcmp(constantName, "g_MtxProjection") == 0) if (strcmp(constantName, "g_MtxProjection") == 0)
@ -1121,6 +1142,7 @@ void ShaderRecompiler::recompile(const uint8_t* shaderData, const std::string_vi
if (strcmp(constantName, "g_MtxPrevInvViewProjection") == 0) if (strcmp(constantName, "g_MtxPrevInvViewProjection") == 0)
hasMtxPrevInvViewProjection = true; hasMtxPrevInvViewProjection = true;
} }
#endif
switch (constantInfo->registerSet) switch (constantInfo->registerSet)
{ {
@ -1292,11 +1314,13 @@ void ShaderRecompiler::recompile(const uint8_t* shaderData, const std::string_vi
const char* usageType = USAGE_TYPES[uint32_t(vertexElement.usage)]; const char* usageType = USAGE_TYPES[uint32_t(vertexElement.usage)];
#ifdef UNLEASHED_RECOMP
if ((vertexElement.usage == DeclUsage::TexCoord && vertexElement.usageIndex == 2 && isMetaInstancer) || if ((vertexElement.usage == DeclUsage::TexCoord && vertexElement.usageIndex == 2 && isMetaInstancer) ||
(vertexElement.usage == DeclUsage::Position && vertexElement.usageIndex == 1)) (vertexElement.usage == DeclUsage::Position && vertexElement.usageIndex == 1))
{ {
usageType = "uint4"; usageType = "uint4";
} }
#endif
out += '\t'; out += '\t';
@ -1315,11 +1339,14 @@ void ShaderRecompiler::recompile(const uint8_t* shaderData, const std::string_vi
vertexElements.emplace(uint32_t(vertexElement.address), vertexElement); vertexElements.emplace(uint32_t(vertexElement.address), vertexElement);
} }
#ifdef UNLEASHED_RECOMP
if (hasIndexCount) if (hasIndexCount)
{ {
out += "\tin uint iVertexId : SV_VertexID,\n"; out += "\tin uint iVertexId : SV_VertexID,\n";
out += "\tin uint iInstanceId : SV_InstanceID,\n"; out += "\tin uint iInstanceId : SV_InstanceID,\n";
} }
#endif
out += "\tout float4 oPos : SV_Position"; out += "\tout float4 oPos : SV_Position";
for (auto& [usage, usageIndex] : INTERPOLATORS) for (auto& [usage, usageIndex] : INTERPOLATORS)
@ -1329,7 +1356,7 @@ void ShaderRecompiler::recompile(const uint8_t* shaderData, const std::string_vi
out += ")\n"; out += ")\n";
out += "{\n"; out += "{\n";
#ifdef UNLEASHED_RECOMP
if (hasMtxProjection) if (hasMtxProjection)
{ {
specConstantsMask |= SPEC_CONSTANT_REVERSE_Z; specConstantsMask |= SPEC_CONSTANT_REVERSE_Z;
@ -1342,6 +1369,7 @@ void ShaderRecompiler::recompile(const uint8_t* shaderData, const std::string_vi
out += "\t[unroll] for (int iterationIndex = 0; iterationIndex < 2; iterationIndex++)\n"; out += "\t[unroll] for (int iterationIndex = 0; iterationIndex < 2; iterationIndex++)\n";
out += "\t{\n"; out += "\t{\n";
} }
#endif
if (shaderContainer->definitionTableOffset != NULL) if (shaderContainer->definitionTableOffset != NULL)
{ {
@ -1418,8 +1446,10 @@ void ShaderRecompiler::recompile(const uint8_t* shaderData, const std::string_vi
if (!isPixelShader) if (!isPixelShader)
{ {
#ifdef UNLEASHED_RECOMP
if (!hasMtxProjection) if (!hasMtxProjection)
out += "\toPos = 0.0;\n"; out += "\toPos = 0.0;\n";
#endif
for (auto& [usage, usageIndex] : INTERPOLATORS) for (auto& [usage, usageIndex] : INTERPOLATORS)
println("\to{}{} = 0.0;", USAGE_VARIABLES[uint32_t(usage)], usageIndex); println("\to{}{} = 0.0;", USAGE_VARIABLES[uint32_t(usage)], usageIndex);
@ -1436,10 +1466,12 @@ void ShaderRecompiler::recompile(const uint8_t* shaderData, const std::string_vi
{ {
out += "float4((iPos.xy - 0.5) * float2(iFace ? 1.0 : -1.0, 1.0), 0.0, 0.0);\n"; out += "float4((iPos.xy - 0.5) * float2(iFace ? 1.0 : -1.0, 1.0), 0.0, 0.0);\n";
} }
#ifdef UNLEASHED_RECOMP
else if (!isPixelShader && hasIndexCount && i == 0) else if (!isPixelShader && hasIndexCount && i == 0)
{ {
out += "float4(iVertexId + g_IndexCount.x * iInstanceId, 0.0, 0.0, 0.0);\n"; out += "float4(iVertexId + g_IndexCount.x * iInstanceId, 0.0, 0.0, 0.0);\n";
} }
#endif
else else
{ {
out += "0.0;\n"; out += "0.0;\n";
@ -1453,7 +1485,9 @@ void ShaderRecompiler::recompile(const uint8_t* shaderData, const std::string_vi
out += "\tfloat ps = 0.0;\n"; out += "\tfloat ps = 0.0;\n";
if (isPixelShader) if (isPixelShader)
{ {
#ifdef UNLEASHED_RECOMP
out += "\tfloat2 pixelCoord = 0.0;\n"; out += "\tfloat2 pixelCoord = 0.0;\n";
#endif
out += "\tCubeMapData cubeMapData = (CubeMapData)0;\n"; out += "\tCubeMapData cubeMapData = (CubeMapData)0;\n";
} }
@ -1611,7 +1645,10 @@ void ShaderRecompiler::recompile(const uint8_t* shaderData, const std::string_vi
if (simpleControlFlow) if (simpleControlFlow)
{ {
indent(); indent();
println("[unroll] for (aL = 0; aL < i{}.x; aL++)", uint32_t(cfInstr.loopStart.loopId)); #ifdef UNLEASHED_RECOMP
print("[unroll] ")
#endif
println("for (aL = 0; aL < i{}.x; aL++)", uint32_t(cfInstr.loopStart.loopId));
indent(); indent();
out += "{\n"; out += "{\n";
++indentation; ++indentation;
@ -1711,6 +1748,7 @@ void ShaderRecompiler::recompile(const uint8_t* shaderData, const std::string_vi
} }
else else
{ {
#ifdef UNLEASHED_RECOMP
if (textureFetch.constIndex == 10) // g_GISampler if (textureFetch.constIndex == 10) // g_GISampler
{ {
specConstantsMask |= SPEC_CONSTANT_BICUBIC_GI_FILTER; specConstantsMask |= SPEC_CONSTANT_BICUBIC_GI_FILTER;
@ -1739,6 +1777,7 @@ void ShaderRecompiler::recompile(const uint8_t* shaderData, const std::string_vi
out += '}'; out += '}';
} }
else else
#endif
{ {
recompile(textureFetch, false); recompile(textureFetch, false);
} }
@ -1757,7 +1796,7 @@ void ShaderRecompiler::recompile(const uint8_t* shaderData, const std::string_vi
{ {
if (isPixelShader) if (isPixelShader)
{ {
specConstantsMask |= (SPEC_CONSTANT_ALPHA_TEST | SPEC_CONSTANT_ALPHA_TO_COVERAGE); specConstantsMask |= SPEC_CONSTANT_ALPHA_TEST;
indent(); indent();
out += "[branch] if (g_SpecConstants() & SPEC_CONSTANT_ALPHA_TEST)"; out += "[branch] if (g_SpecConstants() & SPEC_CONSTANT_ALPHA_TEST)";
@ -1769,6 +1808,10 @@ void ShaderRecompiler::recompile(const uint8_t* shaderData, const std::string_vi
indent(); indent();
out += "}"; out += "}";
#ifdef UNLEASHED_RECOMP
specConstantsMask |= SPEC_CONSTANT_ALPHA_TO_COVERAGE;
indent(); indent();
out += "else if (g_SpecConstants() & SPEC_CONSTANT_ALPHA_TO_COVERAGE)"; out += "else if (g_SpecConstants() & SPEC_CONSTANT_ALPHA_TO_COVERAGE)";
indent(); indent();
@ -1781,15 +1824,22 @@ void ShaderRecompiler::recompile(const uint8_t* shaderData, const std::string_vi
indent(); indent();
out += '}'; out += '}';
#endif
} }
if (simpleControlFlow) if (simpleControlFlow)
{ {
indent(); indent();
#ifdef UNLEASHED_RECOMP
if (hasMtxProjection) if (hasMtxProjection)
{
out += "continue;\n"; out += "continue;\n";
}
else else
#endif
{
out += "return;\n"; out += "return;\n";
}
} }
else else
{ {
@ -1817,8 +1867,10 @@ void ShaderRecompiler::recompile(const uint8_t* shaderData, const std::string_vi
out += "\t}\n"; out += "\t}\n";
} }
#ifdef UNLEASHED_RECOMP
if (hasMtxProjection) if (hasMtxProjection)
out += "\t}\n"; out += "\t}\n";
#endif
out += "}"; out += "}";
} }

View file

@ -33,8 +33,11 @@ struct ShaderRecompiler : StringBuffer
std::unordered_map<uint32_t, const char*> samplers; std::unordered_map<uint32_t, const char*> samplers;
std::unordered_map<uint32_t, uint32_t> ifEndLabels; std::unordered_map<uint32_t, uint32_t> ifEndLabels;
uint32_t specConstantsMask = 0; uint32_t specConstantsMask = 0;
#ifdef UNLEASHED_RECOMP
bool hasMtxProjection = false; bool hasMtxProjection = false;
bool hasMtxPrevInvViewProjection = false; bool hasMtxPrevInvViewProjection = false;
#endif
void indent() void indent()
{ {