Return arrays in HLSL/MSL by writing to an output variable instead.

This commit is contained in:
Hans-Kristian Arntzen 2018-02-05 12:37:41 +01:00
parent 9fa91f7e1c
commit 00ccd590ee
14 changed files with 300 additions and 28 deletions

View File

@ -0,0 +1,31 @@
static const float4 _20[2] = { 10.0f.xxxx, 20.0f.xxxx };
static float4 gl_Position;
static float4 vInput0;
static float4 vInput1;
struct SPIRV_Cross_Input
{
float4 vInput0 : TEXCOORD0;
float4 vInput1 : TEXCOORD1;
};
struct SPIRV_Cross_Output
{
float4 gl_Position : SV_Position;
};
void vert_main()
{
gl_Position = 10.0f.xxxx + vInput1;
}
SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
{
vInput0 = stage_input.vInput0;
vInput1 = stage_input.vInput1;
vert_main();
SPIRV_Cross_Output stage_output;
stage_output.gl_Position = gl_Position;
return stage_output;
}

View File

@ -3,15 +3,20 @@
using namespace metal;
struct main0_in
{
float4 vInput1 [[attribute(1)]];
};
struct main0_out
{
float4 gl_Position [[position]];
};
vertex main0_out main0()
vertex main0_out main0(main0_in in [[stage_in]])
{
main0_out out = {};
out.gl_Position = float4(10.0);
out.gl_Position = float4(10.0) + in.vInput1;
return out;
}

View File

@ -1,7 +1,9 @@
#version 310 es
layout(location = 1) in vec4 vInput1;
void main()
{
gl_Position = vec4(10.0);
gl_Position = vec4(10.0) + vInput1;
}

View File

@ -0,0 +1,48 @@
static const float4 _20[2] = { 10.0f.xxxx, 20.0f.xxxx };
static float4 gl_Position;
static float4 vInput0;
static float4 vInput1;
struct SPIRV_Cross_Input
{
float4 vInput0 : TEXCOORD0;
float4 vInput1 : TEXCOORD1;
};
struct SPIRV_Cross_Output
{
float4 gl_Position : SV_Position;
};
void test(out float4 SPIRV_Cross_return_value[2])
{
SPIRV_Cross_return_value = _20;
}
void test2(out float4 SPIRV_Cross_return_value[2])
{
float4 foobar[2];
foobar[0] = vInput0;
foobar[1] = vInput1;
SPIRV_Cross_return_value = foobar;
}
void vert_main()
{
float4 _42[2];
test(_42);
float4 _44[2];
test2(_44);
gl_Position = _42[0] + _44[1];
}
SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
{
vInput0 = stage_input.vInput0;
vInput1 = stage_input.vInput1;
vert_main();
SPIRV_Cross_Output stage_output;
stage_output.gl_Position = gl_Position;
return stage_output;
}

View File

@ -5,20 +5,45 @@
using namespace metal;
struct main0_in
{
float4 vInput1 [[attribute(1)]];
float4 vInput0 [[attribute(0)]];
};
struct main0_out
{
float4 gl_Position [[position]];
};
float4[2] test()
// Implementation of an array copy function to cover GLSL's ability to copy an array via assignment.
template<typename T, uint N>
void spvArrayCopy(thread T (&dst)[N], thread const T (&src)[N])
{
return {float4(10.0), float4(20.0)};
for (uint i = 0; i < N; dst[i] = src[i], i++);
}
vertex main0_out main0()
void (float4 (&SPIRV_Cross_return_value)[2])
{
SPIRV_Cross_return_value = {float4(10.0), float4(20.0)};
}
void (float4 (&SPIRV_Cross_return_value)[2], thread float4& vInput0, thread float4& vInput1)
{
float4 foobar[2];
foobar[0] = vInput0;
foobar[1] = vInput1;
spvArrayCopy(SPIRV_Cross_return_value, foobar);
}
vertex main0_out main0(main0_in in [[stage_in]])
{
main0_out out = {};
out.gl_Position = test()[0];
float4 _42[2];
test(_42);
float4 _44[2];
test2(_44, in.vInput0, in.vInput1);
out.gl_Position = _42[0] + _44[1];
return out;
}

