ppsspp/unittest/TestShaderGenerators.cpp

653 lines
18 KiB
C++

#include "ppsspp_config.h"
#include <algorithm>
#include "Common/StringUtils.h"
#include "GPU/Common/ShaderId.h"
#include "GPU/Common/ShaderCommon.h"
#include "GPU/Common/GPUStateUtils.h"
#include "Common/Data/Random/Rng.h"
#include "GPU/Vulkan/VulkanContext.h"
#include "GPU/Common/FragmentShaderGenerator.h"
#include "GPU/Common/VertexShaderGenerator.h"
#include "GPU/Common/GeometryShaderGenerator.h"
#include "GPU/Common/ReinterpretFramebuffer.h"
#include "GPU/Common/StencilCommon.h"
#include "GPU/Common/DepalettizeShaderCommon.h"
#if PPSSPP_PLATFORM(WINDOWS)
#include <wrl/client.h>
#include "GPU/D3D11/D3D11Util.h"
#include "GPU/D3D11/D3D11Loader.h"
#include "GPU/D3D9/D3DCompilerLoader.h"
#include "GPU/D3D9/D3D9ShaderCompiler.h"
#endif
static constexpr size_t CODE_BUFFER_SIZE = 32768;
bool GenerateFShader(FShaderID id, char *buffer, ShaderLanguage lang, Draw::Bugs bugs, std::string *errorString) {
buffer[0] = '\0';
FragmentShaderFlags flags;
uint64_t uniformMask;
switch (lang) {
case ShaderLanguage::GLSL_VULKAN:
{
ShaderLanguageDesc compat(ShaderLanguage::GLSL_VULKAN);
return GenerateFragmentShader(id, buffer, compat, bugs, &uniformMask, &flags, errorString);
}
case ShaderLanguage::GLSL_1xx:
{
ShaderLanguageDesc compat(ShaderLanguage::GLSL_1xx);
return GenerateFragmentShader(id, buffer, compat, bugs, &uniformMask, &flags, errorString);
}
case ShaderLanguage::GLSL_3xx:
{
ShaderLanguageDesc compat(ShaderLanguage::GLSL_3xx);
return GenerateFragmentShader(id, buffer, compat, bugs, &uniformMask, &flags, errorString);
}
case ShaderLanguage::HLSL_D3D9:
{
ShaderLanguageDesc compat(ShaderLanguage::HLSL_D3D9);
return GenerateFragmentShader(id, buffer, compat, bugs, &uniformMask, &flags, errorString);
}
case ShaderLanguage::HLSL_D3D11:
{
ShaderLanguageDesc compat(ShaderLanguage::HLSL_D3D11);
return GenerateFragmentShader(id, buffer, compat, bugs, &uniformMask, &flags, errorString);
}
default:
return false;
}
}
bool GenerateVShader(VShaderID id, char *buffer, ShaderLanguage lang, Draw::Bugs bugs, std::string *errorString) {
buffer[0] = '\0';
VertexShaderFlags flags;
uint32_t attrMask;
uint64_t uniformMask;
switch (lang) {
case ShaderLanguage::GLSL_VULKAN:
{
ShaderLanguageDesc compat(ShaderLanguage::GLSL_VULKAN);
return GenerateVertexShader(id, buffer, compat, bugs, &attrMask, &uniformMask, &flags, errorString);
}
case ShaderLanguage::GLSL_1xx:
{
ShaderLanguageDesc compat(ShaderLanguage::GLSL_1xx);
return GenerateVertexShader(id, buffer, compat, bugs, &attrMask, &uniformMask, &flags, errorString);
}
case ShaderLanguage::GLSL_3xx:
{
ShaderLanguageDesc compat(ShaderLanguage::GLSL_3xx);
return GenerateVertexShader(id, buffer, compat, bugs, &attrMask, &uniformMask, &flags, errorString);
}
case ShaderLanguage::HLSL_D3D9:
{
ShaderLanguageDesc compat(ShaderLanguage::HLSL_D3D9);
return GenerateVertexShader(id, buffer, compat, bugs, &attrMask, &uniformMask, &flags, errorString);
}
case ShaderLanguage::HLSL_D3D11:
{
ShaderLanguageDesc compat(ShaderLanguage::HLSL_D3D11);
return GenerateVertexShader(id, buffer, compat, bugs, &attrMask, &uniformMask, &flags, errorString);
}
default:
return false;
}
}
bool GenerateGShader(GShaderID id, char *buffer, ShaderLanguage lang, Draw::Bugs bugs, std::string *errorString) {
buffer[0] = '\0';
errorString->clear();
switch (lang) {
case ShaderLanguage::GLSL_VULKAN:
{
ShaderLanguageDesc compat(ShaderLanguage::GLSL_VULKAN);
return GenerateGeometryShader(id, buffer, compat, bugs, errorString);
}
/*
case ShaderLanguage::GLSL_3xx:
{
ShaderLanguageDesc compat(ShaderLanguage::GLSL_3xx);
return GenerateGeometryShader(id, buffer, compat, bugs, errorString);
}
case ShaderLanguage::HLSL_D3D11:
{
ShaderLanguageDesc compat(ShaderLanguage::HLSL_D3D11);
return GenerateGeometryShader(id, buffer, compat, bugs, errorString);
}
*/
default:
return false;
}
}
static VkShaderStageFlagBits StageToVulkan(ShaderStage stage) {
switch (stage) {
case ShaderStage::Vertex: return VK_SHADER_STAGE_VERTEX_BIT;
case ShaderStage::Geometry: return VK_SHADER_STAGE_GEOMETRY_BIT;
case ShaderStage::Compute: return VK_SHADER_STAGE_COMPUTE_BIT;
case ShaderStage::Fragment: return VK_SHADER_STAGE_FRAGMENT_BIT;
}
return VK_SHADER_STAGE_FRAGMENT_BIT;
}
bool TestCompileShader(const char *buffer, ShaderLanguage lang, ShaderStage stage, std::string *errorMessage) {
std::vector<uint32_t> spirv;
switch (lang) {
#if PPSSPP_PLATFORM(WINDOWS)
case ShaderLanguage::HLSL_D3D11:
{
const char *programType = nullptr;
switch (stage) {
case ShaderStage::Vertex: programType = "vs_4_0"; break;
case ShaderStage::Fragment: programType = "ps_4_0"; break;
case ShaderStage::Geometry: programType = "gs_4_0"; break;
default: return false;
}
auto output = CompileShaderToBytecodeD3D11(buffer, strlen(buffer), programType, 0);
return !output.empty();
}
case ShaderLanguage::HLSL_D3D9:
{
const char *programType = nullptr;
switch (stage) {
case ShaderStage::Vertex: programType = "vs_3_0"; break;
case ShaderStage::Fragment: programType = "ps_3_0"; break;
default: return false;
}
Microsoft::WRL::ComPtr<ID3DBlob> blob;
HRESULT hr = CompileShaderToByteCodeD3D9(buffer, programType, errorMessage, &blob);
return SUCCEEDED(hr);
}
#endif
case ShaderLanguage::GLSL_VULKAN:
return GLSLtoSPV(StageToVulkan(stage), buffer, GLSLVariant::VULKAN, spirv, errorMessage);
case ShaderLanguage::GLSL_1xx:
return GLSLtoSPV(StageToVulkan(stage), buffer, GLSLVariant::GL140, spirv, errorMessage);
case ShaderLanguage::GLSL_3xx:
return GLSLtoSPV(StageToVulkan(stage), buffer, GLSLVariant::GLES300, spirv, errorMessage);
default:
return false;
}
}
void PrintDiff(const char *a, const char *b) {
// Stupidest diff ever: Just print both lines, and a few around it, when we find a mismatch.
std::vector<std::string> a_lines;
std::vector<std::string> b_lines;
SplitString(a, '\n', a_lines);
SplitString(b, '\n', b_lines);
for (size_t i = 0; i < a_lines.size() && i < b_lines.size(); i++) {
if (a_lines[i] != b_lines[i]) {
// Print some context
for (size_t j = std::max((int)i - 4, 0); j < i; j++) {
printf("%s\n", a_lines[j].c_str());
}
printf("DIFF found at line %d:\n", (int)i);
printf("a: %s\n", a_lines[i].c_str());
printf("b: %s\n", b_lines[i].c_str());
printf("...continues...\n");
for (size_t j = i + 1; j < i + 5 && j < a_lines.size() && j < b_lines.size(); j++) {
printf("a: %s\n", a_lines[j].c_str());
printf("b: %s\n", b_lines[j].c_str());
}
printf("==================\n");
return;
}
}
}
const char *ShaderLanguageToString(ShaderLanguage lang) {
switch (lang) {
case HLSL_D3D11: return "HLSL_D3D11";
case HLSL_D3D9: return "HLSL_D3D9";
case GLSL_VULKAN: return "GLSL_VULKAN";
case GLSL_1xx: return "GLSL_1xx";
case GLSL_3xx: return "GLSL_3xx";
default: return "N/A";
}
}
bool TestReinterpretShaders() {
Draw::Bugs bugs;
ShaderLanguage languages[] = {
#if PPSSPP_PLATFORM(WINDOWS)
ShaderLanguage::HLSL_D3D11,
#endif
ShaderLanguage::GLSL_VULKAN,
ShaderLanguage::GLSL_3xx,
};
GEBufferFormat fmts[3] = {
GE_FORMAT_565,
GE_FORMAT_5551,
GE_FORMAT_4444,
};
char *buffer = new char[65536];
// Generate all despite failures - it's only 6.
bool failed = false;
for (int k = 0; k < ARRAY_SIZE(languages); k++) {
printf("=== %s ===\n\n", ShaderLanguageToString(languages[k]));
ShaderLanguageDesc desc(languages[k]);
std::string errorMessage;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == j)
continue; // useless shader!
ShaderWriter writer(buffer, desc, ShaderStage::Fragment);
GenerateReinterpretFragmentShader(writer, fmts[i], fmts[j]);
if (strlen(buffer) >= 8192) {
printf("Reinterpret fragment shader %d exceeded buffer:\n\n%s\n", (int)j, LineNumberString(buffer).c_str());
failed = true;
}
if (!TestCompileShader(buffer, languages[k], ShaderStage::Fragment, &errorMessage)) {
printf("Error compiling reinterpret fragment shader %d:\n\n%s\n\n%s\n", (int)j, LineNumberString(buffer).c_str(), errorMessage.c_str());
failed = true;
} else {
printf("===\n%s\n===\n", buffer);
}
}
}
}
delete[] buffer;
return !failed;
}
bool TestStencilShaders() {
Draw::Bugs bugs;
ShaderLanguage languages[] = {
#if PPSSPP_PLATFORM(WINDOWS)
ShaderLanguage::HLSL_D3D9,
ShaderLanguage::HLSL_D3D11,
#endif
ShaderLanguage::GLSL_VULKAN,
ShaderLanguage::GLSL_3xx,
};
char *buffer = new char[65536];
bool failed = false;
for (int k = 0; k < ARRAY_SIZE(languages); k++) {
printf("=== %s ===\n\n", ShaderLanguageToString(languages[k]));
ShaderLanguageDesc desc(languages[k]);
std::string errorMessage;
// Generate all despite failures - it's only a few.
// Only use export on Vulkan, because GLSL_3xx is ES which doesn't support stencil export.
bool allowUseExport = languages[k] == ShaderLanguage::GLSL_VULKAN;
for (int useExport = 0; useExport <= (allowUseExport ? 1 : 0); ++useExport) {
GenerateStencilFs(buffer, desc, bugs, useExport == 1);
if (strlen(buffer) >= 8192) {
printf("Stencil fragment shader (useExport=%d) exceeded buffer:\n\n%s\n", useExport, LineNumberString(buffer).c_str());
failed = true;
}
if (!TestCompileShader(buffer, languages[k], ShaderStage::Fragment, &errorMessage)) {
printf("Error compiling stencil shader (useExport=%d):\n\n%s\n\n%s\n", useExport, LineNumberString(buffer).c_str(), errorMessage.c_str());
failed = true;
} else {
printf("===\n%s\n===\n", buffer);
}
}
GenerateStencilVs(buffer, desc);
if (strlen(buffer) >= 8192) {
printf("Stencil vertex shader exceeded buffer:\n\n%s\n", LineNumberString(buffer).c_str());
failed = true;
}
if (!TestCompileShader(buffer, languages[k], ShaderStage::Vertex, &errorMessage)) {
printf("Error compiling stencil shader:\n\n%s\n\n%s\n", LineNumberString(buffer).c_str(), errorMessage.c_str());
failed = true;
} else {
printf("===\n%s\n===\n", buffer);
}
}
delete[] buffer;
return !failed;
}
bool TestDepalShaders() {
Draw::Bugs bugs;
ShaderLanguage languages[] = {
#if PPSSPP_PLATFORM(WINDOWS)
ShaderLanguage::HLSL_D3D9,
ShaderLanguage::HLSL_D3D11,
#endif
ShaderLanguage::GLSL_VULKAN,
ShaderLanguage::GLSL_3xx,
ShaderLanguage::GLSL_1xx,
};
char *buffer = new char[65536];
for (int k = 0; k < ARRAY_SIZE(languages); k++) {
printf("=== %s ===\n\n", ShaderLanguageToString(languages[k]));
ShaderLanguageDesc desc(languages[k]);
std::string errorMessage;
// TODO: Try some different configurations of the fragment shader.
// But first just try one.
DepalConfig config{};
config.clutFormat = GE_CMODE_16BIT_ABGR4444;
config.shift = 8;
config.startPos = 64;
config.mask = 0xFF;
config.bufferFormat = GE_FORMAT_8888;
config.textureFormat = GE_TFMT_CLUT32;
config.depthUpperBits = 0;
ShaderWriter writer(buffer, desc, ShaderStage::Fragment);
GenerateDepalFs(writer, config);
if (strlen(buffer) >= 8192) {
printf("Depal shader exceeded buffer:\n\n%s\n", LineNumberString(buffer).c_str());
delete[] buffer;
return false;
}
if (!TestCompileShader(buffer, languages[k], ShaderStage::Fragment, &errorMessage)) {
printf("Error compiling depal shader:\n\n%s\n\n%s\n", LineNumberString(buffer).c_str(), errorMessage.c_str());
delete[] buffer;
return false;
} else {
printf("===\n%s\n===\n", buffer);
}
}
delete[] buffer;
return true;
}
const ShaderLanguage languages[] = {
#if PPSSPP_PLATFORM(WINDOWS)
ShaderLanguage::HLSL_D3D9,
ShaderLanguage::HLSL_D3D11,
#endif
ShaderLanguage::GLSL_VULKAN,
ShaderLanguage::GLSL_1xx,
ShaderLanguage::GLSL_3xx,
};
const int numLanguages = ARRAY_SIZE(languages);
bool TestVertexShaders() {
char *buffer[numLanguages];
for (int i = 0; i < numLanguages; i++) {
buffer[i] = new char[65536];
}
GMRng rng;
int successes = 0;
int count = 700;
Draw::Bugs bugs;
// Generate a bunch of random vertex shader IDs, try to generate shader source.
// Then compile it and check that it's ok.
for (int i = 0; i < count; i++) {
uint32_t bottom = rng.R32();
uint32_t top = rng.R32();
VShaderID id;
id.d[0] = bottom;
id.d[1] = top;
// The generated bits need some adjustment:
// We don't use these bits in the HLSL shader generator.
id.SetBits(VS_BIT_WEIGHT_FMTSCALE, 2, 0);
// If mode is through, we won't do hardware transform.
if (id.Bit(VS_BIT_IS_THROUGH)) {
id.SetBit(VS_BIT_USE_HW_TRANSFORM, 0);
}
if (!id.Bit(VS_BIT_USE_HW_TRANSFORM)) {
id.SetBit(VS_BIT_ENABLE_BONES, 0);
}
if (id.Bit(VS_BIT_VERTEX_RANGE_CULLING)) {
continue;
}
bool generateSuccess[numLanguages]{};
std::string genErrorString[numLanguages];
for (int j = 0; j < numLanguages; j++) {
generateSuccess[j] = GenerateVShader(id, buffer[j], languages[j], bugs, &genErrorString[j]);
if (!genErrorString[j].empty()) {
printf("%s\n", genErrorString[j].c_str());
}
}
for (int j = 0; j < numLanguages; j++) {
if (strlen(buffer[j]) >= CODE_BUFFER_SIZE) {
printf("Vertex shader exceeded buffer:\n\n%s\n", LineNumberString(buffer[j]).c_str());
for (int i = 0; i < numLanguages; i++) {
delete[] buffer[i];
}
return false;
}
}
// Now that we have the strings ready for easy comparison (buffer,4 in the watch window),
// let's try to compile them.
for (int j = 0; j < numLanguages; j++) {
if (generateSuccess[j]) {
std::string errorMessage;
if (!TestCompileShader(buffer[j], languages[j], ShaderStage::Vertex, &errorMessage)) {
printf("Error compiling vertex shader %d:\n\n%s\n\n%s\n", (int)j, LineNumberString(buffer[j]).c_str(), errorMessage.c_str());
for (int i = 0; i < numLanguages; i++) {
delete[] buffer[i];
}
return false;
}
successes++;
}
}
}
printf("%d/%d vertex shaders generated (it's normal that it's not all, there are invalid bit combos)\n", successes, count * numLanguages);
for (int i = 0; i < numLanguages; i++) {
delete[] buffer[i];
}
return true;
}
bool TestFragmentShaders() {
char *buffer[numLanguages];
for (int i = 0; i < numLanguages; i++) {
buffer[i] = new char[65536];
}
GMRng rng;
int successes = 0;
int count = 300;
Draw::Bugs bugs;
// Generate a bunch of random fragment shader IDs, try to generate shader source.
// Then compile it and check that it's ok.
for (int i = 0; i < count; i++) {
uint32_t bottom = rng.R32();
uint32_t top = rng.R32();
FShaderID id;
id.d[0] = bottom;
id.d[1] = top;
// bits we don't need to test because they are irrelevant on d3d11
id.SetBit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL, false);
// DX9 disabling:
if (static_cast<ReplaceAlphaType>(id.Bits(FS_BIT_STENCIL_TO_ALPHA, 2)) == ReplaceAlphaType::REPLACE_ALPHA_DUALSOURCE)
continue;
bool generateSuccess[numLanguages]{};
std::string genErrorString[numLanguages];
for (int j = 0; j < numLanguages; j++) {
generateSuccess[j] = GenerateFShader(id, buffer[j], languages[j], bugs, &genErrorString[j]);
if (!genErrorString[j].empty()) {
printf("%s\n", genErrorString[j].c_str());
}
// We ignore the contents of the error string here, not even gonna try to compile if it errors.
}
for (int j = 0; j < numLanguages; j++) {
if (strlen(buffer[j]) >= CODE_BUFFER_SIZE) {
printf("Fragment shader exceeded buffer:\n\n%s\n", LineNumberString(buffer[j]).c_str());
for (int i = 0; i < numLanguages; i++) {
delete[] buffer[i];
}
return false;
}
}
// Now that we have the strings ready for easy comparison (buffer,4 in the watch window),
// let's try to compile them.
for (int j = 0; j < numLanguages; j++) {
if (generateSuccess[j]) {
std::string errorMessage;
if (!TestCompileShader(buffer[j], languages[j], ShaderStage::Fragment, &errorMessage)) {
printf("Error compiling fragment shader:\n\n%s\n\n%s\n", LineNumberString(buffer[j]).c_str(), errorMessage.c_str());
for (int i = 0; i < numLanguages; i++) {
delete[] buffer[i];
}
return false;
}
successes++;
}
}
}
printf("%d/%d fragment shaders generated (it's normal that it's not all, there are invalid bit combos)\n", successes, count * numLanguages);
for (int i = 0; i < numLanguages; i++) {
delete[] buffer[i];
}
return true;
}
bool TestGeometryShaders() {
char *buffer[numLanguages];
for (int i = 0; i < numLanguages; i++) {
buffer[i] = new char[65536];
}
GMRng rng;
int successes = 0;
int count = 30;
Draw::Bugs bugs;
// Generate a bunch of random fragment shader IDs, try to generate shader source.
// Then compile it and check that it's ok.
for (int i = 0; i < count; i++) {
uint32_t bottom = i << 1;
GShaderID id;
id.d[0] = bottom;
id.d[1] = 0;
id.SetBit(GS_BIT_ENABLED, true);
bool generateSuccess[numLanguages]{};
std::string genErrorString[numLanguages];
for (int j = 0; j < numLanguages; j++) {
buffer[j][0] = 0;
generateSuccess[j] = GenerateGShader(id, buffer[j], languages[j], bugs, &genErrorString[j]);
if (!genErrorString[j].empty()) {
printf("%s\n", genErrorString[j].c_str());
}
// We ignore the contents of the error string here, not even gonna try to compile if it errors.
}
for (int j = 0; j < numLanguages; j++) {
if (strlen(buffer[j]) >= CODE_BUFFER_SIZE) {
printf("Geometry shader exceeded buffer:\n\n%s\n", LineNumberString(buffer[j]).c_str());
for (int i = 0; i < numLanguages; i++) {
delete[] buffer[i];
}
return false;
}
}
// Now that we have the strings ready for easy comparison (buffer,4 in the watch window),
// let's try to compile them.
for (int j = 0; j < numLanguages; j++) {
if (generateSuccess[j]) {
std::string errorMessage;
if (!TestCompileShader(buffer[j], languages[j], ShaderStage::Geometry, &errorMessage)) {
printf("Error compiling geometry shader:\n\n%s\n\n%s\n", LineNumberString(buffer[j]).c_str(), errorMessage.c_str());
for (int i = 0; i < numLanguages; i++) {
delete[] buffer[i];
}
return false;
}
successes++;
}
}
}
printf("%d/%d geometry shaders generated (it's normal that it's not all, there are invalid bit combos)\n", successes, count * numLanguages);
for (int i = 0; i < numLanguages; i++) {
delete[] buffer[i];
}
return true;
}
bool TestShaderGenerators() {
#if PPSSPP_PLATFORM(WINDOWS)
LoadD3D11();
init_glslang();
LoadD3DCompilerDynamic();
#else
init_glslang();
#endif
if (!TestStencilShaders()) {
return false;
}
if (!TestGeometryShaders()) {
return false;
}
if (!TestReinterpretShaders()) {
return false;
}
if (!TestDepalShaders()) {
return false;
}
if (!TestFragmentShaders()) {
return false;
}
if (!TestVertexShaders()) {
return false;
}
return true;
}