mirror of
https://github.com/iHaiDeeZ/SSBU.git
synced 2026-10-29 04:01:44 +01:00
v00.15's match: 940 ms of compiling a shader, up to 370 waiting, their draws skipped (the stage half drawn, the sky black then white), three cores busy compiling (frame times 15-100 ms). - scripts/build-mesa-host.sh: RADV for the PC with the console's identity (Mesa's ac_ps4_info.h, AC_PS4_HOST_IDENTITY: the GPU the queries describe, DRM 3.61, address32_hi 0x44, conditional rendering off), same build id and btver2 target (no FMA): the same cache folder and pipeline keys as the console's (checked against the console's cache) - scripts/build-dxvk-host.sh: DXVK for the PC as the PS4 build is (native_ps4) - tools/shaderpack + scripts/build-shaderpack.sh: the game's 45,653 DXBC shaders made through them (8.5 min on 20 threads): RADV's cache (ssbu_pack.foz, 331 MB) and DXVK's IR cache - scripts/pack-shadercache.py: the DXBC cache folder as one file (dump/shadercache.pak), read by the runtime instead of 45,000 files (the renderer waited in stat 30% of its time) - the port reads ssbu_pack as a read-only Mesa cache (MESA_DISK_CACHE_READ_ONLY_FOZ_DBS) - the PS4 DXVK rebuilt from the committed tree: its cache version matches the PC's
275 lines
13 KiB
C++
275 lines
13 KiB
C++
// The shader pack (scripts/build-shaderpack.sh): the game's DXBC shaders (windowsmash dump/shadercache - the
|
|
// ahead-of-time compiler's and the console's own) made on a PC through DXVK as the PS4 build has it
|
|
// (scripts/build-dxvk-host.sh) on a PC copy of the console's RADV (scripts/build-mesa-host.sh: the console's
|
|
// device and cache identity), so that the two caches the game reads on the console come out filled: RADV's
|
|
// (Mesa's single-file cache: the GPU code of each shader library) and DXVK's (each shader's IR). On the console
|
|
// a new shader then costs a cache read, not the second of compiling v00.15 measured (940 ms on average).
|
|
//
|
|
// shaderpack <libvulkan_radeon.so> <list of DXBC paths> [first] [count] [threads]
|
|
//
|
|
// The device is made as the runtime makes it (runtime/gpu_d3d11.c: BGRA support, feature level 11.1 or 11.0) and
|
|
// each shader as it does (runtime/gpu_d3d11.c create_on: by the stage in its cache name, v<N>_<vs|ps|cs>_...).
|
|
// The environment says where the caches go and what DXVK and drm-shim are told (scripts/build-shaderpack.sh).
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <dlfcn.h>
|
|
|
|
#include <d3d11.h>
|
|
#include <vulkan/vulkan.h>
|
|
|
|
namespace {
|
|
|
|
// ---- the app's Vulkan entry point, as the port's (port/ps4_vk_wsi.cpp) wraps RADV: the instance extensions DXVK's
|
|
// PS4 WSI asks for (VK_KHR_surface, VK_EXT_headless_surface) and VK_KHR_swapchain listed, and left out of what RADV
|
|
// is asked to create - it has no window system. Nothing is presented here: the presentation entry points only
|
|
// exist so that nothing calls through a null pointer.
|
|
|
|
PFN_vkGetInstanceProcAddr g_icd = nullptr; // RADV's vk_icdGetInstanceProcAddr
|
|
VkInstance g_instance = VK_NULL_HANDLE;
|
|
|
|
template <typename T> T icd(VkInstance instance, const char* name) {
|
|
return reinterpret_cast<T>(g_icd(instance, name));
|
|
}
|
|
|
|
VkResult append_extensions(std::vector<VkExtensionProperties>& list,
|
|
std::initializer_list<std::pair<const char*, uint32_t>> extra, uint32_t* count,
|
|
VkExtensionProperties* props) {
|
|
for (const auto& [name, spec] : extra) {
|
|
VkExtensionProperties p{};
|
|
std::strncpy(p.extensionName, name, VK_MAX_EXTENSION_NAME_SIZE - 1);
|
|
p.specVersion = spec;
|
|
list.push_back(p);
|
|
}
|
|
if (!props) {
|
|
*count = uint32_t(list.size());
|
|
return VK_SUCCESS;
|
|
}
|
|
uint32_t n = std::min<uint32_t>(*count, uint32_t(list.size()));
|
|
std::memcpy(props, list.data(), n * sizeof(VkExtensionProperties));
|
|
*count = n;
|
|
return n < list.size() ? VK_INCOMPLETE : VK_SUCCESS;
|
|
}
|
|
|
|
std::vector<const char*> without(uint32_t count, const char* const* names, std::initializer_list<const char*> drop) {
|
|
std::vector<const char*> out;
|
|
for (uint32_t k = 0; k < count; k++)
|
|
if (std::none_of(drop.begin(), drop.end(), [&](const char* d) { return std::strcmp(names[k], d) == 0; }))
|
|
out.push_back(names[k]);
|
|
return out;
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL EnumerateInstanceExtensionProperties(const char* layer, uint32_t* count,
|
|
VkExtensionProperties* props) {
|
|
auto real = icd<PFN_vkEnumerateInstanceExtensionProperties>(VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties");
|
|
uint32_t n = 0;
|
|
real(layer, &n, nullptr);
|
|
std::vector<VkExtensionProperties> list(n);
|
|
real(layer, &n, list.data());
|
|
list.resize(n);
|
|
return append_extensions(list, {{VK_KHR_SURFACE_EXTENSION_NAME, 25}, {VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME, 1}},
|
|
count, props);
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL CreateInstance(const VkInstanceCreateInfo* info, const VkAllocationCallbacks* alloc,
|
|
VkInstance* instance) {
|
|
auto real = icd<PFN_vkCreateInstance>(VK_NULL_HANDLE, "vkCreateInstance");
|
|
VkInstanceCreateInfo copy = *info;
|
|
auto names = without(info->enabledExtensionCount, info->ppEnabledExtensionNames,
|
|
{VK_KHR_SURFACE_EXTENSION_NAME, VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME});
|
|
copy.enabledExtensionCount = uint32_t(names.size());
|
|
copy.ppEnabledExtensionNames = names.data();
|
|
VkResult r = real(©, alloc, instance);
|
|
if (r == VK_SUCCESS) g_instance = *instance;
|
|
return r;
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL EnumerateDeviceExtensionProperties(VkPhysicalDevice physical, const char* layer,
|
|
uint32_t* count, VkExtensionProperties* props) {
|
|
auto real = icd<PFN_vkEnumerateDeviceExtensionProperties>(g_instance, "vkEnumerateDeviceExtensionProperties");
|
|
uint32_t n = 0;
|
|
real(physical, layer, &n, nullptr);
|
|
std::vector<VkExtensionProperties> list(n);
|
|
real(physical, layer, &n, list.data());
|
|
list.resize(n);
|
|
return append_extensions(list, {{VK_KHR_SWAPCHAIN_EXTENSION_NAME, 70}}, count, props);
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL CreateDevice(VkPhysicalDevice physical, const VkDeviceCreateInfo* info,
|
|
const VkAllocationCallbacks* alloc, VkDevice* device) {
|
|
auto real = icd<PFN_vkCreateDevice>(g_instance, "vkCreateDevice");
|
|
VkDeviceCreateInfo copy = *info;
|
|
auto names = without(info->enabledExtensionCount, info->ppEnabledExtensionNames, {VK_KHR_SWAPCHAIN_EXTENSION_NAME});
|
|
copy.enabledExtensionCount = uint32_t(names.size());
|
|
copy.ppEnabledExtensionNames = names.data();
|
|
return real(physical, ©, alloc, device);
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL CreateHeadlessSurfaceEXT(VkInstance, const VkHeadlessSurfaceCreateInfoEXT*,
|
|
const VkAllocationCallbacks*, VkSurfaceKHR* surface) {
|
|
*surface = reinterpret_cast<VkSurfaceKHR>(uintptr_t(1));
|
|
return VK_SUCCESS;
|
|
}
|
|
VKAPI_ATTR void VKAPI_CALL DestroySurfaceKHR(VkInstance, VkSurfaceKHR, const VkAllocationCallbacks*) {}
|
|
VKAPI_ATTR VkResult VKAPI_CALL GetPhysicalDeviceSurfaceSupportKHR(VkPhysicalDevice, uint32_t family, VkSurfaceKHR,
|
|
VkBool32* supported) {
|
|
*supported = family == 0 ? VK_TRUE : VK_FALSE;
|
|
return VK_SUCCESS;
|
|
}
|
|
VKAPI_ATTR VkResult VKAPI_CALL NoSurface() { return VK_ERROR_SURFACE_LOST_KHR; }
|
|
|
|
PFN_vkVoidFunction intercept(const char* name) {
|
|
#define SHADERPACK_ENTRY(fn) \
|
|
if (std::strcmp(name, "vk" #fn) == 0) return reinterpret_cast<PFN_vkVoidFunction>(&fn)
|
|
SHADERPACK_ENTRY(EnumerateInstanceExtensionProperties);
|
|
SHADERPACK_ENTRY(CreateInstance);
|
|
SHADERPACK_ENTRY(EnumerateDeviceExtensionProperties);
|
|
SHADERPACK_ENTRY(CreateDevice);
|
|
SHADERPACK_ENTRY(CreateHeadlessSurfaceEXT);
|
|
SHADERPACK_ENTRY(DestroySurfaceKHR);
|
|
SHADERPACK_ENTRY(GetPhysicalDeviceSurfaceSupportKHR);
|
|
#undef SHADERPACK_ENTRY
|
|
static const char* const unpresented[] = {
|
|
"vkGetPhysicalDeviceSurfaceCapabilitiesKHR", "vkGetPhysicalDeviceSurfaceCapabilities2KHR",
|
|
"vkGetPhysicalDeviceSurfaceFormatsKHR", "vkGetPhysicalDeviceSurfaceFormats2KHR",
|
|
"vkGetPhysicalDeviceSurfacePresentModesKHR", "vkCreateSwapchainKHR", "vkGetSwapchainImagesKHR",
|
|
"vkAcquireNextImageKHR", "vkAcquireNextImage2KHR", "vkQueuePresentKHR"};
|
|
for (const char* u : unpresented)
|
|
if (std::strcmp(name, u) == 0) return reinterpret_cast<PFN_vkVoidFunction>(&NoSurface);
|
|
return nullptr;
|
|
}
|
|
|
|
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetDeviceProcAddr(VkDevice device, const char* name) {
|
|
if (std::strcmp(name, "vkGetDeviceProcAddr") == 0) return reinterpret_cast<PFN_vkVoidFunction>(&GetDeviceProcAddr);
|
|
if (auto fn = intercept(name)) return fn;
|
|
static auto real = icd<PFN_vkGetDeviceProcAddr>(g_instance, "vkGetDeviceProcAddr");
|
|
return real(device, name);
|
|
}
|
|
|
|
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetInstanceProcAddr(VkInstance instance, const char* name) {
|
|
if (std::strcmp(name, "vkGetInstanceProcAddr") == 0)
|
|
return reinterpret_cast<PFN_vkVoidFunction>(&GetInstanceProcAddr);
|
|
if (std::strcmp(name, "vkGetDeviceProcAddr") == 0) return reinterpret_cast<PFN_vkVoidFunction>(&GetDeviceProcAddr);
|
|
if (auto fn = intercept(name)) return fn;
|
|
return g_icd(instance, name);
|
|
}
|
|
|
|
// ---- the shaders -----------------------------------------------------------------------------------------------
|
|
|
|
enum class Stage { Vertex, Pixel, Compute, Unknown };
|
|
|
|
// runtime/gpu_d3d11.c's cache names: v<version>_<vs|ps|cs>_<hash>_<vs outputs>_<tables>.dxbc
|
|
Stage stage_of(const std::string& path) {
|
|
const size_t slash = path.find_last_of('/');
|
|
const std::string name = slash == std::string::npos ? path : path.substr(slash + 1);
|
|
const size_t u = name.find('_');
|
|
if (u == std::string::npos || name.size() < u + 3) return Stage::Unknown;
|
|
const std::string s = name.substr(u + 1, 2);
|
|
return s == "vs" ? Stage::Vertex : s == "ps" ? Stage::Pixel : s == "cs" ? Stage::Compute : Stage::Unknown;
|
|
}
|
|
|
|
bool read_file(const std::string& path, std::vector<char>& out) {
|
|
FILE* f = std::fopen(path.c_str(), "rb");
|
|
if (!f) return false;
|
|
std::fseek(f, 0, SEEK_END);
|
|
const long n = std::ftell(f);
|
|
std::fseek(f, 0, SEEK_SET);
|
|
out.resize(n > 0 ? size_t(n) : 0);
|
|
const bool ok = n > 0 && std::fread(out.data(), 1, out.size(), f) == out.size();
|
|
std::fclose(f);
|
|
return ok;
|
|
}
|
|
|
|
// as runtime/gpu_d3d11.c create_on
|
|
HRESULT create(ID3D11Device* dev, Stage stage, const std::vector<char>& code) {
|
|
IUnknown* obj = nullptr;
|
|
HRESULT hr = E_INVALIDARG;
|
|
if (stage == Stage::Vertex)
|
|
hr = dev->CreateVertexShader(code.data(), code.size(), nullptr, reinterpret_cast<ID3D11VertexShader**>(&obj));
|
|
else if (stage == Stage::Pixel)
|
|
hr = dev->CreatePixelShader(code.data(), code.size(), nullptr, reinterpret_cast<ID3D11PixelShader**>(&obj));
|
|
else if (stage == Stage::Compute)
|
|
hr = dev->CreateComputeShader(code.data(), code.size(), nullptr, reinterpret_cast<ID3D11ComputeShader**>(&obj));
|
|
if (obj) obj->Release();
|
|
return hr;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
extern "C" PFN_vkGetInstanceProcAddr dxvk_ps4_vk_get_instance_proc_addr(void) { return &GetInstanceProcAddr; }
|
|
|
|
int main(int argc, char** argv) {
|
|
if (argc < 3) {
|
|
std::fprintf(stderr, "usage: %s <libvulkan_radeon.so> <list of DXBC paths> [first] [count] [threads]\n", argv[0]);
|
|
return 2;
|
|
}
|
|
void* radv = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL);
|
|
if (!radv || !(g_icd = reinterpret_cast<PFN_vkGetInstanceProcAddr>(dlsym(radv, "vk_icdGetInstanceProcAddr")))) {
|
|
std::fprintf(stderr, "shaderpack: cannot load RADV from %s: %s\n", argv[1], dlerror());
|
|
return 1;
|
|
}
|
|
std::vector<std::string> files;
|
|
if (FILE* list = std::fopen(argv[2], "r")) {
|
|
char line[4096];
|
|
while (std::fgets(line, sizeof line, list)) {
|
|
line[std::strcspn(line, "\r\n")] = 0;
|
|
if (line[0]) files.emplace_back(line);
|
|
}
|
|
std::fclose(list);
|
|
}
|
|
const size_t first = argc > 3 ? std::stoul(argv[3]) : 0;
|
|
const size_t end = std::min(files.size(), argc > 4 ? first + std::stoul(argv[4]) : files.size());
|
|
const unsigned threads = argc > 5 ? unsigned(std::stoul(argv[5])) : std::max(1u, std::thread::hardware_concurrency());
|
|
if (first >= end) {
|
|
std::fprintf(stderr, "shaderpack: nothing to do (%zu files, from %zu)\n", files.size(), first);
|
|
return 0;
|
|
}
|
|
|
|
const D3D_FEATURE_LEVEL levels[] = {D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0};
|
|
ID3D11Device* dev = nullptr;
|
|
ID3D11DeviceContext* ctx = nullptr;
|
|
HRESULT hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, levels,
|
|
2, D3D11_SDK_VERSION, &dev, nullptr, &ctx);
|
|
if (FAILED(hr)) {
|
|
std::fprintf(stderr, "shaderpack: D3D11CreateDevice failed (%#x)\n", unsigned(hr));
|
|
return 1;
|
|
}
|
|
|
|
std::atomic<size_t> next{first}, done{0}, failed{0};
|
|
const auto t0 = std::chrono::steady_clock::now();
|
|
auto work = [&] {
|
|
std::vector<char> code;
|
|
for (size_t i; (i = next++) < end;) {
|
|
const Stage stage = stage_of(files[i]);
|
|
if (stage == Stage::Unknown || !read_file(files[i], code) || FAILED(create(dev, stage, code))) {
|
|
if (failed++ < 20) std::fprintf(stderr, "shaderpack: %s not made\n", files[i].c_str());
|
|
}
|
|
done++;
|
|
}
|
|
};
|
|
std::vector<std::thread> pool;
|
|
for (unsigned k = 0; k < threads; k++) pool.emplace_back(work);
|
|
for (size_t last = 0; done.load() < end - first;) {
|
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
const size_t d = done.load();
|
|
const double s = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
|
|
if (d - last >= 200 || d == end - first) {
|
|
std::fprintf(stderr, "shaderpack: %zu of %zu (%.0f a second)\n", first + d, files.size(), d / s);
|
|
last = d;
|
|
}
|
|
}
|
|
for (auto& t : pool) t.join();
|
|
ctx->Release();
|
|
dev->Release(); // (DXVK's cache written out, and Mesa's queued cache writes finished, as the device goes)
|
|
const double s = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
|
|
std::fprintf(stderr, "shaderpack: %zu shaders made in %.1f s, %zu failed\n", end - first, s, failed.load());
|
|
return failed ? 3 : 0;
|
|
}
|