diff --git a/CMakeLists.txt b/CMakeLists.txt index ce37815473..1497230247 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1354,6 +1354,8 @@ set(GPU_SOURCES GPU/Common/DrawEngineCommon.h GPU/Common/PresentationCommon.cpp GPU/Common/PresentationCommon.h + GPU/Common/ReinterpretFramebuffer.cpp + GPU/Common/ReinterpretFramebuffer.h GPU/Common/ShaderId.cpp GPU/Common/ShaderId.h GPU/Common/ShaderUniforms.cpp diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index f3ebb5328d..fe60080c88 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -386,6 +386,7 @@ + diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index 38d3896534..4f5d867492 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -381,6 +381,9 @@ GPU + + Data\Collections + @@ -850,4 +853,4 @@ Math\fast - \ No newline at end of file + diff --git a/Common/Data/Collections/Slice.h b/Common/Data/Collections/Slice.h new file mode 100644 index 0000000000..a58b902481 --- /dev/null +++ b/Common/Data/Collections/Slice.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +// Like a const begin/end pair, just more convenient to use (and can only be used for linear array data). +// Inspired by Rust's slices and Google's StringPiece. +template +struct Slice { + // View some memory as a slice. + Slice(const T *data, size_t size) : data_(data), size_(size) {} + + // Intentionally non-explicit. + // View a const array as a slice. + template + Slice(const T(&data)[N]) : data_(data), size_(N) {} + + // Intentionally non-explicit. + // View a const array as a slice. + Slice(const std::vector &data) : data_(data.data()), size_(data.size()) {} + + const T &operator[](size_t index) const { + return data_[index]; + } + + size_t size() const { + return size_; + } + + // "Iterators" + const T *begin() const { + return data_; + } + const T *end() const { + return data_ + size_; + } + + static Slice empty() { + return Slice(nullptr, 0); + } + + bool is_empty() const { + return size_ == 0; + } + +private: + const T *data_; + size_t size_; +}; diff --git a/Common/GPU/ShaderWriter.cpp b/Common/GPU/ShaderWriter.cpp index cebb0d017a..ee29dbb83f 100644 --- a/Common/GPU/ShaderWriter.cpp +++ b/Common/GPU/ShaderWriter.cpp @@ -64,11 +64,12 @@ const char *hlsl_preamble_vs = "\n"; // Unsafe. But doesn't matter, we'll use big buffers for shader gen. -void ShaderWriter::F(const char *format, ...) { +ShaderWriter & ShaderWriter::F(const char *format, ...) { va_list args; va_start(args, format); p_ += vsprintf(p_, format, args); va_end(args); + return *this; } void ShaderWriter::Preamble(const char **gl_extensions, size_t num_gl_extensions) { @@ -110,6 +111,9 @@ void ShaderWriter::Preamble(const char **gl_extensions, size_t num_gl_extensions C("#define DISCARD discard\n"); if (lang_.gles) { C("precision lowp float;\n"); + if (lang_.glslES30) { + C("precision highp int;\n"); + } } break; case ShaderStage::Vertex: @@ -128,3 +132,126 @@ void ShaderWriter::Preamble(const char **gl_extensions, size_t num_gl_extensions break; } } + +void ShaderWriter::BeginVSMain(Slice inputs, Slice uniforms, Slice varyings) { + switch (lang_.shaderLanguage) { + case HLSL_D3D11: + case HLSL_D3D9: + break; + case GLSL_VULKAN: + default: + C("void main() {\n"); + break; + } +} + +void ShaderWriter::BeginFSMain(Slice uniforms, Slice varyings) { + switch (lang_.shaderLanguage) { + case HLSL_D3D11: + if (!uniforms.is_empty()) { + for (auto &uniform : uniforms) { + //F(" %s %s : %s;\n", uniform.type, uniform.name, uniform.index); + } + } + // Let's do the varyings as parameters to main, no struct. + C("vec4 main("); + for (auto &varying : varyings) { + F(" %s %s : %s, ", varying.type, varying.name, varying.semantic); + } + // Erase the last comma + Rewind(2); + + F(") : SV_Target0 {\n"); + break; + case HLSL_D3D9: + for (auto &uniform : uniforms) { + F(" %s %s : %s;\n", uniform.type, uniform.name, uniform.index); + } + // Let's do the varyings as parameters to main, no struct. + C("vec4 main("); + for (auto &varying : varyings) { + F(" %s %s : %s, ", varying.type, varying.name, varying.semantic); + } + // Erase the last comma + Rewind(2); + + F(") : COLOR {\n"); + break; + case GLSL_VULKAN: + for (auto &varying : varyings) { + F("layout(location = %d) in %s %s; // %s\n", varying.index, varying.type, varying.name, varying.semantic); + } + C("layout (location = 0, index = 0) out vec4 fragColor0;\n"); + C("\nvoid main() {\n"); + break; + default: + for (auto &varying : varyings) { + F("in %s %s; // %s\n", varying.type, varying.name, varying.semantic); + } + if (!strcmp(lang_.fragColor0, "fragColor0")) { + C("out vec4 fragColor0;\n"); + } + C("\nvoid main() {\n"); + break; + } +} + +void ShaderWriter::EndVSMain() { + C("}\n"); +} + +void ShaderWriter::EndFSMain(const char *vec4_color_variable) { + switch (lang_.shaderLanguage) { + case HLSL_D3D11: + case HLSL_D3D9: + F(" return %s;\n", vec4_color_variable); + break; + case GLSL_VULKAN: + default: // OpenGL + F(" %s = %s;\n", lang_.fragColor0, vec4_color_variable); + break; + } + C("}\n"); +} + +void ShaderWriter::DeclareTexture2D(const char *name, int binding) { + switch (lang_.shaderLanguage) { + case HLSL_D3D11: + F("Texture2D %s : register(t%d);\n", name, binding); + break; + case HLSL_D3D9: + break; + case GLSL_VULKAN: + F("layout(set = 0, binding = %d) uniform sampler2D %s;\n", binding, name); + break; + default: + F("uniform sampler2D %s;\n", name); + break; + } +} + +void ShaderWriter::DeclareSampler2D(const char *name, int binding) { + // We only use separate samplers in HLSL D3D11, where we have no choice. + switch (lang_.shaderLanguage) { + case HLSL_D3D11: + F("SamplerState %s : register(s%d);\n", name, binding); + break; + } +} + +ShaderWriter &ShaderWriter::SampleTexture2D(const char *texName, const char *samplerName, const char *uv) { + switch (lang_.shaderLanguage) { + case HLSL_D3D11: + F("%s.Sample(%s, %s)", texName, samplerName, uv); + break; + case HLSL_D3D9: + F("tex2D(%s, %s)", texName, uv); + break; + default: + // Note: we ignore the sampler. make sure you bound samplers to the textures correctly. + F("%s(%s, %s)", lang_.texture, texName, uv); + break; + } + return *this; +} + diff --git a/Common/GPU/ShaderWriter.h b/Common/GPU/ShaderWriter.h index d870f72fe4..2e51dcc730 100644 --- a/Common/GPU/ShaderWriter.h +++ b/Common/GPU/ShaderWriter.h @@ -4,11 +4,35 @@ #include "Common/Log.h" #include "Common/GPU/Shader.h" +#include "GPU/ge_constants.h" +#include "GPU/GPUCommon.h" +#include "Common/Data/Collections/Slice.h" // Helps generate a shader compatible with all backends. +// +// Can use the uniform buffer support in thin3d. +// // Using #defines and magic in this class, we partially define our own shader language that basically looks // like GLSL, but has a few little oddities like splat3. +struct InputDef { + const char *type; + const char *name; +}; + +struct UniformDef { + const char *type; + const char *name; + int index; +}; + +struct VaryingDef { + const char *type; + const char *name; + const char *semantic; + int index; +}; + class ShaderWriter { public: ShaderWriter(char *buffer, const ShaderLanguageDesc &lang, ShaderStage stage, const char **gl_extensions, size_t num_gl_extensions) : p_(buffer), lang_(lang), stage_(stage) { @@ -21,23 +45,42 @@ public: // Assumes the input is zero-terminated. // C : Copies a buffer directly to the stream. template - void C(const char(&text)[T]) { + ShaderWriter &C(const char(&text)[T]) { memcpy(p_, text, T); p_ += T - 1; + return *this; } // W: Writes a zero-terminated string to the stream. - void W(const char *text) { + ShaderWriter &W(const char *text) { size_t len = strlen(text); memcpy(p_, text, len + 1); p_ += len; + return *this; } // F: Formats into the buffer. - void F(const char *format, ...); + ShaderWriter &F(const char *format, ...); - // void BeginMain(); - // void EndMain(); + // Several of the shader languages ignore samplers, beware of that. + void DeclareSampler2D(const char *name, int binding); + void DeclareTexture2D(const char *name, int binding); + ShaderWriter &SampleTexture2D(const char *texName, const char *samplerName, const char *uv); + + // Simple shaders with no special tricks. + void BeginVSMain(Slice inputs, Slice uniforms, Slice varyings); + void BeginFSMain(Slice uniforms, Slice varyings); + + // For simple shaders that output a single color, we can deal with this generically. + void EndVSMain(); + void EndFSMain(const char *vec4_color_variable); + + + void Rewind(size_t offset) { + p_ -= offset; + } + + // Can probably remove this char *GetPos() { return p_; } diff --git a/Core/Opcode.h b/Core/Opcode.h index 6eb01c41fc..456d77dbf6 100644 --- a/Core/Opcode.h +++ b/Core/Opcode.h @@ -17,7 +17,7 @@ #pragma once -#include "CommonTypes.h" +#include "Common/CommonTypes.h" // Broken out of MemMap.h to avoid a bad include dependency. diff --git a/GPU/Common/ColorReinterpret.cpp b/GPU/Common/ColorReinterpret.cpp deleted file mode 100644 index 3699d2f8aa..0000000000 --- a/GPU/Common/ColorReinterpret.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include - -#include "GPU/Common/ColorReinterpret.h" -#include "GPU/Common/ShaderWriter.h" - -// TODO: We could have an option to preserve any extra color precision. But gonna start without it. -// Requires full size integer math. -bool GenerateReinterpretFragmentShader(char *buffer, GEBufferFormat from, GEBufferFormat to, const ShaderLanguageDesc &lang) { - if (!lang.bitwiseOps) { - return false; - } - ShaderWriter writer(buffer, lang, ShaderStage::Fragment); - - switch (from) { - case GE_FORMAT_4444: - writer.W(" uint color = uint(in.r * 15.99) | (uint(in.g * 15.99) << 4) | (uint(in.b * 15.99) << 8) | (uint(in.a * 15.99) << 12);\n"); - break; - case GE_FORMAT_5551: - writer.W(" uint color = uint(in.r * 31.99) | (uint(in.g * 31.99) << 5) | (uint(in.b * 31.99) << 10);\n"); - writer.W(" if (in.a > 128.0) color |= 0x8000;\n"); - break; - case GE_FORMAT_565: - writer.W(" uint color = uint(in.r * 31.99) | (uint(in.g * 63.99) << 5) | (uint(in.b * 31.99) << 11);\n"); - break; - default: _assert_(false); - } - - switch (to) { - case GE_FORMAT_4444: - writer.W(" vec4 output = vec4(float(color & 0xF), float((color >> 4) & 0xF), float((color >> 8) & 0xF), float((color >> 12) & 0xF));\n"); - writer.W(" output *= 1.0 / 15.0;\n"); - break; - case GE_FORMAT_5551: - writer.W(" vec4 output = vec4(float(color & 0x1F), float((color >> 5) & 0x1F), float((color >> 10) & 0x1F), 0.0);\n"); - writer.W(" output.rgb *= 1.0 / 31.0;\n"); - writer.W(" output.a = float(color >> 15);\n"); - break; - case GE_FORMAT_565: - writer.W(" vec4 output = vec4(float(color & 0x1F), float((color >> 5) & 0x3F), float((color >> 11) & 0x1F), 1.0);\n"); - writer.W(" output.rb *= 1.0 / 31.0;\n"); - writer.W(" output.g *= 1.0 / 63.0;\n"); - break; - default: _assert_(false); - } - - writer.W("}"); - - return true; -} diff --git a/GPU/Common/ColorReinterpret.h b/GPU/Common/ColorReinterpret.h deleted file mode 100644 index c1ff33226a..0000000000 --- a/GPU/Common/ColorReinterpret.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include "Common/Log.h" -#include "GPU/ge_constants.h" -#include "GPU/GPUCommon.h" -#include "GPU/Common/ShaderWriter.h" diff --git a/GPU/Common/ReinterpretFramebuffer.cpp b/GPU/Common/ReinterpretFramebuffer.cpp new file mode 100644 index 0000000000..cf039c0aa6 --- /dev/null +++ b/GPU/Common/ReinterpretFramebuffer.cpp @@ -0,0 +1,64 @@ +#include + +#include "Common/GPU/ShaderWriter.h" +#include "GPU/Common/ReinterpretFramebuffer.h" + +// TODO: We could have an option to preserve any extra color precision. But gonna start without it. +// Requires full size integer math. +bool GenerateReinterpretFragmentShader(char *buffer, GEBufferFormat from, GEBufferFormat to, const ShaderLanguageDesc &lang) { + if (!lang.bitwiseOps) { + return false; + } + + ShaderWriter writer(buffer, lang, ShaderStage::Fragment, nullptr, 0); + + writer.DeclareSampler2D("samp", 0); + writer.DeclareTexture2D("tex", 0); + + static const VaryingDef varyings[1] = { + { "vec4", "v_texcoord", "TEXCOORD0" }, + }; + + writer.BeginFSMain(Slice::empty(), varyings); + + writer.C(" vec4 val = ").SampleTexture2D("tex", "samp", "v_texcoord.xy").C(";\n"); + + switch (from) { + case GE_FORMAT_4444: + writer.C(" uint color = uint(val.r * 15.99) | (uint(val.g * 15.99) << 4) | (uint(val.b * 15.99) << 8) | (uint(val.a * 15.99) << 12);\n"); + break; + case GE_FORMAT_5551: + writer.C(" uint color = uint(val.r * 31.99) | (uint(val.g * 31.99) << 5) | (uint(val.b * 31.99) << 10);\n"); + writer.C(" if (val.a > 128.0) color |= 0x8000U;\n"); + break; + case GE_FORMAT_565: + writer.C(" uint color = uint(val.r * 31.99) | (uint(val.g * 63.99) << 5) | (uint(val.b * 31.99) << 11);\n"); + break; + default: + _assert_(false); + break; + } + + switch (to) { + case GE_FORMAT_4444: + writer.C(" vec4 outColor = vec4(float(color & 0xFU), float((color >> 4) & 0xFU), float((color >> 8) & 0xFU), float((color >> 12) & 0xFU));\n"); + writer.C(" outColor *= 1.0 / 15.0;\n"); + break; + case GE_FORMAT_5551: + writer.C(" vec4 outColor = vec4(float(color & 0x1FU), float((color >> 5) & 0x1FU), float((color >> 10) & 0x1FU), 0.0);\n"); + writer.C(" outColor.rgb *= 1.0 / 31.0;\n"); + writer.C(" outColor.a = float(color >> 15);\n"); + break; + case GE_FORMAT_565: + writer.C(" vec4 outColor = vec4(float(color & 0x1FU), float((color >> 5) & 0x3FU), float((color >> 11) & 0x1FU), 1.0);\n"); + writer.C(" outColor.rb *= 1.0 / 31.0;\n"); + writer.C(" outColor.g *= 1.0 / 63.0;\n"); + break; + default: + _assert_(false); + break; + } + + writer.EndFSMain("outColor"); + return true; +} diff --git a/GPU/Common/ReinterpretFramebuffer.h b/GPU/Common/ReinterpretFramebuffer.h new file mode 100644 index 0000000000..f10fb9e3a3 --- /dev/null +++ b/GPU/Common/ReinterpretFramebuffer.h @@ -0,0 +1,8 @@ +#pragma once + +#include "Common/Log.h" +#include "GPU/ge_constants.h" +#include "GPU/GPUCommon.h" +#include "Common/GPU/ShaderWriter.h" + +bool GenerateReinterpretFragmentShader(char *buffer, GEBufferFormat from, GEBufferFormat to, const ShaderLanguageDesc &lang); diff --git a/GPU/GPU.vcxproj b/GPU/GPU.vcxproj index 675602d6d5..4f2550f635 100644 --- a/GPU/GPU.vcxproj +++ b/GPU/GPU.vcxproj @@ -338,6 +338,7 @@ + @@ -475,6 +476,7 @@ + diff --git a/GPU/GPU.vcxproj.filters b/GPU/GPU.vcxproj.filters index 509e962588..2c800c5e5c 100644 --- a/GPU/GPU.vcxproj.filters +++ b/GPU/GPU.vcxproj.filters @@ -270,6 +270,9 @@ Common + + Common + @@ -539,5 +542,8 @@ Common + + Common + - \ No newline at end of file + diff --git a/UWP/GPU_UWP/GPU_UWP.vcxproj b/UWP/GPU_UWP/GPU_UWP.vcxproj index d5b5d3167e..fcf535af3d 100644 --- a/UWP/GPU_UWP/GPU_UWP.vcxproj +++ b/UWP/GPU_UWP/GPU_UWP.vcxproj @@ -388,6 +388,7 @@ + @@ -445,6 +446,7 @@ + diff --git a/UWP/GPU_UWP/GPU_UWP.vcxproj.filters b/UWP/GPU_UWP/GPU_UWP.vcxproj.filters index 12f33ec3af..58617e8b91 100644 --- a/UWP/GPU_UWP/GPU_UWP.vcxproj.filters +++ b/UWP/GPU_UWP/GPU_UWP.vcxproj.filters @@ -57,6 +57,7 @@ + @@ -114,5 +115,6 @@ + \ No newline at end of file diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 845f3ca441..f0dde080ea 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -316,6 +316,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/GPU/Common/ShaderId.cpp.arm \ $(SRC)/GPU/Common/GPUStateUtils.cpp.arm \ $(SRC)/GPU/Common/SoftwareTransformCommon.cpp.arm \ + $(SRC)/GPU/Common/ReinterpretFramebuffer.cpp \ $(SRC)/GPU/Common/VertexDecoderCommon.cpp.arm \ $(SRC)/GPU/Common/TextureCacheCommon.cpp.arm \ $(SRC)/GPU/Common/TextureScalerCommon.cpp.arm \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 15620ed2b6..72c79b7129 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -234,6 +234,7 @@ SOURCES_CXX += \ $(GPUCOMMONDIR)/SplineCommon.cpp \ $(GPUCOMMONDIR)/FramebufferManagerCommon.cpp \ $(GPUCOMMONDIR)/PresentationCommon.cpp \ + $(GPUCOMMONDIR)/ReinterpretFramebuffer.cpp \ $(GPUCOMMONDIR)/ShaderId.cpp \ $(GPUCOMMONDIR)/ShaderCommon.cpp \ $(GPUCOMMONDIR)/ShaderUniforms.cpp \ diff --git a/unittest/TestShaderGenerators.cpp b/unittest/TestShaderGenerators.cpp index 2f67729573..35dcde1104 100644 --- a/unittest/TestShaderGenerators.cpp +++ b/unittest/TestShaderGenerators.cpp @@ -11,6 +11,7 @@ #include "GPU/Common/FragmentShaderGenerator.h" #include "GPU/Common/VertexShaderGenerator.h" +#include "GPU/Common/ReinterpretFramebuffer.h" #include "GPU/D3D11/D3D11Util.h" #include "GPU/D3D11/D3D11Loader.h" @@ -141,12 +142,72 @@ void PrintDiff(const char *a, const char *b) { } } +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() { + ShaderLanguage languages[] = { + ShaderLanguage::HLSL_D3D11, + 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]); + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (i == j) + continue; // useless shader! + if (!GenerateReinterpretFragmentShader(buffer, fmts[i], fmts[j], desc)) { + printf("Failed!\n%s\n", buffer); + failed = true; + } else { + std::string errorMessage; + if (!TestCompileShader(buffer, languages[k], false, &errorMessage)) { + printf("Error compiling fragment shader %d:\n\n%s\n\n%s\n", (int)j, LineNumberString(buffer).c_str(), errorMessage.c_str()); + failed = true; + return false; + } else { + printf("===\n%s\n===\n", buffer); + } + } + } + } + + } + return !failed; +} + bool TestShaderGenerators() { LoadD3D11(); init_glslang(); LoadD3DCompilerDynamic(); + if (!TestReinterpretShaders()) { + return false; + } + ShaderLanguage languages[] = { ShaderLanguage::HLSL_D3D9, ShaderLanguage::HLSL_D3D11,