View File

@ -1,12 +1,23 @@
#version 310 es
layout(location = 0) in vec4 vInput0;
layout(location = 1) in vec4 vInput1;
vec4[2] test()
{
return vec4[](vec4(10.0), vec4(20.0));
}
void main()
vec4[2] test2()
{
gl_Position = test()[0];
vec4 foobar[2];
foobar[0] = vInput0;
foobar[1] = vInput1;
return foobar;
}
void main()
{
gl_Position = test()[0] + test2()[1];
}

View File

@ -0,0 +1,22 @@
#version 310 es
layout(location = 0) in vec4 vInput0;
layout(location = 1) in vec4 vInput1;
vec4[2] test()
{
return vec4[](vec4(10.0), vec4(20.0));
}
vec4[2] test2()
{
vec4 foobar[2];
foobar[0] = vInput0;
foobar[1] = vInput1;
return foobar;
}
void main()
{
gl_Position = test()[0] + test2()[1];
}

View File

@ -1,11 +1,22 @@
#version 310 es
layout(location = 0) in vec4 vInput0;
layout(location = 1) in vec4 vInput1;
vec4[2] test()
{
return vec4[](vec4(10.0), vec4(20.0));
}
vec4[2] test2()
{
vec4 foobar[2];
foobar[0] = vInput0;
foobar[1] = vInput1;
return foobar;
}
void main()
{
gl_Position = test()[0];
gl_Position = test()[0] + test2()[1];
}

View File

@ -1,11 +1,22 @@
#version 310 es
layout(location = 0) in vec4 vInput0;
layout(location = 1) in vec4 vInput1;
vec4[2] test()
{
return vec4[](vec4(10.0), vec4(20.0));
}
vec4[2] test2()
{
vec4 foobar[2];
foobar[0] = vInput0;
foobar[1] = vInput1;
return foobar;
}
void main()
{
gl_Position = test()[0];
gl_Position = test()[0] + test2()[1];
}

View File

@ -5367,9 +5367,11 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction)
length -= 3;
auto &callee = get<SPIRFunction>(func);
auto &return_type = get<SPIRType>(callee.return_type);
bool pure = function_is_pure(callee);
bool callee_has_out_variables = false;
bool emit_return_value_as_argument = false;
// Invalidate out variables passed to functions since they can be OpStore'd to.
for (uint32_t i = 0; i < length; i++)
@ -5383,12 +5385,25 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction)
flush_variable_declaration(arg[i]);
}
if (!return_type.array.empty() && !backend.can_return_array)
{
callee_has_out_variables = true;
emit_return_value_as_argument = true;
}
if (!pure)
register_impure_function_call();
string funexpr;
vector<string> arglist;
funexpr += to_name(func) + "(";
if (emit_return_value_as_argument)
{
statement(type_to_glsl(return_type), " ", to_name(id), type_to_array_glsl(return_type), ";");
arglist.push_back(to_name(id));
}
for (uint32_t i = 0; i < length; i++)
{
// Do not pass in separate images or samplers if we're remapping
@ -5423,7 +5438,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction)
// Check for function call constraints.
check_function_call_constraints(arg, length);
if (get<SPIRType>(result_type).basetype != SPIRType::Void)
if (return_type.basetype != SPIRType::Void)
{
// If the function actually writes to an out variable,
// take the conservative route and do not forward.
@ -5435,6 +5450,12 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction)
bool forward = args_will_forward(id, arg, length, pure) && !callee_has_out_variables && pure &&
(forced_temporaries.find(id) == end(forced_temporaries));
if (emit_return_value_as_argument)
{
statement(funexpr, ";");
set<SPIRExpression>(id, to_name(id), result_type, true);
}
else
emit_op(result_type, id, funexpr, forward);
// Function calls are implicit loads from all variables in question.
@ -8453,18 +8474,37 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block)
emit_fixup();
if (block.return_value)
{
auto &type = expression_type(block.return_value);
if (!type.array.empty() && !backend.can_return_array)
{
// If we cannot return arrays, we will have a special out argument we can write to instead.
// The backend is responsible for setting this up, and redirection the return values as appropriate.
if (ids.at(block.return_value).get_type() != TypeUndef)
emit_array_copy("SPIRV_Cross_return_value", block.return_value);
if (!block_is_outside_flow_control_from_block(get<SPIRBlock>(current_function->entry_block), block) ||
block.loop_dominator != SPIRBlock::NoDominator)
{
statement("return;");
}
}
else
{
// OpReturnValue can return Undef, so don't emit anything for this case.
if (ids.at(block.return_value).get_type() != TypeUndef)
statement("return ", to_expression(block.return_value), ";");
}
}
// If this block is the very final block and not called from control flow,
// we do not need an explicit return which looks out of place. Just end the function here.
// In the very weird case of for(;;) { return; } executing return is unconditional,
// but we actually need a return here ...
else if (!block_is_outside_flow_control_from_block(get<SPIRBlock>(current_function->entry_block), block) ||
block.loop_dominator != SPIRBlock::NoDominator)
{
statement("return;");
}
break;
case SPIRBlock::Kill:
@ -8582,3 +8622,8 @@ uint32_t CompilerGLSL::mask_relevant_memory_semantics(uint32_t semantics)
MemorySemanticsWorkgroupMemoryMask | MemorySemanticsUniformMemoryMask |
MemorySemanticsCrossWorkgroupMemoryMask | MemorySemanticsSubgroupMemoryMask);
}
void CompilerGLSL::emit_array_copy(const string &lhs, uint32_t rhs_id)
{
statement(lhs, " = ", to_expression(rhs_id), ";");
}

