Skip to content

Commit 85c3c83

Browse files
committed
AIR Compilation, Compression, and Packing
Signed-off-by: Isaac Marovitz <isaacryu@icloud.com>
1 parent 04539fd commit 85c3c83

4 files changed

Lines changed: 152 additions & 4 deletions

File tree

XenosRecomp/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ endif()
1010

1111
set(SMOLV_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/smol-v/source")
1212

13-
add_executable(XenosRecomp
13+
add_executable(XenosRecomp
1414
constant_table.h
15+
air_compiler.cpp
16+
air_compiler.h
1517
dxc_compiler.cpp
1618
dxc_compiler.h
1719
main.cpp

XenosRecomp/air_compiler.cpp

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#include "air_compiler.h"
2+
3+
#include <iostream>
4+
#include <spawn.h>
5+
#include <unistd.h>
6+
#include <sys/wait.h>
7+
#include <cstdio>
8+
9+
std::vector<uint8_t> AirCompiler::compile(const std::string& shaderSource) {
10+
// First, generate AIR from shader source
11+
std::string inputFile = ".metal";
12+
int tmpFD = makeTemporaryFile(inputFile);
13+
write(tmpFD, shaderSource.data(), shaderSource.size());
14+
close(tmpFD);
15+
16+
std::string irFile = ".ir";
17+
tmpFD = makeTemporaryFile(irFile);
18+
close(tmpFD);
19+
20+
pid_t pid;
21+
char* airArgv[] = { "xcrun", "-sdk", "macosx", "metal", "-o", irFile.data(), "-c", inputFile.data(), "-D__air__", "-DUNLEASHED_RECOMP", "-Wno-unused-variable", nullptr };
22+
23+
if (posix_spawn(&pid, "/usr/bin/xcrun", nullptr, nullptr, airArgv, nullptr) != 0) {
24+
unlink(inputFile.data());
25+
unlink(irFile.data());
26+
fmt::println("Failed to spawn AIR xcrun process");
27+
exit(1);
28+
}
29+
30+
int status;
31+
32+
if (waitpid(pid, &status, 0) == -1) {
33+
unlink(inputFile.data());
34+
unlink(irFile.data());
35+
fmt::println("Failed to wait AIR xcrun process");
36+
exit(1);
37+
}
38+
39+
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
40+
unlink(inputFile.data());
41+
unlink(irFile.data());
42+
fmt::println("AIR xcrun exited with code {}", WEXITSTATUS(status));
43+
fmt::println("{}", shaderSource);
44+
exit(1);
45+
}
46+
47+
unlink(inputFile.data());
48+
49+
// Now we need to turn the AIR into a .metallib
50+
std::string libFile = ".metallib";
51+
tmpFD = makeTemporaryFile(libFile);
52+
close(tmpFD);
53+
54+
char* libArgv[] = { "xcrun", "-sdk", "macosx", "metallib", "-o", libFile.data(), irFile.data(), nullptr };
55+
56+
if (posix_spawn(&pid, "/usr/bin/xcrun", nullptr, nullptr, libArgv, nullptr) != 0) {
57+
unlink(irFile.data());
58+
unlink(libFile.data());
59+
fmt::println("Failed to spawn .metallib xcrun process");
60+
exit(1);
61+
}
62+
63+
if (waitpid(pid, &status, 0) == -1) {
64+
unlink(irFile.data());
65+
unlink(libFile.data());
66+
fmt::println("Failed to wait .metallib xcrun process");
67+
exit(1);
68+
}
69+
70+
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
71+
unlink(irFile.data());
72+
unlink(libFile.data());
73+
fmt::println(".metallib exited with code {}", WEXITSTATUS(status));
74+
exit(1);
75+
}
76+
77+
// Read from built .metallib
78+
FILE* file = fopen(libFile.data(), "rb");
79+
fseek(file, 0, SEEK_END);
80+
size_t fileSize = ftell(file);
81+
fseek(file, 0, SEEK_SET);
82+
auto data = std::vector<uint8_t>(fileSize);
83+
fread(data.data(), 1, fileSize, file);
84+
fclose(file);
85+
86+
// Cleanup temporary files
87+
unlink(irFile.data());
88+
unlink(libFile.data());
89+
90+
return data;
91+
}
92+
93+
int AirCompiler::makeTemporaryFile(std::string &extension) {
94+
const std::string path = "/tmp/xenos_metal_XXXXXX";
95+
96+
size_t size = path.size() + extension.size() + 1;
97+
char fullTemplate[size];
98+
snprintf(fullTemplate, size, "%s%s", path.c_str(), extension.c_str());
99+
100+
int tmpFD = mkstemps(fullTemplate, extension.size());
101+
if (tmpFD == -1) {
102+
fmt::println("Failed to open temporary file, \"{}\"!", std::string(fullTemplate));
103+
unlink(fullTemplate);
104+
exit(1);
105+
}
106+
107+
extension = fullTemplate;
108+
return tmpFD;
109+
}

XenosRecomp/air_compiler.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#pragma once
2+
3+
struct AirCompiler
4+
{
5+
static std::vector<uint8_t> compile(const std::string& shaderSource);
6+
7+
private:
8+
static int makeTemporaryFile(std::string& extension);
9+
};

XenosRecomp/main.cpp

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "shader.h"
22
#include "shader_recompiler.h"
33
#include "dxc_compiler.h"
4+
#include "air_compiler.h"
45

56
static std::unique_ptr<uint8_t[]> readAllBytes(const char* filePath, size_t& fileSize)
67
{
@@ -26,6 +27,7 @@ struct RecompiledShader
2627
uint8_t* data = nullptr;
2728
IDxcBlob* dxil = nullptr;
2829
std::vector<uint8_t> spirv;
30+
std::vector<uint8_t> air;
2931
uint32_t specConstantsMask = 0;
3032
};
3133

@@ -133,6 +135,10 @@ int main(int argc, char** argv)
133135
assert(*(reinterpret_cast<uint32_t *>(shader.dxil->GetBufferPointer()) + 1) != 0 && "DXIL was not signed properly!");
134136
#endif
135137

138+
#ifdef XENOS_RECOMP_AIR
139+
shader.air = AirCompiler::compile(recompiler.out);
140+
#endif
141+
136142
IDxcBlob* spirv = dxcCompiler.compile(recompiler.out, recompiler.isPixelShader, false, true);
137143
assert(spirv != nullptr);
138144

@@ -154,18 +160,24 @@ int main(int argc, char** argv)
154160

155161
std::vector<uint8_t> dxil;
156162
std::vector<uint8_t> spirv;
163+
std::vector<uint8_t> air;
157164

158165
for (auto& [hash, shader] : shaders)
159166
{
160-
f.println("\t{{ 0x{:X}, {}, {}, {}, {}, {} }},",
161-
hash, dxil.size(), (shader.dxil != nullptr) ? shader.dxil->GetBufferSize() : 0, spirv.size(), shader.spirv.size(), shader.specConstantsMask);
167+
f.println("\t{{ 0x{:X}, {}, {}, {}, {}, {}, {}, {} }},",
168+
hash, dxil.size(), (shader.dxil != nullptr) ? shader.dxil->GetBufferSize() : 0,
169+
spirv.size(), shader.spirv.size(), air.size(), shader.air.size(), shader.specConstantsMask);
162170

163171
if (shader.dxil != nullptr)
164172
{
165173
dxil.insert(dxil.end(), reinterpret_cast<uint8_t *>(shader.dxil->GetBufferPointer()),
166174
reinterpret_cast<uint8_t *>(shader.dxil->GetBufferPointer()) + shader.dxil->GetBufferSize());
167175
}
168-
176+
177+
#ifdef XENOS_RECOMP_AIR
178+
air.insert(air.end(), shader.air.begin(), shader.air.end());
179+
#endif
180+
169181
spirv.insert(spirv.end(), shader.spirv.begin(), shader.spirv.end());
170182
}
171183

@@ -189,6 +201,22 @@ int main(int argc, char** argv)
189201
f.println("const size_t g_dxilCacheDecompressedSize = {};", dxil.size());
190202
#endif
191203

204+
#ifdef XENOS_RECOMP_AIR
205+
fmt::println("Compressing AIR cache...");
206+
207+
std::vector<uint8_t> airCompressed(ZSTD_compressBound(air.size()));
208+
airCompressed.resize(ZSTD_compress(airCompressed.data(), airCompressed.size(), air.data(), air.size(), level));
209+
210+
f.print("const uint8_t g_compressedAirCache[] = {{");
211+
212+
for (auto data : airCompressed)
213+
f.print("{},", data);
214+
215+
f.println("}};");
216+
f.println("const size_t g_airCacheCompressedSize = {};", airCompressed.size());
217+
f.println("const size_t g_airCacheDecompressedSize = {};", air.size());
218+
#endif
219+
192220
fmt::println("Compressing SPIRV cache...");
193221

194222
std::vector<uint8_t> spirvCompressed(ZSTD_compressBound(spirv.size()));

0 commit comments

Comments
 (0)