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,