View File

@ -331,6 +331,7 @@ protected:
bool allow_precision_qualifiers = false;
bool can_swizzle_scalar = false;
bool force_gl_in_out_block = false;
bool can_return_array = true;
} backend;
void emit_struct(SPIRType &type);
@ -424,6 +425,7 @@ protected:
std::string layout_for_variable(const SPIRVariable &variable);
std::string to_combined_image_sampler(uint32_t image_id, uint32_t samp_id);
virtual bool skip_argument(uint32_t id) const;
virtual void emit_array_copy(const std::string &lhs, uint32_t rhs_id);
bool buffer_is_packing_standard(const SPIRType &type, BufferPackingStandard packing, uint32_t start_offset = 0,
uint32_t end_offset = std::numeric_limits<uint32_t>::max());

View File

@ -1663,11 +1663,17 @@ void CompilerHLSL::emit_function_prototype(SPIRFunction &func, uint64_t return_f
string decl;
auto &type = get<SPIRType>(func.return_type);
if (type.array.empty())
{
decl += flags_to_precision_qualifiers_glsl(type, return_flags);
decl += type_to_glsl(type);
if (!type.array.empty())
SPIRV_CROSS_THROW("Returning arrays from functions not possible in HLSL.");
decl += " ";
}
else
{
// We cannot return arrays in HLSL, so "return" through an out variable.
decl = "void ";
}
if (func.self == entry_point)
{
@ -1685,6 +1691,19 @@ void CompilerHLSL::emit_function_prototype(SPIRFunction &func, uint64_t return_f
decl += to_name(func.self);
decl += "(";
if (!type.array.empty())
{
// Fake array returns by writing to an out array instead.
decl += "out ";
decl += type_to_glsl(type);
decl += " ";
decl += "SPIRV_Cross_return_value";
decl += type_to_array_glsl(type);
if (!func.arguments.empty())
decl += ", ";
}
for (auto &arg : func.arguments)
{
// Might change the variable name if it already exists in this function.
@ -3804,6 +3823,7 @@ string CompilerHLSL::compile()
backend.can_swizzle_scalar = true;
backend.can_declare_struct_inline = false;
backend.can_declare_arrays_inline = false;
backend.can_return_array = false;
update_active_builtins();
analyze_sampler_comparison_states();

View File

@ -72,6 +72,7 @@ string CompilerMSL::compile()
backend.use_typed_initializer_list = true;
backend.native_row_major_matrix = false;
backend.flexible_member_array_supported = false;
backend.can_return_array = false;
replace_illegal_names();
@ -913,10 +914,10 @@ void CompilerMSL::emit_custom_functions()
case SPVFuncImplArrayCopy:
statement("// Implementation of an array copy function to cover GLSL's ability to copy an array via "
"assignment. ");
statement("template<typename T>");
statement("void spvArrayCopy(thread T* dst, thread const T* src, uint count)");
statement("template<typename T, uint N>");
statement("void spvArrayCopy(thread T (&dst)[N], thread const T (&src)[N])");
begin_scope();
statement("for (uint i = 0; i < count; *dst++ = *src++, i++);");
statement("for (uint i = 0; i < N; dst[i] = src[i], i++);");
end_scope();
statement("");
break;
@ -1743,6 +1744,15 @@ bool CompilerMSL::maybe_emit_input_struct_assignment(uint32_t id_lhs, uint32_t i
return true;
}
void CompilerMSL::emit_array_copy(const string &lhs, uint32_t rhs_id)
{
// Assignment from an array initializer is fine.
if (ids[rhs_id].get_type() != TypeConstant)
statement("spvArrayCopy(", lhs, ", ", to_expression(rhs_id), ");");
else
statement(lhs, " = ", to_expression(rhs_id), ";");
}
// Since MSL does not allow arrays to be copied via simple variable assignment,
// if the LHS and RHS represent an assignment of an entire array, it must be
// implemented by calling an array copy function.
@ -1763,7 +1773,7 @@ bool CompilerMSL::maybe_emit_array_assignment(uint32_t id_lhs, uint32_t id_rhs)
if (p_v_lhs)
flush_variable_declaration(p_v_lhs->self);
statement("spvArrayCopy(", to_expression(id_lhs), ", ", to_expression(id_rhs), ", ", to_array_size(type, 0), ");");
emit_array_copy(to_expression(id_lhs), id_rhs);
register_write(id_lhs);
return true;
@ -1949,12 +1959,31 @@ void CompilerMSL::emit_function_prototype(SPIRFunction &func, uint64_t)
processing_entry_point = (func.self == entry_point);
auto &type = get<SPIRType>(func.return_type);
if (type.array.empty())
{
decl += func_type_decl(type);
decl += " ";
decl += to_name(func.self);
}
else
{
// We cannot return arrays in MSL, so "return" through an out variable.
decl = "void ";
}
decl += "(";
if (!type.array.empty())
{
// Fake arrays returns by writing to an out array instead.
decl += type_to_glsl(type);
decl += " (&SPIRV_Cross_return_value)";
decl += type_to_array_glsl(type);
if (!func.arguments.empty())
decl += ", ";
}
if (processing_entry_point)
{
decl += entry_point_args(!func.arguments.empty());
@ -3522,6 +3551,15 @@ CompilerMSL::SPVFuncImpl CompilerMSL::OpCodePreprocessor::get_spv_func_impl(Op o
case OpFMod:
return SPVFuncImplMod;
case OpFunctionCall:
{
auto &return_type = compiler.get<SPIRType>(args[0]);
if (!return_type.array.empty())
return SPVFuncImplArrayCopy;
else
return SPVFuncImplNone;
}
case OpStore:
{
// Get the result type of the RHS. Since this is run as a pre-processing stage,

View File

@ -265,6 +265,7 @@ protected:
const char *get_memory_order(uint32_t spv_mem_sem);
void add_pragma_line(const std::string &line);
void emit_barrier(uint32_t id_exe_scope, uint32_t id_mem_scope, uint32_t id_mem_sem);
void emit_array_copy(const std::string &lhs, uint32_t rhs_id) override;
Options options;
std::set<SPVFuncImpl> spv_function_implementations;