diff --git a/main.cpp b/main.cpp index f5fa091e..adc29acb 100644 --- a/main.cpp +++ b/main.cpp @@ -446,6 +446,7 @@ struct CLIArguments bool hlsl = false; bool hlsl_compat = false; bool vulkan_semantics = false; + bool flatten_multidimensional_arrays = false; bool remove_unused = false; bool cfg_analysis = true; }; @@ -461,6 +462,7 @@ static void print_help() "[--separate-shader-objects]" "[--pls-in format input-name] [--pls-out format output-name] [--remap source_name target_name " "components] [--extension ext] [--entry name] [--remove-unused-variables] " + "[--flatten-multidimensional-arrays] " "[--remap-variable-type ]\n"); } @@ -583,6 +585,7 @@ int main(int argc, char *argv[]) cbs.add("--hlsl", [&args](CLIParser &) { args.hlsl = true; }); cbs.add("--hlsl-enable-compat", [&args](CLIParser &) { args.hlsl_compat = true; }); cbs.add("--vulkan-semantics", [&args](CLIParser &) { args.vulkan_semantics = true; }); + cbs.add("--flatten-multidimensional-arrays", [&args](CLIParser &) { args.flatten_multidimensional_arrays = true; }); cbs.add("--extension", [&args](CLIParser &parser) { args.extensions.push_back(parser.next_string()); }); cbs.add("--entry", [&args](CLIParser &parser) { args.entry = parser.next_string(); }); cbs.add("--separate-shader-objects", [&args](CLIParser &) { args.sso = true; }); @@ -690,6 +693,7 @@ int main(int argc, char *argv[]) opts.es = args.es; opts.force_temporary = args.force_temporary; opts.separate_shader_objects = args.sso; + opts.flatten_multidimensional_arrays = args.flatten_multidimensional_arrays; opts.vulkan_semantics = args.vulkan_semantics; opts.vertex.fixup_clipspace = args.fixup; opts.cfg_analysis = args.cfg_analysis; diff --git a/reference/shaders/flatten/multi-dimensional.desktop.flatten_dim.frag b/reference/shaders/flatten/multi-dimensional.desktop.flatten_dim.frag new file mode 100644 index 00000000..21c3363c --- /dev/null +++ b/reference/shaders/flatten/multi-dimensional.desktop.flatten_dim.frag @@ -0,0 +1,24 @@ +#version 450 + +layout(binding = 0) uniform sampler2D uTextures[2 * 3 * 1]; + +layout(location = 1) in vec2 vUV; +layout(location = 0) out vec4 FragColor; +layout(location = 0) flat in int vIndex; + +void main() +{ + vec4 values3[2 * 3 * 1]; + for (int z = 0; z < 2; z++) + { + for (int y = 0; y < 3; y++) + { + for (int x = 0; x < 1; x++) + { + values3[z * 3 * 1 + y * 1 + x] = texture(uTextures[z * 3 * 1 + y * 1 + x], vUV); + } + } + } + FragColor = ((values3[1 * 3 * 1 + 2 * 1 + 0]) + (values3[0 * 3 * 1 + 2 * 1 + 0])) + (values3[(vIndex + 1) * 3 * 1 + 2 * 1 + vIndex]); +} + diff --git a/shaders/flatten/multi-dimensional.desktop.flatten_dim.frag b/shaders/flatten/multi-dimensional.desktop.flatten_dim.frag new file mode 100644 index 00000000..24b2ff1d --- /dev/null +++ b/shaders/flatten/multi-dimensional.desktop.flatten_dim.frag @@ -0,0 +1,18 @@ +#version 450 + +layout(location = 0) out vec4 FragColor; +layout(binding = 0) uniform sampler2D uTextures[2][3][1]; +layout(location = 0) flat in int vIndex; +layout(location = 1) in vec2 vUV; + +void main() +{ + vec4 values3[2][3][1]; + + for (int z = 0; z < 2; z++) + for (int y = 0; y < 3; y++) + for (int x = 0; x < 1; x++) + values3[z][y][x] = texture(uTextures[z][y][x], vUV); + + FragColor = values3[1][2][0] + values3[0][2][0] + values3[vIndex + 1][2][vIndex]; +} diff --git a/spirv_glsl.cpp b/spirv_glsl.cpp index f3f04ff4..43649e8f 100644 --- a/spirv_glsl.cpp +++ b/spirv_glsl.cpp @@ -1816,10 +1816,8 @@ void CompilerGLSL::strip_enclosed_expression(string &expr) expr.erase(begin(expr)); } -// Just like to_expression except that we enclose the expression inside parentheses if needed. -string CompilerGLSL::to_enclosed_expression(uint32_t id) +string CompilerGLSL::enclose_expression(const string &expr) { - auto expr = to_expression(id); bool need_parens = false; uint32_t paren_count = 0; for (auto c : expr) @@ -1848,6 +1846,12 @@ string CompilerGLSL::to_enclosed_expression(uint32_t id) return expr; } +// Just like to_expression except that we enclose the expression inside parentheses if needed. +string CompilerGLSL::to_enclosed_expression(uint32_t id) +{ + return enclose_expression(to_expression(id)); +} + string CompilerGLSL::to_expression(uint32_t id) { auto itr = invalid_expressions.find(id); @@ -3525,6 +3529,8 @@ string CompilerGLSL::access_chain_internal(uint32_t base, const uint32_t *indice bool access_chain_is_arrayed = false; bool row_major_matrix_needs_conversion = is_non_native_row_major_matrix(base); + bool pending_array_enclose = false; + bool dimension_flatten = false; for (uint32_t i = 0; i < count; i++) { @@ -3533,14 +3539,50 @@ string CompilerGLSL::access_chain_internal(uint32_t base, const uint32_t *indice // Arrays if (!type->array.empty()) { - expr += "["; - if (index_is_literal) - expr += convert_to_string(index); - else - expr += to_expression(index); - expr += "]"; + // If we are flattening multidimensional arrays, only create opening bracket on first + // array index. + if (options.flatten_multidimensional_arrays && !pending_array_enclose) + { + dimension_flatten = type->array.size() > 1; + pending_array_enclose = dimension_flatten; + if (pending_array_enclose) + expr += "["; + } assert(type->parent_type); + // If we are flattening multidimensional arrays, do manual stride computation. + if (options.flatten_multidimensional_arrays && dimension_flatten) + { + auto &parent_type = get(type->parent_type); + + if (index_is_literal) + expr += convert_to_string(index); + else + expr += to_enclosed_expression(index); + + for (auto j = uint32_t(parent_type.array.size()); j; j--) + { + expr += " * "; + expr += enclose_expression(to_array_size(parent_type, j - 1)); + } + + if (parent_type.array.empty()) + pending_array_enclose = false; + else + expr += " + "; + } + else + { + expr += "["; + if (index_is_literal) + expr += convert_to_string(index); + else + expr += to_expression(index); + } + + if (!pending_array_enclose) + expr += "]"; + type = &get(type->parent_type); access_chain_is_arrayed = true; @@ -3636,6 +3678,13 @@ string CompilerGLSL::access_chain_internal(uint32_t base, const uint32_t *indice SPIRV_CROSS_THROW("Cannot subdivide a scalar value!"); } + if (pending_array_enclose) + { + SPIRV_CROSS_THROW("Flattening of multidimensional arrays were enabled, " + "but the access chain was terminated in the middle of a multidimensional array. " + "This is not supported."); + } + if (need_transpose) *need_transpose = row_major_matrix_needs_conversion; return expr; @@ -6022,14 +6071,40 @@ string CompilerGLSL::type_to_array_glsl(const SPIRType &type) if (type.array.empty()) return ""; - string res; - for (auto i = uint32_t(type.array.size()); i; i--) + if (options.flatten_multidimensional_arrays) { + string res; res += "["; - res += to_array_size(type, i - 1); + for (auto i = uint32_t(type.array.size()); i; i--) + { + res += enclose_expression(to_array_size(type, i - 1)); + if (i > 1) + res += " * "; + } res += "]"; + return res; + } + else + { + if (type.array.size() > 1) + { + if (!options.es && options.version < 430) + require_extension("GL_ARB_arrays_of_arrays"); + else if (options.es && options.version < 310) + SPIRV_CROSS_THROW("Arrays of arrays not supported before ESSL version 310. " + "Try using --flatten-multidimensional-arrays or set " + "options.flatten_multidimensional_arrays to true."); + } + + string res; + for (auto i = uint32_t(type.array.size()); i; i--) + { + res += "["; + res += to_array_size(type, i - 1); + res += "]"; + } + return res; } - return res; } string CompilerGLSL::image_type_glsl(const SPIRType &type) @@ -6111,6 +6186,16 @@ string CompilerGLSL::image_type_glsl(const SPIRType &type) string CompilerGLSL::type_to_glsl_constructor(const SPIRType &type) { + if (type.array.size() > 1) + { + if (options.flatten_multidimensional_arrays) + SPIRV_CROSS_THROW("Cannot flatten constructors of multidimensional array constructors, e.g. float[][]()."); + else if (!options.es && options.version < 430) + require_extension("GL_ARB_arrays_of_arrays"); + else if (options.es && options.version < 310) + SPIRV_CROSS_THROW("Arrays of arrays not supported before ESSL version 310."); + } + auto e = type_to_glsl(type); for (uint32_t i = 0; i < type.array.size(); i++) e += "[]"; diff --git a/spirv_glsl.hpp b/spirv_glsl.hpp index 6d06518b..49163a66 100644 --- a/spirv_glsl.hpp +++ b/spirv_glsl.hpp @@ -73,6 +73,12 @@ public: // (EXT_shader_io_blocks) which makes things a bit more fuzzy. bool separate_shader_objects = false; + // Flattens multidimensional arrays, e.g. float foo[a][b][c] into single-dimensional arrays, + // e.g. float foo[a * b * c]. + // This function does not change the actual SPIRType of any object. + // Only the generated code, including declarations of interface variables are changed to be single array dimension. + bool flatten_multidimensional_arrays = false; + enum Precision { DontCare, @@ -359,6 +365,7 @@ protected: void append_global_func_args(const SPIRFunction &func, uint32_t index, std::vector &arglist); std::string to_expression(uint32_t id); std::string to_enclosed_expression(uint32_t id); + std::string enclose_expression(const std::string &expr); void strip_enclosed_expression(std::string &expr); std::string to_member_name(const SPIRType &type, uint32_t index); std::string type_to_glsl_constructor(const SPIRType &type); diff --git a/test_shaders.py b/test_shaders.py index 808f9d2d..ff89de6e 100755 --- a/test_shaders.py +++ b/test_shaders.py @@ -121,7 +121,7 @@ def validate_shader(shader, vulkan): else: subprocess.check_call(['glslangValidator', shader]) -def cross_compile(shader, vulkan, spirv, invalid_spirv, eliminate, is_legacy, flatten_ubo, sso): +def cross_compile(shader, vulkan, spirv, invalid_spirv, eliminate, is_legacy, flatten_ubo, sso, flatten_dim): spirv_f, spirv_path = tempfile.mkstemp() glsl_f, glsl_path = tempfile.mkstemp(suffix = os.path.basename(shader)) os.close(spirv_f) @@ -148,6 +148,8 @@ def cross_compile(shader, vulkan, spirv, invalid_spirv, eliminate, is_legacy, fl extra_args += ['--flatten-ubo'] if sso: extra_args += ['--separate-shader-objects'] + if flatten_dim: + extra_args += ['--flatten-multidimensional-arrays'] spirv_cross_path = './spirv-cross' subprocess.check_call([spirv_cross_path, '--entry', 'main', '--output', glsl_path, spirv_path] + extra_args) @@ -244,6 +246,9 @@ def shader_is_flatten_ubo(shader): def shader_is_sso(shader): return '.sso.' in shader +def shader_is_flatten_dimensions(shader): + return '.flatten_dim.' in shader + def test_shader(stats, shader, update, keep): joined_path = os.path.join(shader[0], shader[1]) vulkan = shader_is_vulkan(shader[1]) @@ -254,9 +259,10 @@ def test_shader(stats, shader, update, keep): is_legacy = shader_is_legacy(shader[1]) flatten_ubo = shader_is_flatten_ubo(shader[1]) sso = shader_is_sso(shader[1]) + flatten_dim = shader_is_flatten_dimensions(shader[1]) print('Testing shader:', joined_path) - spirv, glsl, vulkan_glsl = cross_compile(joined_path, vulkan, is_spirv, invalid_spirv, eliminate, is_legacy, flatten_ubo, sso) + spirv, glsl, vulkan_glsl = cross_compile(joined_path, vulkan, is_spirv, invalid_spirv, eliminate, is_legacy, flatten_ubo, sso, flatten_dim) # Only test GLSL stats if we have a shader following GL semantics. if stats and (not vulkan) and (not is_spirv) and (not desktop):