// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. // . #include "MetalShaderFormat.h" #include "Core.h" #include "hlslcc.h" #include "hlslcc_private.h" #include "MetalBackend.h" #include "compiler.h" PRAGMA_DISABLE_SHADOW_VARIABLE_WARNINGS #include "glsl_parser_extras.h" PRAGMA_POP #include "hash_table.h" #include "ir_rvalue_visitor.h" #include "PackUniformBuffers.h" #include "IRDump.h" #include "OptValueNumbering.h" #include "ir_optimization.h" #include "MetalUtils.h" #if !PLATFORM_WINDOWS #define _strdup strdup #endif #define GROUP_MEMORY_BARRIER "GroupMemoryBarrier" #define GROUP_MEMORY_BARRIER_WITH_GROUP_SYNC "GroupMemoryBarrierWithGroupSync" #define DEVICE_MEMORY_BARRIER "DeviceMemoryBarrier" #define DEVICE_MEMORY_BARRIER_WITH_GROUP_SYNC "DeviceMemoryBarrierWithGroupSync" #define ALL_MEMORY_BARRIER "AllMemoryBarrier" #define ALL_MEMORY_BARRIER_WITH_GROUP_SYNC "AllMemoryBarrierWithGroupSync" /** * This table must match the ir_expression_operation enum. */ static const char * const MetalExpressionTable[ir_opcode_count][4] = { { "(~", ")", "", "" }, // ir_unop_bit_not, { "not(", ")", "", "!" }, // ir_unop_logic_not, { "(-", ")", "", "" }, // ir_unop_neg, { "fabs(", ")", "", "" }, // ir_unop_abs, { "sign(", ")", "", "" }, // ir_unop_sign, { "(1.0/(", "))", "", "" }, // ir_unop_rcp, { "rsqrt(", ")", "", "" }, // ir_unop_rsq, { "sqrt(", ")", "", "" }, // ir_unop_sqrt, { "exp(", ")", "", "" }, // ir_unop_exp, /**< Log base e on gentype */ { "log(", ")", "", "" }, // ir_unop_log, /**< Natural log on gentype */ { "exp2(", ")", "", "" }, // ir_unop_exp2, { "log2(", ")", "", "" }, // ir_unop_log2, { "int(", ")", "", "" }, // ir_unop_f2i, /**< Float-to-integer conversion. */ { "float(", ")", "", "" }, // ir_unop_i2f, /**< Integer-to-float conversion. */ { "bool(", ")", "", "" }, // ir_unop_f2b, /**< Float-to-boolean conversion */ { "float(", ")", "", "" }, // ir_unop_b2f, /**< Boolean-to-float conversion */ { "bool(", ")", "", "" }, // ir_unop_i2b, /**< int-to-boolean conversion */ { "int(", ")", "", "" }, // ir_unop_b2i, /**< Boolean-to-int conversion */ { "uint(", ")", "", "" }, // ir_unop_b2u, { "bool(", ")", "", "" }, // ir_unop_u2b, { "uint(", ")", "", "" }, // ir_unop_f2u, { "float(", ")", "", "" }, // ir_unop_u2f, /**< Unsigned-to-float conversion. */ { "uint(", ")", "", "" }, // ir_unop_i2u, /**< Integer-to-unsigned conversion. */ { "int(", ")", "", "" }, // ir_unop_u2i, /**< Unsigned-to-integer conversion. */ { "int(", ")", "", "" }, // ir_unop_h2i, { "half(", ")", "", "" }, // ir_unop_i2h, { "float(", ")", "", "" }, // ir_unop_h2f, { "half(", ")", "", "" }, // ir_unop_f2h, { "bool(", ")", "", "" }, // ir_unop_h2b, { "float(", ")", "", "" }, // ir_unop_b2h, { "uint(", ")", "", "" }, // ir_unop_h2u, { "uint(", ")", "", "" }, // ir_unop_u2h, { "transpose(", ")", "", "" }, // ir_unop_transpose { "any(", ")", "", "" }, // ir_unop_any, { "all(", ")", "", "" }, // ir_unop_all, /** * \name Unary floating-point rounding operations. */ /*@{*/ { "trunc(", ")", "", "" }, // ir_unop_trunc, { "ceil(", ")", "", "" }, // ir_unop_ceil, { "floor(", ")", "", "" }, // ir_unop_floor, { "fract(", ")", "", "" }, // ir_unop_fract, { "round(", ")", "", "" }, // ir_unop_round, /*@}*/ /** * \name Trigonometric operations. */ /*@{*/ { "sin(", ")", "", "" }, // ir_unop_sin, { "cos(", ")", "", "" }, // ir_unop_cos, { "tan(", ")", "", "" }, // ir_unop_tan, { "asin(", ")", "", "" }, // ir_unop_asin, { "acos(", ")", "", "" }, // ir_unop_acos, { "atan(", ")", "", "" }, // ir_unop_atan, { "sinh(", ")", "", "" }, // ir_unop_sinh, { "cosh(", ")", "", "" }, // ir_unop_cosh, { "tanh(", ")", "", "" }, // ir_unop_tanh, /*@}*/ /** * \name Normalize. */ /*@{*/ { "normalize(", ")", "", "" }, // ir_unop_normalize, /*@}*/ /** * \name Partial derivatives. */ /*@{*/ { "dfdx(", ")", "", "" }, // ir_unop_dFdx, { "dfdy(", ")", "", "" }, // ir_unop_dFdy, /*@}*/ { "isnan(", ")", "", "" }, // ir_unop_isnan, { "isinf(", ")", "", "" }, // ir_unop_isinf, { "floatBitsToUint(", ")", "", "" }, // ir_unop_fasu, { "floatBitsToInt(", ")", "", "" }, // ir_unop_fasi, { "intBitsToFloat(", ")", "", "" }, // ir_unop_iasf, { "uintBitsToFloat(", ")", "", "" }, // ir_unop_uasf, { "bitfieldReverse(", ")", "", "" }, // ir_unop_bitreverse, { "bitCount(", ")", "", "" }, // ir_unop_bitcount, { "findMSB(", ")", "", "" }, // ir_unop_msb, { "findLSB(", ")", "", "" }, // ir_unop_lsb, { "ERROR_NO_NOISE_FUNCS(", ")", "", "" }, // ir_unop_noise, { "(", "+", ")", "" }, // ir_binop_add, { "(", "-", ")", "" }, // ir_binop_sub, { "(", "*", ")", "" }, // ir_binop_mul, { "(", "/", ")", "" }, // ir_binop_div, /** * Takes one of two combinations of arguments: * * - mod(vecN, vecN) * - mod(vecN, float) * * Does not take integer types. */ { "fmod(", ",", ")", "%" }, // ir_binop_mod, { "modf(", ",", ")", "" }, // ir_binop_modf, { "step(", ",", ")", "" }, // ir_binop_step, /** * \name Binary comparison operators which return a boolean vector. * The type of both operands must be equal. */ /*@{*/ { "(", "<", ")", "<" }, // ir_binop_less, { "(", ">", ")", ">" }, // ir_binop_greater, { "(", "<=", ")", "<=" }, // ir_binop_lequal, { "(", ">=", ")", ">=" }, // ir_binop_gequal, { "(", "==", ")", "==" }, // ir_binop_equal, { "(", "!=", ")", "!=" }, // ir_binop_nequal, /** * Returns single boolean for whether all components of operands[0] * equal the components of operands[1]. */ { "(", "==", ")", "" }, // ir_binop_all_equal, /** * Returns single boolean for whether any component of operands[0] * is not equal to the corresponding component of operands[1]. */ { "(", "!=", ")", "" }, // ir_binop_any_nequal, /*@}*/ /** * \name Bit-wise binary operations. */ /*@{*/ { "(", "<<", ")", "" }, // ir_binop_lshift, { "(", ">>", ")", "" }, // ir_binop_rshift, { "(", "&", ")", "" }, // ir_binop_bit_and, { "(", "^", ")", "" }, // ir_binop_bit_xor, { "(", "|", ")", "" }, // ir_binop_bit_or, /*@}*/ { "bool%d(uint%d(", ")*uint%d(", "))", "&&" }, // ir_binop_logic_and, { "bool%d(abs(int%d(", ")+int%d(", ")))", "^^" }, // ir_binop_logic_xor, { "bool%d(uint%d(", ")+uint%d(", "))", "||" }, // ir_binop_logic_or, { "dot(", ",", ")", "" }, // ir_binop_dot, { "cross(", ",", ")", "" }, // ir_binop_cross, { "fmin(", ",", ")", "" }, // ir_binop_min, { "fmax(", ",", ")", "" }, // ir_binop_max, { "atan2(", ",", ")", "" }, { "pow(", ",", ")", "" }, // ir_binop_pow, { "mix(", ",", ",", ")" }, // ir_ternop_lerp, { "smoothstep(", ",", ",", ")" }, // ir_ternop_smoothstep, { "clamp(", ",", ",", ")" }, // ir_ternop_clamp, { "ERROR_QUADOP_VECTOR(", ",", ")" }, // ir_quadop_vector, }; static_assert((sizeof(MetalExpressionTable) / sizeof(MetalExpressionTable[0])) == ir_opcode_count, "Metal Expression Table Size Mismatch"); struct SDMARange { unsigned SourceCB; unsigned SourceOffset; unsigned Size; unsigned DestCBIndex; unsigned DestCBPrecision; unsigned DestOffset; bool operator <(SDMARange const & Other) const { if (SourceCB == Other.SourceCB) { return SourceOffset < Other.SourceOffset; } return SourceCB < Other.SourceCB; } }; typedef std::list TDMARangeList; typedef std::map TCBDMARangeMap; static void InsertRange( TCBDMARangeMap& CBAllRanges, unsigned SourceCB, unsigned SourceOffset, unsigned Size, unsigned DestCBIndex, unsigned DestCBPrecision, unsigned DestOffset ) { check(SourceCB < (1 << 12)); check(DestCBIndex < (1 << 12)); check(DestCBPrecision < (1 << 8)); unsigned SourceDestCBKey = (SourceCB << 20) | (DestCBIndex << 8) | DestCBPrecision; SDMARange Range = { SourceCB, SourceOffset, Size, DestCBIndex, DestCBPrecision, DestOffset }; TDMARangeList& CBRanges = CBAllRanges[SourceDestCBKey]; //printf("* InsertRange: %08x\t%d:%d - %d:%c:%d:%d\n", SourceDestCBKey, SourceCB, SourceOffset, DestCBIndex, DestCBPrecision, DestOffset, Size); if (CBRanges.empty()) { CBRanges.push_back(Range); } else { TDMARangeList::iterator Prev = CBRanges.end(); bool bAdded = false; for (auto Iter = CBRanges.begin(); Iter != CBRanges.end(); ++Iter) { if (SourceOffset + Size <= Iter->SourceOffset) { if (Prev == CBRanges.end()) { CBRanges.push_front(Range); } else { CBRanges.insert(Iter, Range); } bAdded = true; break; } Prev = Iter; } if (!bAdded) { CBRanges.push_back(Range); } if (CBRanges.size() > 1) { // Try to merge ranges bool bDirty = false; do { bDirty = false; TDMARangeList NewCBRanges; for (auto Iter = CBRanges.begin(); Iter != CBRanges.end(); ++Iter) { if (Iter == CBRanges.begin()) { Prev = CBRanges.begin(); } else { if (Prev->SourceOffset + Prev->Size == Iter->SourceOffset && Prev->DestOffset + Prev->Size == Iter->DestOffset) { SDMARange Merged = *Prev; Merged.Size = Prev->Size + Iter->Size; NewCBRanges.pop_back(); NewCBRanges.push_back(Merged); ++Iter; NewCBRanges.insert(NewCBRanges.end(), Iter, CBRanges.end()); bDirty = true; break; } } NewCBRanges.push_back(*Iter); Prev = Iter; } CBRanges.swap(NewCBRanges); } while (bDirty); } } } static TDMARangeList SortRanges( TCBDMARangeMap& CBRanges ) { TDMARangeList Sorted; for (auto Iter = CBRanges.begin(); Iter != CBRanges.end(); ++Iter) { Sorted.insert(Sorted.end(), Iter->second.begin(), Iter->second.end()); } Sorted.sort(); return Sorted; } static void DumpSortedRanges(TDMARangeList& SortedRanges) { printf("**********************************\n"); for (auto i = SortedRanges.begin(); i != SortedRanges.end(); ++i ) { auto o = *i; printf("\t%d:%d - %d:%c:%d:%d\n", o.SourceCB, o.SourceOffset, o.DestCBIndex, o.DestCBPrecision, o.DestOffset, o.Size); } } /** * IR visitor used to generate Metal. Based on ir_print_visitor. */ class FGenerateMetalVisitor : public ir_visitor { _mesa_glsl_parse_state* ParseState; /** Track which multi-dimensional arrays are used. */ struct md_array_entry : public exec_node { const glsl_type* type; }; public: /** External variables. */ exec_list input_variables; protected: exec_list output_variables; exec_list uniform_variables; exec_list sampler_variables; exec_list image_variables; /** Attribute [numthreads(X,Y,Z)] */ int32 NumThreadsX; int32 NumThreadsY; int32 NumThreadsZ; /** Track global instructions. */ struct global_ir : public exec_node { ir_instruction* ir; explicit global_ir(ir_instruction* in_ir) : ir(in_ir) {} }; /** Global instructions. */ exec_list global_instructions; /** A mapping from ir_variable * -> unique printable names. */ hash_table *printable_names; /** Structures required by the code. */ hash_table *used_structures; /** Uniform block variables required by the code. */ hash_table *used_uniform_blocks; /** Multi-dimensional arrays required by the code. */ exec_list used_md_arrays; // Code generation flags _mesa_glsl_parser_targets Frequency; FBuffers& Buffers; /** Memory context within which to make allocations. */ void *mem_ctx; /** Buffer to which GLSL source is being generated. */ char** buffer; /** Indentation level. */ int indentation; /** Scope depth. */ int scope_depth; /** The number of temporary variables declared in the current scope. */ int temp_id; /** The number of global variables declared. */ int global_id; /** Whether a semicolon must be printed before the next EOL. */ bool needs_semicolon; bool IsMain; /** * Whether uint literals should be printed as int literals. This is a hack * because glCompileShader crashes on Mac OS X with code like this: * foo = bar[0u]; */ bool should_print_uint_literals_as_ints; /** number of loops in the generated code */ int loop_count; // Only one stage_in is allowed bool bStageInEmitted; // Are we in the middle of a function signature? //bool bIsFunctionSig = false; // Use packed_ prefix when printing out structs bool bUsePacked; // Do we need to add #include bool bNeedsComputeInclude; /** * Return true if the type is a multi-dimensional array. Also, track the * array. */ bool is_md_array(const glsl_type* type) { if (type->base_type == GLSL_TYPE_ARRAY && type->fields.array->base_type == GLSL_TYPE_ARRAY) { foreach_iter(exec_list_iterator, iter, used_md_arrays) { md_array_entry* entry = (md_array_entry*)iter.get(); if (entry->type == type) { return true; } } md_array_entry* entry = new(mem_ctx) md_array_entry(); entry->type = type; used_md_arrays.push_tail(entry); return true; } return false; } /** * Fetch/generate a unique name for ir_variable. * * GLSL IR permits multiple ir_variables to share the same name. This works * fine until we try to print it, when we really need a unique one. */ const char *unique_name(ir_variable *var) { if (var->mode == ir_var_temporary || var->mode == ir_var_auto) { /* Do we already have a name for this variable? */ const char *name = (const char *) hash_table_find(this->printable_names, var); if (name == NULL) { bool bIsGlobal = (scope_depth == 0 && var->mode != ir_var_temporary); const char* prefix = "g"; if (!bIsGlobal) { if (var->type->is_matrix()) { prefix = "m"; } else if (var->type->is_vector()) { prefix = "v"; } else { switch (var->type->base_type) { case GLSL_TYPE_BOOL: prefix = "b"; break; case GLSL_TYPE_UINT: prefix = "u"; break; case GLSL_TYPE_INT: prefix = "i"; break; case GLSL_TYPE_HALF: prefix = "h"; break; case GLSL_TYPE_FLOAT: prefix = "f"; break; default: prefix = "t"; break; } } } int var_id = bIsGlobal ? global_id++ : temp_id++; name = ralloc_asprintf(mem_ctx, "%s%d", prefix, var_id); hash_table_insert(this->printable_names, (void *)name, var); } return name; } /* If there's no conflict, just use the original name */ return var->name; } /** * Add tabs/spaces for the current indentation level. */ void indent() { for (int i = 0; i < indentation; i++) { ralloc_asprintf_append(buffer, "\t"); } } /** * Print out the internal name for a multi-dimensional array. */ void print_md_array_type(const glsl_type *t) { if (t->base_type == GLSL_TYPE_ARRAY) { ralloc_asprintf_append(buffer, "_mdarr_"); do { ralloc_asprintf_append(buffer, "%d_", t->length); t = t->fields.array; } while (t->base_type == GLSL_TYPE_ARRAY); print_base_type(t); } } /** * Print the base type, e.g. vec3. */ void print_base_type(const glsl_type *t) { if (t->base_type == GLSL_TYPE_ARRAY) { bool bPrevPacked = bUsePacked; if (t->element_type()->is_vector() && t->element_type()->vector_elements == 3) { bUsePacked = false; } print_base_type(t->fields.array); bUsePacked = bPrevPacked; } else if (t->base_type == GLSL_TYPE_INPUTPATCH) { ralloc_asprintf_append(buffer, "GLSL_TYPE_INPUTPATCH"); check(0); ralloc_asprintf_append(buffer, "/* %s */ ", t->name); print_base_type(t->inner_type); } else if (t->base_type == GLSL_TYPE_OUTPUTPATCH) { ralloc_asprintf_append(buffer, "GLSL_TYPE_OUTPUTPATCH"); check(0); ralloc_asprintf_append(buffer, "/* %s */ ", t->name); print_base_type(t->inner_type); } else if ((t->base_type == GLSL_TYPE_STRUCT) && (strncmp("gl_", t->name, 3) != 0)) { ralloc_asprintf_append(buffer, "%s", t->name); } else if (t->base_type == GLSL_TYPE_SAMPLER && t->sampler_buffer) { // Typed buffer read check(t->inner_type); print_base_type(t->inner_type); } else if (t->base_type == GLSL_TYPE_IMAGE) { // Do nothing... } else { if (t->base_type == GLSL_TYPE_SAMPLER) { bool bDone = false; if (t->sampler_dimensionality == GLSL_SAMPLER_DIM_2D && t->sampler_array) { ralloc_asprintf_append(buffer, t->sampler_shadow ? "depth2d_array" : "texture2d_array"); bDone = true; } else if (t->HlslName) { if (!strcmp(t->HlslName, "texture2d") && t->sampler_shadow) { ralloc_asprintf_append(buffer, "depth2d"); bDone = true; } else if (!strcmp(t->HlslName, "texturecube") && t->sampler_shadow) { ralloc_asprintf_append(buffer, "depthcube"); bDone = true; } } if (!bDone) { ralloc_asprintf_append(buffer, "%s", t->HlslName ? t->HlslName : "UnsupportedSamplerType"); } } else { check(t->HlslName); if (bUsePacked && t->is_vector() && t->vector_elements < 4) { ralloc_asprintf_append(buffer, "packed_%s", t->HlslName); } else { ralloc_asprintf_append(buffer, "%s", t->HlslName); } } } } /** * Print the portion of the type that appears before a variable declaration. */ void print_type_pre(const glsl_type *t) { if (is_md_array(t)) { print_md_array_type(t); } else { print_base_type(t); } } /** * Print the portion of the type that appears after a variable declaration. */ void print_type_post(const glsl_type *t) { if (t->base_type == GLSL_TYPE_ARRAY && !is_md_array(t)) { ralloc_asprintf_append(buffer, "[%u]", t->length); } else if (t->base_type == GLSL_TYPE_INPUTPATCH || t->base_type == GLSL_TYPE_OUTPUTPATCH) { ralloc_asprintf_append(buffer, "[%u] /* %s */", t->patch_length, t->name); } } /** * Print a full variable declaration. */ void print_type_full(const glsl_type *t) { print_type_pre(t); print_type_post(t); } /** * Visit a single instruction. Appends a semicolon and EOL if needed. */ void do_visit(ir_instruction* ir) { needs_semicolon = true; ir->accept(this); if (needs_semicolon) { ralloc_asprintf_append(buffer, ";\n"); } } /** * \name Visit methods * * As typical for the visitor pattern, there must be one \c visit method for * each concrete subclass of \c ir_instruction. Virtual base classes within * the hierarchy should not have \c visit methods. */ virtual void visit(ir_rvalue *rvalue) override { check(0 && "ir_rvalue not handled for GLSL export."); } virtual void visit(ir_variable *var) override { // Check for an initialized const variable // If var is read-only and initialized, set it up as an initialized const bool constInit = false; if (var->has_initializer && var->read_only && (var->constant_initializer || var->constant_value)) { ralloc_asprintf_append( buffer, "const "); constInit = true; } if (scope_depth == 0) { check(0); } if (scope_depth == 0 && var->mode == ir_var_temporary) { check(0); } else { if (scope_depth == 0 && ((var->mode == ir_var_in) || (var->mode == ir_var_out)) && var->is_interface_block) { check(0); } else if (var->type->is_image()) { auto* PtrType = var->type->is_array() ? var->type->element_type() : var->type; check(!PtrType->is_array() && PtrType->inner_type); // Buffer int BufferIndex = Buffers.GetIndex(var); check(BufferIndex >= 0); if (var->type->sampler_buffer) { ralloc_asprintf_append( buffer, "device " ); print_type_pre(PtrType->inner_type); ralloc_asprintf_append(buffer, " *%s", unique_name(var)); print_type_post(PtrType->inner_type); ralloc_asprintf_append( buffer, " [[ buffer(%d) ]]", BufferIndex ); } else { check(PtrType->inner_type->is_numeric()); auto* Found = strstr(PtrType->name, "image"); check(Found); Found += 5; //strlen("image") char Temp[16]; char* TempPtr = Temp; do { *TempPtr++ = tolower(*Found); ++Found; } while (*Found); *TempPtr = 0; ralloc_asprintf_append(buffer, "texture%s<", Temp); print_type_pre(PtrType->inner_type); ralloc_asprintf_append(buffer, ", access::write> %s", unique_name(var)); ralloc_asprintf_append( buffer, " [[ buffer(%d) ]]", BufferIndex ); } } else { if (IsMain && var->type->base_type == GLSL_TYPE_STRUCT && (var->mode == ir_var_in || var->mode == ir_var_out || var->mode == ir_var_uniform)) { if (hash_table_find(used_structures, var->type) == NULL) { hash_table_insert(used_structures, (void*)var->type, var->type); } } if (IsMain && var->mode == ir_var_uniform) { auto* PtrType = var->type->is_array() ? var->type->element_type() : var->type; check(!PtrType->is_array()); if (var->type->is_sampler()) { if (var->type->sampler_buffer) { // Buffer int BufferIndex = Buffers.GetIndex(var); check(BufferIndex >= 0); ralloc_asprintf_append( buffer, "constant " ); print_type_pre(PtrType); ralloc_asprintf_append(buffer, " *%s", unique_name(var)); print_type_post(PtrType); ralloc_asprintf_append( buffer, " [[ buffer(%d) ]]", BufferIndex ); } else { // Regular textures auto* Entry = ParseState->FindPackedSamplerEntry(var->name); check(Entry); //@todo-rco: SamplerStates auto SamplerStateFound = ParseState->TextureToSamplerMap.find(Entry->Name.c_str()); if (SamplerStateFound != ParseState->TextureToSamplerMap.end()) { auto& SamplerStates = SamplerStateFound->second; for (auto& SamplerState : SamplerStates) { int32 SamplerStateIndex = Buffers.GetUniqueSamplerStateIndex(SamplerState, true); ralloc_asprintf_append( buffer, "sampler s%d [[ sampler(%d) ]], ", SamplerStateIndex, SamplerStateIndex); } } print_type_pre(PtrType); ralloc_asprintf_append( buffer, "<%s> %s", (PtrType->inner_type && PtrType->inner_type->base_type == GLSL_TYPE_HALF) ? "half" : "float", unique_name(var)); print_type_post(PtrType); ralloc_asprintf_append( buffer, " [[ texture(%d) ]]", Entry->offset ); } } else { int BufferIndex = Buffers.GetIndex(var); bool bNeedsPointer = (var->semantic && (strlen(var->semantic) == 1)); check(BufferIndex >= 0); ralloc_asprintf_append( buffer, "constant " ); print_type_pre(PtrType); ralloc_asprintf_append(buffer, " %s%s", bNeedsPointer ? "*" : "&", unique_name(var)); print_type_post(PtrType); ralloc_asprintf_append( buffer, " [[ buffer(%d) ]]", BufferIndex ); } } else if (IsMain && var->mode == ir_var_in) { if (!strcmp(var->name, "gl_FrontFacing")) { check(var->type->is_boolean()); print_type_pre(var->type); ralloc_asprintf_append(buffer, " %s", unique_name(var)); print_type_post(var->type); ralloc_asprintf_append( buffer, " [[ front_facing ]]" ); } else if (var->semantic && !strncmp(var->semantic, "[[ color(", 9)) { check(var->type->is_vector() && var->type->vector_elements == 4); print_type_pre(var->type); ralloc_asprintf_append(buffer, " %s", unique_name(var)); print_type_post(var->type); ralloc_asprintf_append( buffer, " %s", var->semantic ); } else if(var->semantic && !strncmp(var->semantic,"[[", 2)) { check(!var->type->is_record()); print_type_pre(var->type); ralloc_asprintf_append(buffer," %s",unique_name(var)); print_type_post(var->type); ralloc_asprintf_append( buffer, " %s", var->semantic ); } else { check(var->type->is_record()); check(!bStageInEmitted); print_type_pre(var->type); ralloc_asprintf_append(buffer, " %s", unique_name(var)); print_type_post(var->type); ralloc_asprintf_append( buffer, " [[ stage_in ]]" ); bStageInEmitted = true; } } else if (IsMain && var->mode == ir_var_out) { auto* PtrType = var->type->is_array() ? var->type->element_type() : var->type; check(!PtrType->is_array()); print_type_pre(PtrType); ralloc_asprintf_append(buffer, " %s", unique_name(var)); print_type_post(PtrType); } else { if (var->mode == ir_var_shared) { ralloc_asprintf_append(buffer, "threadgroup "); } print_type_pre(var->type); ralloc_asprintf_append(buffer, " %s", unique_name(var)); print_type_post(var->type); } } } // Add the initializer if we need it if (constInit) { ralloc_asprintf_append(buffer, " = "); if (var->constant_initializer) { var->constant_initializer->accept(this); } else { var->constant_value->accept(this); } } } virtual void visit(ir_function_signature *sig) override { // Reset temporary id count. temp_id = 0; bool bPrintComma = false; scope_depth++; IsMain = sig->is_main; print_type_full(sig->return_type); ralloc_asprintf_append(buffer, " %s(", sig->function_name()); foreach_iter(exec_list_iterator, iter, sig->parameters) { ir_variable *const inst = (ir_variable *) iter.get(); if (bPrintComma) { ralloc_asprintf_append(buffer, ",\n"); ++indentation; indent(); --indentation; } inst->accept(this); bPrintComma = true; } ralloc_asprintf_append(buffer, ")\n"); indent(); ralloc_asprintf_append(buffer, "{\n"); if (sig->is_main && !global_instructions.is_empty()) { indentation++; foreach_iter(exec_list_iterator, iter, global_instructions) { global_ir* gir = (global_ir*)iter.get(); indent(); do_visit(gir->ir); } indentation--; } // Copy the global attributes if (sig->is_main) { NumThreadsX = sig->wg_size_x; NumThreadsY = sig->wg_size_y; NumThreadsZ = sig->wg_size_z; } indentation++; foreach_iter(exec_list_iterator, iter, sig->body) { ir_instruction *const inst = (ir_instruction *) iter.get(); indent(); do_visit(inst); } indentation--; indent(); ralloc_asprintf_append(buffer, "}\n"); needs_semicolon = false; IsMain = false; scope_depth--; } virtual void visit(ir_function *func) override { foreach_iter(exec_list_iterator, iter, *func) { ir_function_signature *const sig = (ir_function_signature *) iter.get(); if (sig->is_defined && !sig->is_builtin) { indent(); if (sig->is_main) { switch (Frequency) { case vertex_shader: ralloc_asprintf_append(buffer, "vertex "); break; case fragment_shader: ralloc_asprintf_append(buffer, "fragment "); break; case compute_shader: ralloc_asprintf_append(buffer, "kernel "); break; default: check(0); break; } } sig->accept(this); } } needs_semicolon = false; } virtual void visit(ir_expression *expr) override { check(scope_depth > 0); int numOps = expr->get_num_operands(); ir_expression_operation op = expr->operation; if (op == ir_unop_rcp) { check(numOps == 1); std::string Type = expr->type->name; Type = FixVecPrefix(Type); ralloc_asprintf_append(buffer, "(%s(1.0) / ", Type.c_str()); expr->operands[0]->accept(this); ralloc_asprintf_append(buffer, ")"); } else if (numOps == 1 && op >= ir_unop_first_conversion && op <= ir_unop_last_conversion) { std::string Type = expr->type->name; Type = FixVecPrefix(Type); ralloc_asprintf_append(buffer, "%s(", Type.c_str()); expr->operands[0]->accept(this); ralloc_asprintf_append(buffer, ")"); } else if (expr->type->is_scalar() && ((numOps == 1 && op == ir_unop_logic_not) || (numOps == 2 && op >= ir_binop_first_comparison && op <= ir_binop_last_comparison) || (numOps == 2 && op >= ir_binop_first_logic && op <= ir_binop_last_logic))) { const char* op_str = MetalExpressionTable[op][3]; ralloc_asprintf_append(buffer, "%s(", (numOps == 1) ? op_str : ""); expr->operands[0]->accept(this); if (numOps == 2) { ralloc_asprintf_append(buffer, "%s", op_str); expr->operands[1]->accept(this); } ralloc_asprintf_append(buffer, ")"); } else if (expr->type->is_vector() && numOps == 2 && op >= ir_binop_first_logic && op <= ir_binop_last_logic) { ralloc_asprintf_append(buffer, MetalExpressionTable[op][0], expr->type->vector_elements, expr->type->vector_elements); expr->operands[0]->accept(this); ralloc_asprintf_append(buffer, MetalExpressionTable[op][1], expr->type->vector_elements); expr->operands[1]->accept(this); ralloc_asprintf_append(buffer, MetalExpressionTable[op][2]); } else if (op == ir_binop_mod && !expr->type->is_float()) { ralloc_asprintf_append(buffer, "(("); expr->operands[0]->accept(this); ralloc_asprintf_append(buffer, ")%%("); expr->operands[1]->accept(this); ralloc_asprintf_append(buffer, "))"); } else if (op == ir_binop_mul && expr->type->is_matrix() && expr->operands[0]->type->is_matrix() && expr->operands[1]->type->is_matrix()) { ralloc_asprintf_append(buffer, "ERRROR_MulMatrix()"); check(0); } else if (op == ir_ternop_clamp && expr->type->base_type == GLSL_TYPE_FLOAT) { ralloc_asprintf_append(buffer, "precise::%s", MetalExpressionTable[op][0]); for (int i = 0; i < numOps; ++i) { expr->operands[i]->accept(this); ralloc_asprintf_append(buffer, MetalExpressionTable[op][i+1]); } } else if (numOps == 2 && (op == ir_binop_max || op == ir_binop_min) && expr->type->is_integer()) { // Convert fmax/fmin to max/min when dealing with integers auto* OpString = MetalExpressionTable[op][0]; check(OpString[0] == 'f'); ralloc_asprintf_append(buffer, OpString + 1); expr->operands[0]->accept(this); ralloc_asprintf_append(buffer, MetalExpressionTable[op][1]); expr->operands[1]->accept(this); ralloc_asprintf_append(buffer, MetalExpressionTable[op][2]); } else if (numOps < 4) { ralloc_asprintf_append(buffer, MetalExpressionTable[op][0]); for (int i = 0; i < numOps; ++i) { expr->operands[i]->accept(this); ralloc_asprintf_append(buffer, MetalExpressionTable[op][i+1]); } } } virtual void visit(ir_texture *tex) override { check(scope_depth > 0); bool bNeedsClosingParenthesis = true; tex->sampler->accept(this); if (tex->op == ir_tex || tex->op == ir_txl || tex->op == ir_txb) { ralloc_asprintf_append(buffer, tex->shadow_comparitor ? ".sample_compare(" : ".sample("); auto* Texture = tex->sampler->variable_referenced(); check(Texture); auto* Entry = ParseState->FindPackedSamplerEntry(Texture->name); int32 SamplerStateIndex = Buffers.GetUniqueSamplerStateIndex(tex->SamplerStateName, false); check(SamplerStateIndex != INDEX_NONE); ralloc_asprintf_append(buffer, "s%d, ", SamplerStateIndex); if (tex->sampler->type->sampler_array) { // Need to split the coordinate ralloc_asprintf_append(buffer, "("); tex->coordinate->accept(this); ralloc_asprintf_append(buffer, ").x%s, (uint)(", tex->sampler->type->sampler_dimensionality == GLSL_SAMPLER_DIM_2D ? "y" : ""); tex->coordinate->accept(this); ralloc_asprintf_append(buffer, ").%s", tex->sampler->type->sampler_dimensionality == GLSL_SAMPLER_DIM_2D ? "z" : "y"); } else { tex->coordinate->accept(this); } if (tex->op == ir_txl && !tex->shadow_comparitor) { ralloc_asprintf_append(buffer, ", level("); tex->lod_info.lod->accept(this); ralloc_asprintf_append(buffer, ")"); } else if (tex->op == ir_txb) { ralloc_asprintf_append(buffer, ", bias("); tex->lod_info.lod->accept(this); ralloc_asprintf_append(buffer, ")"); } if (tex->shadow_comparitor) { ralloc_asprintf_append(buffer, ", "); tex->shadow_comparitor->accept(this); } if (tex->offset) { ralloc_asprintf_append(buffer, ", "); tex->offset->accept(this); } } else if (tex->op == ir_txf) { if (tex->sampler->type && tex->sampler->type->is_sampler() && tex->sampler->type->sampler_buffer) { ralloc_asprintf_append(buffer, "["); tex->coordinate->accept(this); ralloc_asprintf_append(buffer, "]"); bNeedsClosingParenthesis = false; } else { ralloc_asprintf_append(buffer, ".read("); tex->coordinate->accept(this); } } else { ralloc_asprintf_append(buffer, "UNKNOWN TEXOP %d!", tex->op); check(0); } if (bNeedsClosingParenthesis) { ralloc_asprintf_append(buffer, ")"); } } virtual void visit(ir_swizzle *swizzle) override { check(scope_depth > 0); const unsigned mask[4] = { swizzle->mask.x, swizzle->mask.y, swizzle->mask.z, swizzle->mask.w, }; if (swizzle->val->type->is_scalar()) { // Scalar -> Vector swizzles must use the constructor syntax. if (swizzle->type->is_scalar() == false) { print_type_full(swizzle->type); ralloc_asprintf_append(buffer, "("); swizzle->val->accept(this); ralloc_asprintf_append(buffer, ")"); } } else { const bool is_constant = swizzle->val->as_constant() != nullptr; if (is_constant) { ralloc_asprintf_append(buffer, "("); } swizzle->val->accept(this); if (is_constant) { ralloc_asprintf_append(buffer, ")"); } ralloc_asprintf_append(buffer, "."); for (unsigned i = 0; i < swizzle->mask.num_components; i++) { ralloc_asprintf_append(buffer, "%c", "xyzw"[mask[i]]); } } } virtual void visit(ir_dereference_variable *deref) override { check(scope_depth > 0); ir_variable* var = deref->variable_referenced(); ralloc_asprintf_append(buffer, unique_name(var)); if (var->type->base_type == GLSL_TYPE_STRUCT) { if (hash_table_find(used_structures, var->type) == NULL) { hash_table_insert(used_structures, (void*)var->type, var->type); } } if (var->type->base_type == GLSL_TYPE_ARRAY && var->type->fields.array->base_type == GLSL_TYPE_STRUCT) { if (hash_table_find(used_structures, var->type->fields.array) == NULL) { hash_table_insert(used_structures, (void*)var->type->fields.array, var->type->fields.array); } } if ((var->type->base_type == GLSL_TYPE_INPUTPATCH || var->type->base_type == GLSL_TYPE_OUTPUTPATCH) && var->type->inner_type->base_type == GLSL_TYPE_STRUCT) { if (hash_table_find(used_structures, var->type->inner_type) == NULL) { hash_table_insert(used_structures, (void*)var->type->inner_type, var->type->inner_type); } } if (var->mode == ir_var_uniform && var->semantic != NULL) { if (hash_table_find(used_uniform_blocks, var->semantic) == NULL) { hash_table_insert(used_uniform_blocks, (void*)var->semantic, var->semantic); } } if (is_md_array(deref->type)) { ralloc_asprintf_append(buffer, ".Inner"); } } virtual void visit(ir_dereference_array *deref) override { check(scope_depth > 0); deref->array->accept(this); // Make extra sure crappy Mac OS X compiler won't have any reason to crash bool enforceInt = false; if (deref->array_index->type->base_type == GLSL_TYPE_UINT) { if (deref->array_index->ir_type == ir_type_constant) { should_print_uint_literals_as_ints = true; } else { enforceInt = true; } } if (enforceInt) { ralloc_asprintf_append(buffer, "[int("); } else { ralloc_asprintf_append(buffer, "["); } deref->array_index->accept(this); should_print_uint_literals_as_ints = false; if (enforceInt) { ralloc_asprintf_append(buffer, ")]"); } else { ralloc_asprintf_append(buffer, "]"); } if (is_md_array(deref->array->type)) { ralloc_asprintf_append(buffer, ".Inner"); } } void print_image_op( ir_dereference_image *deref, ir_rvalue *src) { const int dst_elements = deref->type->vector_elements; const int src_elements = (src) ? src->type->vector_elements : 1; check( 1 <= dst_elements && dst_elements <= 4); check( 1 <= src_elements && src_elements <= 4); if ( deref->op == ir_image_access) { bool bIsRWTexture = !deref->image->type->sampler_buffer; if (src == nullptr) { deref->image->accept(this); if (bIsRWTexture) { ralloc_asprintf_append(buffer, ".read("); deref->image_index->accept(this); ralloc_asprintf_append(buffer, ")"); } else { ralloc_asprintf_append(buffer, "["); deref->image_index->accept(this); ralloc_asprintf_append(buffer, "]"/*.%s, swizzle[dst_elements - 1]*/); } } else { deref->image->accept(this); if (bIsRWTexture) { ralloc_asprintf_append(buffer, ".write("); src->accept(this); ralloc_asprintf_append(buffer, ","); deref->image_index->accept(this); ralloc_asprintf_append(buffer, ")"); } else { ralloc_asprintf_append( buffer, "[" ); deref->image_index->accept(this); ralloc_asprintf_append( buffer, "] = " ); src->accept(this); ralloc_asprintf_append(buffer, ""/*".%s", expand[src_elements - 1]*/); } } } else if ( deref->op == ir_image_dimensions) { ralloc_asprintf_append( buffer, "imageSize( " ); deref->image->accept(this); ralloc_asprintf_append(buffer, ")"); } else { check( !"Unknown image operation"); } } virtual void visit(ir_dereference_image *deref) override { check(scope_depth > 0); print_image_op( deref, NULL); } virtual void visit(ir_dereference_record *deref) override { check(scope_depth > 0); deref->record->accept(this); ralloc_asprintf_append(buffer, ".%s", deref->field); if (is_md_array(deref->type)) { ralloc_asprintf_append(buffer, ".Inner"); } } virtual void visit(ir_assignment *assign) override { if (scope_depth == 0) { global_instructions.push_tail(new(mem_ctx) global_ir(assign)); needs_semicolon = false; return; } // constant variables with initializers are statically assigned ir_variable *var = assign->lhs->variable_referenced(); if (var->has_initializer && var->read_only && (var->constant_initializer || var->constant_value)) { //This will leave a blank line with a semi-colon return; } if (assign->condition) { ralloc_asprintf_append(buffer, "if("); assign->condition->accept(this); ralloc_asprintf_append(buffer, ") { "); } if (assign->lhs->as_dereference_image() != NULL) { /** EHart - should the write mask be checked here? */ print_image_op( assign->lhs->as_dereference_image(), assign->rhs); } else { char mask[6]; unsigned j = 1; if (assign->lhs->type->is_scalar() == false || assign->write_mask != 0x1) { for (unsigned i = 0; i < 4; i++) { if ((assign->write_mask & (1 << i)) != 0) { mask[j] = "xyzw"[i]; j++; } } } mask[j] = '\0'; mask[0] = (j == 1) ? '\0' : '.'; assign->lhs->accept(this); ralloc_asprintf_append(buffer, "%s = ", mask); // Hack: Need to add additional cast from packed types bool bNeedToAcceptRHS = true; if (auto* Expr = assign->rhs->as_expression()) { if (Expr->operation == ir_unop_f2h) { auto* Var = Expr->operands[0]->variable_referenced(); if (Var && Var->mode == ir_var_uniform && Var->type->HlslName && !strcmp(Var->type->HlslName, "__PACKED__")) { ralloc_asprintf_append(buffer, "%s(%s(", Expr->type->name, FixVecPrefix(PromoteHalfToFloatType(ParseState, Expr->type)->name).c_str()); Expr->operands[0]->accept(this); ralloc_asprintf_append(buffer, "))"); bNeedToAcceptRHS = false; } } } if (bNeedToAcceptRHS) { assign->rhs->accept(this); } } if (assign->condition) { ralloc_asprintf_append(buffer, "%s }", needs_semicolon ? ";" : ""); } } void print_constant(ir_constant *constant, int index) { if (constant->type->is_float()) { if (constant->is_component_finite(index)) { float value = constant->value.f[index]; const char *format = (fabsf(fmodf(value,1.0f)) < 1.e-8f) ? "%.1f" : "%.8f"; ralloc_asprintf_append(buffer, format, value); } else { switch (constant->value.u[index]) { case 0x7f800000u: ralloc_asprintf_append(buffer, "(1.0/0.0)"); break; case 0xffc00000u: ralloc_asprintf_append(buffer, "(0.0/0.0)"); break; case 0xff800000u: ralloc_asprintf_append(buffer, "(-1.0/0.0)"); break; default: ralloc_asprintf_append(buffer, "UnknownNonFinite_0x%08x", constant->value.u[index]); check(0); } } } else if (constant->type->base_type == GLSL_TYPE_INT) { ralloc_asprintf_append(buffer, "%d", constant->value.i[index]); } else if (constant->type->base_type == GLSL_TYPE_UINT) { ralloc_asprintf_append(buffer, "%u%s", constant->value.u[index], should_print_uint_literals_as_ints ? "" : "u" ); } else if (constant->type->base_type == GLSL_TYPE_BOOL) { ralloc_asprintf_append(buffer, "%s", constant->value.b[index] ? "true" : "false"); } } virtual void visit(ir_constant *constant) override { if (constant->type == glsl_type::float_type || constant->type == glsl_type::int_type || constant->type == glsl_type::uint_type) { print_constant(constant, 0); } else if (constant->type->is_record()) { print_type_full(constant->type); ralloc_asprintf_append(buffer, "("); ir_constant* value = (ir_constant*)constant->components.get_head(); if (value) { value->accept(this); } for (uint32 i = 1; i < constant->type->length; i++) { check(value); value = (ir_constant*)value->next; if (value) { ralloc_asprintf_append(buffer, ","); value->accept(this); } } ralloc_asprintf_append(buffer, ")"); } else if (constant->type->is_array()) { print_type_full(constant->type); ralloc_asprintf_append(buffer, "("); constant->get_array_element(0)->accept(this); for (uint32 i = 1; i < constant->type->length; ++i) { ralloc_asprintf_append(buffer, ","); constant->get_array_element(i)->accept(this); } ralloc_asprintf_append(buffer, ")"); } else { print_type_full(constant->type); ralloc_asprintf_append(buffer, "("); print_constant(constant, 0); int num_components = constant->type->components(); for (int i = 1; i < num_components; ++i) { ralloc_asprintf_append(buffer, ","); print_constant(constant, i); } ralloc_asprintf_append(buffer, ")"); } } virtual void visit(ir_call *call) override { if (scope_depth == 0) { global_instructions.push_tail(new(mem_ctx) global_ir(call)); needs_semicolon = false; return; } if (call->return_deref) { call->return_deref->accept(this); ralloc_asprintf_append(buffer, " = "); } else { //@todo-rco: Fix this properly if (!strcmp(call->callee_name(), GROUP_MEMORY_BARRIER) || !strcmp(call->callee_name(), GROUP_MEMORY_BARRIER_WITH_GROUP_SYNC)) { bNeedsComputeInclude = true; ralloc_asprintf_append(buffer, "threadgroup_barrier(mem_flags::mem_threadgroup)"); return; } else if (!strcmp(call->callee_name(), DEVICE_MEMORY_BARRIER) || !strcmp(call->callee_name(), DEVICE_MEMORY_BARRIER_WITH_GROUP_SYNC)) { bNeedsComputeInclude = true; ralloc_asprintf_append(buffer, "threadgroup_barrier(mem_flags::mem_device)"); return; } else if (!strcmp(call->callee_name(), ALL_MEMORY_BARRIER) || !strcmp(call->callee_name(), ALL_MEMORY_BARRIER_WITH_GROUP_SYNC)) { bNeedsComputeInclude = true; ralloc_asprintf_append(buffer, "threadgroup_barrier(mem_flags::mem_device_and_threadgroup)"); return; } } if (!strcmp(call->callee_name(), "packHalf2x16")) { ralloc_asprintf_append(buffer, "as_type(half2("); } else { ralloc_asprintf_append(buffer, "%s(", call->callee_name()); } bool bPrintComma = false; foreach_iter(exec_list_iterator, iter, *call) { ir_instruction *const inst = (ir_instruction *) iter.get(); if (bPrintComma) { ralloc_asprintf_append(buffer, ","); } inst->accept(this); bPrintComma = true; } ralloc_asprintf_append(buffer, ")"); if (!strcmp(call->callee_name(), "packHalf2x16")) { ralloc_asprintf_append(buffer, ")"); } } virtual void visit(ir_return *ret) override { check(scope_depth > 0); ralloc_asprintf_append(buffer, "return "); ir_rvalue *const value = ret->get_value(); if (value) { value->accept(this); } } virtual void visit(ir_discard *discard) override { check(scope_depth > 0); if (discard->condition) { ralloc_asprintf_append(buffer, "if ("); discard->condition->accept(this); ralloc_asprintf_append(buffer, ") "); } ralloc_asprintf_append(buffer, "discard_fragment()"); } bool try_conditional_move(ir_if *expr) { ir_dereference_variable *dest_deref = NULL; ir_rvalue *true_value = NULL; ir_rvalue *false_value = NULL; unsigned write_mask = 0; const glsl_type *assign_type = NULL; int num_inst; num_inst = 0; foreach_iter(exec_list_iterator, iter, expr->then_instructions) { if (num_inst > 0) { // multiple instructions? not a conditional move return false; } ir_instruction *const inst = (ir_instruction *) iter.get(); ir_assignment *assignment = inst->as_assignment(); if (assignment && (assignment->rhs->ir_type == ir_type_dereference_variable || assignment->rhs->ir_type == ir_type_constant)) { dest_deref = assignment->lhs->as_dereference_variable(); true_value = assignment->rhs; write_mask = assignment->write_mask; } num_inst++; } if (dest_deref == NULL || true_value == NULL) return false; num_inst = 0; foreach_iter(exec_list_iterator, iter, expr->else_instructions) { if (num_inst > 0) { // multiple instructions? not a conditional move return false; } ir_instruction *const inst = (ir_instruction *) iter.get(); ir_assignment *assignment = inst->as_assignment(); if (assignment && (assignment->rhs->ir_type == ir_type_dereference_variable || assignment->rhs->ir_type == ir_type_constant)) { ir_dereference_variable *tmp_deref = assignment->lhs->as_dereference_variable(); if (tmp_deref && tmp_deref->var == dest_deref->var && tmp_deref->type == dest_deref->type && assignment->write_mask == write_mask) { false_value= assignment->rhs; } } num_inst++; } if (false_value == NULL) return false; char mask[6]; unsigned j = 1; if (dest_deref->type->is_scalar() == false || write_mask != 0x1) { for (unsigned i = 0; i < 4; i++) { if ((write_mask & (1 << i)) != 0) { mask[j] = "xyzw"[i]; j++; } } } mask[j] = '\0'; mask[0] = (j == 1) ? '\0' : '.'; dest_deref->accept(this); ralloc_asprintf_append(buffer, "%s = (", mask); expr->condition->accept(this); ralloc_asprintf_append(buffer, ")?("); true_value->accept(this); ralloc_asprintf_append(buffer, "):("); false_value->accept(this); ralloc_asprintf_append(buffer, ")"); return true; } virtual void visit(ir_if *expr) override { check(scope_depth > 0); if (try_conditional_move(expr) == false) { ralloc_asprintf_append(buffer, "if ("); expr->condition->accept(this); ralloc_asprintf_append(buffer, ")\n"); indent(); ralloc_asprintf_append(buffer, "{\n"); indentation++; foreach_iter(exec_list_iterator, iter, expr->then_instructions) { ir_instruction *const inst = (ir_instruction *) iter.get(); indent(); do_visit(inst); } indentation--; indent(); ralloc_asprintf_append(buffer, "}\n"); if (!expr->else_instructions.is_empty()) { indent(); ralloc_asprintf_append(buffer, "else\n"); indent(); ralloc_asprintf_append(buffer, "{\n"); indentation++; foreach_iter(exec_list_iterator, iter, expr->else_instructions) { ir_instruction *const inst = (ir_instruction *) iter.get(); indent(); do_visit(inst); } indentation--; indent(); ralloc_asprintf_append(buffer, "}\n"); } needs_semicolon = false; } } virtual void visit(ir_loop *loop) override { check(scope_depth > 0); if (loop->counter && loop->to) { // IR cmp operator is when to terminate loop; whereas GLSL for loop syntax // is while to continue the loop. Invert the meaning of operator when outputting. const char* termOp = NULL; switch (loop->cmp) { case ir_binop_less: termOp = ">="; break; case ir_binop_greater: termOp = "<="; break; case ir_binop_lequal: termOp = ">"; break; case ir_binop_gequal: termOp = "<"; break; case ir_binop_equal: termOp = "!="; break; case ir_binop_nequal: termOp = "=="; break; default: check(false); } ralloc_asprintf_append(buffer, "for (;%s%s", unique_name(loop->counter), termOp); loop->to->accept (this); ralloc_asprintf_append(buffer, ";)\n"); } else { #if 1 ralloc_asprintf_append(buffer, "for (;;)\n"); #else ralloc_asprintf_append(buffer, "for ( int loop%d = 0; loop%d < 256; loop%d ++)\n", loop_count, loop_count, loop_count); loop_count++; #endif } indent(); ralloc_asprintf_append(buffer, "{\n"); indentation++; foreach_iter(exec_list_iterator, iter, loop->body_instructions) { ir_instruction *const inst = (ir_instruction *) iter.get(); indent(); do_visit(inst); } indentation--; indent(); ralloc_asprintf_append(buffer, "}\n"); needs_semicolon = false; } virtual void visit(ir_loop_jump *jmp) override { check(scope_depth > 0); ralloc_asprintf_append(buffer, "%s", jmp->is_break() ? "break" : "continue"); } virtual void visit(ir_atomic *ir) override { const char *sharedAtomicFunctions[] = { "atomicAdd", "atomicAnd", "atomicMin", "atomicMax", "atomicOr", "atomicXor", "atomicExchange", "atomicCompSwap" }; const char *imageAtomicFunctions[] = { "imageAtomicAdd", "imageAtomicAnd", "imageAtomicMin", "imageAtomicMax", "imageAtomicOr", "imageAtomicXor", "imageAtomicExchange", "imageAtomicCompSwap" }; check(scope_depth > 0); const bool is_image = ir->memory_ref->as_dereference_image() != NULL; ir->lhs->accept(this); if (!is_image) { ralloc_asprintf_append(buffer, " = %s(", sharedAtomicFunctions[ir->operation]); ir->memory_ref->accept(this); ralloc_asprintf_append(buffer, ", "); ir->operands[0]->accept(this); if (ir->operands[1]) { ralloc_asprintf_append(buffer, ", "); ir->operands[1]->accept(this); } ralloc_asprintf_append(buffer, ")"); } else { ir_dereference_image *image = ir->memory_ref->as_dereference_image(); ralloc_asprintf_append(buffer, " = %s(", imageAtomicFunctions[ir->operation]); image->image->accept(this); ralloc_asprintf_append(buffer, ", "); image->image_index->accept(this); ralloc_asprintf_append(buffer, ", "); ir->operands[0]->accept(this); if (ir->operands[1]) { ralloc_asprintf_append(buffer, ", "); ir->operands[1]->accept(this); } ralloc_asprintf_append(buffer, ")"); } } /** * Declare structs used to simulate multi-dimensional arrays. */ void declare_md_array_struct(const glsl_type* type, hash_table* ht) { check(type->is_array()); if (hash_table_find(ht, (void*)type) == NULL) { const glsl_type* subtype = type->fields.array; if (subtype->base_type == GLSL_TYPE_ARRAY) { declare_md_array_struct(subtype, ht); ralloc_asprintf_append(buffer, "struct "); print_md_array_type(type); ralloc_asprintf_append(buffer, "\n{\n\t"); print_md_array_type(subtype); ralloc_asprintf_append(buffer, " Inner[%u];\n};\n\n", type->length); } else { ralloc_asprintf_append(buffer, "struct "); print_md_array_type(type); ralloc_asprintf_append(buffer, "\n{\n\t"); print_type_pre(type); ralloc_asprintf_append(buffer, " Inner"); print_type_post(type); ralloc_asprintf_append(buffer, ";\n};\n\n"); } hash_table_insert(ht, (void*)type, (void*)type); } } /** * Declare structs used by the code that has been generated. */ void declare_structs(_mesa_glsl_parse_state* state) { // If any variable in a uniform block is in use, the entire uniform block // must be present including structs that are not actually accessed. for (unsigned i = 0; i < state->num_uniform_blocks; i++) { const glsl_uniform_block* block = state->uniform_blocks[i]; if (hash_table_find(used_uniform_blocks, block->name)) { for (unsigned var_index = 0; var_index < block->num_vars; ++var_index) { const glsl_type* type = block->vars[var_index]->type; if (type->base_type == GLSL_TYPE_STRUCT && hash_table_find(used_structures, type) == NULL) { hash_table_insert(used_structures, (void*)type, type); } } } } // If otherwise unused structure is a member of another, used structure, the unused structure is also, in fact, used { int added_structure_types; do { added_structure_types = 0; for (unsigned i = 0; i < state->num_user_structures; i++) { const glsl_type *const s = state->user_structures[i]; if (hash_table_find(used_structures, s) == NULL) { continue; } for (unsigned j = 0; j < s->length; j++) { const glsl_type* type = s->fields.structure[j].type; if (type->base_type == GLSL_TYPE_STRUCT) { if (hash_table_find(used_structures, type) == NULL) { hash_table_insert(used_structures, (void*)type, type); ++added_structure_types; } } else if (type->base_type == GLSL_TYPE_ARRAY && type->fields.array->base_type == GLSL_TYPE_STRUCT) { if (hash_table_find(used_structures, type->fields.array) == NULL) { hash_table_insert(used_structures, (void*)type->fields.array, type->fields.array); } } else if ((type->base_type == GLSL_TYPE_INPUTPATCH || type->base_type == GLSL_TYPE_OUTPUTPATCH) && type->inner_type->base_type == GLSL_TYPE_STRUCT) { if (hash_table_find(used_structures, type->inner_type) == NULL) { hash_table_insert(used_structures, (void*)type->inner_type, type->inner_type); } } } } } while( added_structure_types > 0 ); } // Generate structures that allow support for multi-dimensional arrays. { hash_table* ht = hash_table_ctor(32, hash_table_pointer_hash, hash_table_pointer_compare); foreach_iter(exec_list_iterator, iter, used_md_arrays) { md_array_entry* entry = (md_array_entry*)iter.get(); declare_md_array_struct(entry->type, ht); } hash_table_dtor(ht); } for (unsigned i = 0; i < state->num_user_structures; i++) { const glsl_type *const s = state->user_structures[i]; if (hash_table_find(used_structures, s) == NULL) { continue; } if (s->HlslName && !strcmp(s->HlslName, "__PACKED__")) { bUsePacked = true; } ralloc_asprintf_append(buffer, "struct %s\n{\n", s->name); if (s->length == 0) { ralloc_asprintf_append(buffer, "\t float glsl_doesnt_like_empty_structs;\n"); } else { for (unsigned j = 0; j < s->length; j++) { ralloc_asprintf_append(buffer, "\t"); print_type_pre(s->fields.structure[j].type); ralloc_asprintf_append(buffer, " %s", s->fields.structure[j].name); print_type_post(s->fields.structure[j].type); //@todo-rco if (s->fields.structure[j].semantic) { if (!strncmp(s->fields.structure[j].semantic, "ATTRIBUTE", 9)) { ralloc_asprintf_append(buffer, " [[ attribute(%s) ]]", s->fields.structure[j].semantic + 9); } else if (!strncmp(s->fields.structure[j].semantic, "gl_FragDepth", 12)) { ralloc_asprintf_append(buffer, " [[ depth(any) ]]"); } else if (!strncmp(s->fields.structure[j].semantic, "[[", 2)) { ralloc_asprintf_append(buffer, " %s", s->fields.structure[j].semantic); } else { ralloc_asprintf_append(buffer, "[[ ERROR! ]]"); check(0); } } ralloc_asprintf_append(buffer, ";\n"); } } ralloc_asprintf_append(buffer, "};\n\n"); bUsePacked = false; } unsigned num_used_blocks = 0; for (unsigned i = 0; i < state->num_uniform_blocks; i++) { const glsl_uniform_block* block = state->uniform_blocks[i]; if (hash_table_find(used_uniform_blocks, block->name)) { const char* block_name = block->name; check(0); /* if (state->has_packed_uniforms) { block_name = ralloc_asprintf(mem_ctx, "%sb%u", glsl_variable_tag_from_parser_target(state->target), num_used_blocks ); } ralloc_asprintf_append(buffer, "layout(std140) uniform %s\n{\n", block_name); for (unsigned var_index = 0; var_index < block->num_vars; ++var_index) { ir_variable* var = block->vars[var_index]; ralloc_asprintf_append(buffer, "\t"); print_type_pre(var->type); ralloc_asprintf_append(buffer, " %s", var->name); print_type_post(var->type); ralloc_asprintf_append(buffer, ";\n"); } ralloc_asprintf_append(buffer, "};\n\n"); */ num_used_blocks++; } } } void PrintPackedSamplers(_mesa_glsl_parse_state::TUniformList& Samplers, TStringToSetMap& TextureToSamplerMap) { bool bPrintHeader = true; bool bNeedsComma = false; for (_mesa_glsl_parse_state::TUniformList::iterator Iter = Samplers.begin(); Iter != Samplers.end(); ++Iter) { glsl_packed_uniform& Sampler = *Iter; std::string SamplerStates(""); TStringToSetMap::iterator IterFound = TextureToSamplerMap.find(Sampler.Name); if (IterFound != TextureToSamplerMap.end()) { TStringSet& ListSamplerStates = IterFound->second; check(!ListSamplerStates.empty()); for (TStringSet::iterator IterSS = ListSamplerStates.begin(); IterSS != ListSamplerStates.end(); ++IterSS) { if (IterSS == ListSamplerStates.begin()) { SamplerStates += "["; } else { SamplerStates += ","; } SamplerStates += *IterSS; } SamplerStates += "]"; } // Try to find SRV index uint32 Offset = Sampler.offset; for (int i = 0; i < Buffers.Buffers.Num(); ++i) { if (Buffers.Buffers[i]) { auto* Var = Buffers.Buffers[i]->as_variable(); if (!Var->semantic && Var->type->is_sampler() && Var->name && Sampler.CB_PackedSampler == Var->name && Var->type->sampler_buffer) { Offset = i; break; } } } ralloc_asprintf_append( buffer, "%s%s(%u:%u%s)", bNeedsComma ? "," : "", Sampler.Name.c_str(), Offset, Sampler.num_components, SamplerStates.c_str() ); bNeedsComma = true; } } void PrintImages(_mesa_glsl_parse_state::TUniformList& Uniforms) { bool bPrintHeader = true; bool bNeedsComma = false; for (_mesa_glsl_parse_state::TUniformList::iterator Iter = Uniforms.begin(); Iter != Uniforms.end(); ++Iter) { glsl_packed_uniform& Uniform = *Iter; ANSICHAR Name[32]; FCStringAnsi::Sprintf(Name, "%si%d", glsl_variable_tag_from_parser_target(Frequency), Uniform.offset); int32 Offset = Buffers.GetIndex(Name); check(Offset != -1); ralloc_asprintf_append( buffer, "%s%s(%u:%u)", bNeedsComma ? "," : "", Uniform.Name.c_str(), Offset, Uniform.num_components ); bNeedsComma = true; } } void PrintPackedGlobals(_mesa_glsl_parse_state* State) { // @PackedGlobals: Global0(DestArrayType, DestOffset, SizeInFloats), Global1(DestArrayType, DestOffset, SizeInFloats), ... bool bNeedsHeader = true; bool bNeedsComma = false; for (auto ArrayIter = State->GlobalPackedArraysMap.begin(); ArrayIter != State->GlobalPackedArraysMap.end(); ++ArrayIter) { char ArrayType = ArrayIter->first; if (ArrayType != EArrayType_Image && ArrayType != EArrayType_Sampler) { _mesa_glsl_parse_state::TUniformList& Uniforms = ArrayIter->second; check(!Uniforms.empty()); for (auto Iter = Uniforms.begin(); Iter != Uniforms.end(); ++Iter) { glsl_packed_uniform& Uniform = *Iter; if (!State->bFlattenUniformBuffers || Uniform.CB_PackedSampler.empty()) { if (bNeedsHeader) { ralloc_asprintf_append(buffer, "// @PackedGlobals: "); bNeedsHeader = false; } ralloc_asprintf_append( buffer, "%s%s(%c:%u,%u)", bNeedsComma ? "," : "", Uniform.Name.c_str(), ArrayType, Uniform.offset, Uniform.num_components ); bNeedsComma = true; } } } } if (!bNeedsHeader) { ralloc_asprintf_append(buffer, "\n"); } } void PrintPackedUniformBuffers(_mesa_glsl_parse_state* State) { // @PackedUB: UniformBuffer0(SourceIndex0): Member0(SourceOffset,SizeInFloats),Member1(SourceOffset,SizeInFloats), ... // @PackedUB: UniformBuffer1(SourceIndex1): Member0(SourceOffset,SizeInFloats),Member1(SourceOffset,SizeInFloats), ... // ... // First find all used CBs (since we lost that info during flattening) TStringSet UsedCBs; for (auto IterCB = State->CBPackedArraysMap.begin(); IterCB != State->CBPackedArraysMap.end(); ++IterCB) { for (auto Iter = IterCB->second.begin(); Iter != IterCB->second.end(); ++Iter) { _mesa_glsl_parse_state::TUniformList& Uniforms = Iter->second; for (auto IterU = Uniforms.begin(); IterU != Uniforms.end(); ++IterU) { if (!IterU->CB_PackedSampler.empty()) { check(IterCB->first == IterU->CB_PackedSampler); UsedCBs.insert(IterU->CB_PackedSampler); } } } } check(UsedCBs.size() == State->CBPackedArraysMap.size()); // Now get the CB index based off source declaration order, and print an info line for each, while creating the mem copy list unsigned CBIndex = 0; TCBDMARangeMap CBRanges; for (unsigned i = 0; i < State->num_uniform_blocks; i++) { const glsl_uniform_block* block = State->uniform_blocks[i]; if (UsedCBs.find(block->name) != UsedCBs.end()) { bool bNeedsHeader = true; // Now the members for this CB bool bNeedsComma = false; auto IterPackedArrays = State->CBPackedArraysMap.find(block->name); check(IterPackedArrays != State->CBPackedArraysMap.end()); for (auto Iter = IterPackedArrays->second.begin(); Iter != IterPackedArrays->second.end(); ++Iter) { char ArrayType = Iter->first; check(ArrayType != EArrayType_Image && ArrayType != EArrayType_Sampler); _mesa_glsl_parse_state::TUniformList& Uniforms = Iter->second; for (auto IterU = Uniforms.begin(); IterU != Uniforms.end(); ++IterU) { glsl_packed_uniform& Uniform = *IterU; if (Uniform.CB_PackedSampler == block->name) { if (bNeedsHeader) { ralloc_asprintf_append(buffer, "// @PackedUB: %s(%d): ", block->name, CBIndex); bNeedsHeader = false; } ralloc_asprintf_append(buffer, "%s%s(%u,%u)", bNeedsComma ? "," : "", Uniform.Name.c_str(), Uniform.OffsetIntoCBufferInFloats, Uniform.SizeInFloats); bNeedsComma = true; unsigned SourceOffset = Uniform.OffsetIntoCBufferInFloats; unsigned DestOffset = Uniform.offset; unsigned Size = Uniform.SizeInFloats; unsigned DestCBIndex = 0; unsigned DestCBPrecision = ArrayType; InsertRange(CBRanges, CBIndex, SourceOffset, Size, DestCBIndex, DestCBPrecision, DestOffset); } } } if (!bNeedsHeader) { ralloc_asprintf_append(buffer, "\n"); } CBIndex++; } } //DumpSortedRanges(SortRanges(CBRanges)); // @PackedUBCopies: SourceArray:SourceOffset-DestArray:DestOffset,SizeInFloats;SourceArray:SourceOffset-DestArray:DestOffset,SizeInFloats,... bool bFirst = true; for (auto Iter = CBRanges.begin(); Iter != CBRanges.end(); ++Iter) { TDMARangeList& List = Iter->second; for (auto IterList = List.begin(); IterList != List.end(); ++IterList) { if (bFirst) { ralloc_asprintf_append(buffer, "// @PackedUBGlobalCopies: "); bFirst = false; } else { ralloc_asprintf_append(buffer, ","); } check(IterList->DestCBIndex == 0); ralloc_asprintf_append(buffer, "%d:%d-%c:%d:%d", IterList->SourceCB, IterList->SourceOffset, IterList->DestCBPrecision, IterList->DestOffset, IterList->Size); } } if (!bFirst) { ralloc_asprintf_append(buffer, "\n"); } } void PrintPackedUniforms(_mesa_glsl_parse_state* State) { PrintPackedGlobals(State); if (State->bFlattenUniformBuffers && !State->CBuffersOriginal.empty()) { PrintPackedUniformBuffers(State); } } /** * Print a list of external variables. */ void print_extern_vars(_mesa_glsl_parse_state* State, exec_list* extern_vars) { const char *type_str[] = { "u", "i", "f", "f", "b", "t", "?", "?", "?", "?", "s", "os", "im", "ip", "op" }; const char *col_str[] = { "", "", "2x", "3x", "4x" }; const char *row_str[] = { "", "1", "2", "3", "4" }; check( sizeof(type_str)/sizeof(char*) == GLSL_TYPE_MAX); bool need_comma = false; foreach_iter(exec_list_iterator, iter, *extern_vars) { ir_variable* var = ((extern_var*)iter.get())->var; const glsl_type* type = var->type; if (!strcmp(var->name,"gl_in")) { // Ignore it, as we can't properly frame this information in current format, and it's not used anyway for geometry shaders continue; } if (!strncmp(var->name,"in_",3) || !strncmp(var->name,"out_",4)) { if (type->is_record()) { // This is the specific case for GLSL >= 150, as we generate a struct with a member for each interpolator (which we still want to count) if (type->length != 1) { _mesa_glsl_warning(State, "Found a complex structure as in/out, counting is not implemented yet...\n"); continue; } type = type->fields.structure->type; } // In and out variables may be packed in structures, or array of structures. // But we're interested only in those that aren't, ie. inputs for vertex shader and outputs for pixel shader. if (type->is_array() || type->is_record()) { continue; } } bool is_array = type->is_array(); int array_size = is_array ? type->length : 0; if (is_array) { type = type->fields.array; } ralloc_asprintf_append(buffer, "%s%s%s%s", need_comma ? "," : "", type_str[type->base_type], col_str[type->matrix_columns], row_str[type->vector_elements]); if (is_array) { ralloc_asprintf_append(buffer, "[%u]", array_size); } ralloc_asprintf_append(buffer, ":%s", var->name); need_comma = true; } } /** * Print the input/output signature for this shader. */ void print_signature(_mesa_glsl_parse_state *state) { if (!input_variables.is_empty()) { ralloc_asprintf_append(buffer, "// @Inputs: "); print_extern_vars(state, &input_variables); ralloc_asprintf_append(buffer, "\n"); } if (!output_variables.is_empty()) { ralloc_asprintf_append(buffer, "// @Outputs: "); print_extern_vars(state, &output_variables); ralloc_asprintf_append(buffer, "\n"); } if (state->num_uniform_blocks > 0 && !state->bFlattenUniformBuffers) { bool bFirst = true; for (int i = 0; i < Buffers.Buffers.Num(); ++i) { // Some entries might be null, if we used more packed than real UBs used if (Buffers.Buffers[i]) { auto* Var = Buffers.Buffers[i]->as_variable(); if (!Var->semantic && !Var->type->is_sampler() && !Var->type->is_image()) { ralloc_asprintf_append(buffer, "%s%s(%d)", bFirst ? "// @UniformBlocks: " : ",", Var->name, i); bFirst = false; } } } if (!bFirst) { ralloc_asprintf_append(buffer, "\n"); } } if (state->has_packed_uniforms) { PrintPackedUniforms(state); if (!state->GlobalPackedArraysMap[EArrayType_Sampler].empty()) { ralloc_asprintf_append(buffer, "// @Samplers: "); PrintPackedSamplers( state->GlobalPackedArraysMap[EArrayType_Sampler], state->TextureToSamplerMap ); ralloc_asprintf_append(buffer, "\n"); } if (!state->GlobalPackedArraysMap[EArrayType_Image].empty()) { ralloc_asprintf_append(buffer, "// @UAVs: "); PrintImages(state->GlobalPackedArraysMap[EArrayType_Image]); ralloc_asprintf_append(buffer, "\n"); } } else { if (!uniform_variables.is_empty()) { ralloc_asprintf_append(buffer, "// @Uniforms: "); print_extern_vars(state, &uniform_variables); ralloc_asprintf_append(buffer, "\n"); } if (!sampler_variables.is_empty()) { ralloc_asprintf_append(buffer, "// @Samplers: "); print_extern_vars(state, &sampler_variables); ralloc_asprintf_append(buffer, "\n"); } if (!image_variables.is_empty()) { ralloc_asprintf_append(buffer, "// @UAVs: "); print_extern_vars(state, &image_variables); ralloc_asprintf_append(buffer, "\n"); } } if (Buffers.UniqueSamplerStates.Num() > 0) { ralloc_asprintf_append(buffer, "// @SamplerStates: "); for (int32 Index = 0; Index < Buffers.UniqueSamplerStates.Num(); ++Index) { ralloc_asprintf_append(buffer, "%s%d:%s", Index > 0 ? "," : "", Index, Buffers.UniqueSamplerStates[Index].c_str()); } ralloc_asprintf_append(buffer, "\n"); } if (Frequency == compute_shader) { ralloc_asprintf_append(buffer, "// @NumThreads: %d, %d, %d\n", this->NumThreadsX, this->NumThreadsY, this->NumThreadsZ); } } public: /** Constructor. */ FGenerateMetalVisitor(_mesa_glsl_parse_state* InParseState, _mesa_glsl_parser_targets InFrequency, FBuffers& InBuffers) : ParseState(InParseState) , Frequency(InFrequency) , Buffers(InBuffers) , buffer(0) , indentation(0) , scope_depth(0) , temp_id(0) , global_id(0) , needs_semicolon(false) , IsMain(false) , should_print_uint_literals_as_ints(false) , loop_count(0) , bStageInEmitted(false) , bUsePacked(false) , bNeedsComputeInclude(false) { printable_names = hash_table_ctor(32, hash_table_pointer_hash, hash_table_pointer_compare); used_structures = hash_table_ctor(128, hash_table_pointer_hash, hash_table_pointer_compare); used_uniform_blocks = hash_table_ctor(32, hash_table_string_hash, hash_table_string_compare); } /** Destructor. */ virtual ~FGenerateMetalVisitor() { hash_table_dtor(printable_names); hash_table_dtor(used_structures); hash_table_dtor(used_uniform_blocks); } /** * Executes the visitor on the provided ir. * @returns the Metal source code generated. */ const char* run(exec_list* ir) { mem_ctx = ralloc_context(NULL); char* code_buffer = ralloc_asprintf(mem_ctx, ""); buffer = &code_buffer; foreach_iter(exec_list_iterator, iter, *ir) { ir_instruction *inst = (ir_instruction *)iter.get(); do_visit(inst); } buffer = 0; char* decl_buffer = ralloc_asprintf(mem_ctx, ""); buffer = &decl_buffer; declare_structs(ParseState); buffer = 0; char* signature = ralloc_asprintf(mem_ctx, ""); buffer = &signature; print_signature(ParseState); buffer = 0; char* full_buffer = ralloc_asprintf( ParseState, "// Compiled by HLSLCC\n%s\n#include \n%s\nusing namespace metal;\n\n%s%s", signature, bNeedsComputeInclude ? "#include " : "", decl_buffer, code_buffer ); ralloc_free(mem_ctx); return full_buffer; } }; char* FMetalCodeBackend::GenerateCode(exec_list* ir, _mesa_glsl_parse_state* state, EHlslShaderFrequency Frequency) { // We'll need this Buffers info for the [[buffer()]] index FBuffers Buffers; FGenerateMetalVisitor visitor(state, state->target, Buffers); // At this point, all inputs and outputs are global uniforms, no structures. // Promotes all inputs from half to float to avoid stage_in issues PromoteInputsAndOutputsGlobalHalfToFloat(ir, state, Frequency); // Move all inputs & outputs to structs for Metal PackInputsAndOutputs(ir, state, Frequency, visitor.input_variables); // ir_var_uniform instances be global, so move them as arguments to main MovePackedUniformsToMain(ir, state, Buffers); //@todo-rco: Do we need this here? ExpandArrayAssignments(ir, state); // Fix any special language extensions (FrameBufferFetchES2() intrinsic) FixIntrinsics(ir, state); // Remove half->float->half or float->half->float FixRedundantCasts(ir); if (!OptimizeAndValidate(ir, state)) { return nullptr; } // Do not call Optimize() after this! { // Metal can't do implicit conversions between half<->float during math expressions BreakPrecisionChangesVisitor(ir, state); // Metal can't read from a packed_* type, which for us come from a constant buffer //@todo-rco: Might not work if accessing packed_half* m[N]! RemovePackedVarReferences(ir, state); bool bConvertUniformsToFloats = (HlslCompileFlags & HLSLCC_FlattenUniformBuffers) != HLSLCC_FlattenUniformBuffers; ConvertHalfToFloatUniformsAndSamples(ir, state, bConvertUniformsToFloats, true); Validate(ir, state); } // Generate the actual code string const char* code = visitor.run(ir); return _strdup(code); } struct FMetalUnsupportedSamplerVisitor : public ir_hierarchical_visitor { _mesa_glsl_parse_state* ParseState; const bool bIsVertexShader; FMetalUnsupportedSamplerVisitor(_mesa_glsl_parse_state* InParseState, bool bInIsVertexShader) : ParseState(InParseState), bIsVertexShader(bInIsVertexShader) { } virtual ir_visitor_status visit_leave(ir_texture* IR) override { return visit_continue; } }; bool FMetalCodeBackend::ApplyAndVerifyPlatformRestrictions(exec_list* Instructions, _mesa_glsl_parse_state* ParseState, EHlslShaderFrequency Frequency) { bool bIsVertexShader = (Frequency == HSF_VertexShader); const bool bAllowVertexTextureFetch = true; if (!bAllowVertexTextureFetch) { FMetalUnsupportedSamplerVisitor Visitor(ParseState, bIsVertexShader); Visitor.run(Instructions); } return true; } bool FMetalCodeBackend::GenerateMain(EHlslShaderFrequency Frequency, const char* EntryPoint, exec_list* Instructions, _mesa_glsl_parse_state* ParseState) { auto* EntryPointSig = FindEntryPointFunction(Instructions, ParseState, EntryPoint); if (!EntryPointSig) { _mesa_glsl_error(ParseState, "shader entry point '%s' not found", EntryPoint); return false; } exec_list DeclInstructions; exec_list PreCallInstructions; exec_list ArgInstructions; exec_list PostCallInstructions; ParseState->symbols->push_scope(); // Find all system semantics and generate in/out globals foreach_iter(exec_list_iterator, Iter, EntryPointSig->parameters) { const ir_variable* Variable = (ir_variable*)Iter.get(); if (Variable->semantic != NULL || Variable->type->is_record()) { ir_dereference_variable* ArgVarDeref = NULL; switch (Variable->mode) { case ir_var_in: ArgVarDeref = MetalUtils::GenerateInput( Frequency, ParseState, Variable->semantic, Variable->type, &DeclInstructions, &PreCallInstructions ); break; case ir_var_out: ArgVarDeref = MetalUtils::GenerateOutput( Frequency, ParseState, Variable->semantic, Variable->type, &DeclInstructions, &PreCallInstructions, &PostCallInstructions ); break; default: _mesa_glsl_error( ParseState, "entry point parameter '%s' must be an input or output", Variable->name ); } ArgInstructions.push_tail(ArgVarDeref); } } // The function's return value should have an output semantic if it's not void. ir_dereference_variable* EntryPointReturn = nullptr; if (!EntryPointSig->return_type->is_void()) { EntryPointReturn = MetalUtils::GenerateOutput(Frequency, ParseState, EntryPointSig->return_semantic, EntryPointSig->return_type, &DeclInstructions, &PreCallInstructions, &PostCallInstructions); } ParseState->symbols->pop_scope(); // Generate the Main() function signature ir_function_signature* MainSig = new(ParseState) ir_function_signature(glsl_type::void_type); MainSig->is_defined = true; MainSig->is_main = true; MainSig->body.append_list(&PreCallInstructions); // Call the original EntryPoint MainSig->body.push_tail(new(ParseState) ir_call(EntryPointSig, EntryPointReturn, &ArgInstructions)); MainSig->body.append_list(&PostCallInstructions); MainSig->wg_size_x = EntryPointSig->wg_size_x; MainSig->wg_size_y = EntryPointSig->wg_size_y; MainSig->wg_size_z = EntryPointSig->wg_size_z; // Generate the Main() function auto* MainFunction = new(ParseState)ir_function("Main"); MainFunction->add_signature(MainSig); // Adds uniforms as globals Instructions->append_list(&DeclInstructions); Instructions->push_tail(MainFunction); // Now that we have a proper Main(), move global setup to Main(). MoveGlobalInstructionsToMain(Instructions); //IRDump(Instructions); return true; } FMetalCodeBackend::FMetalCodeBackend(unsigned int InHlslCompileFlags) : FCodeBackend(InHlslCompileFlags) { } void FMetalLanguageSpec::SetupLanguageIntrinsics(_mesa_glsl_parse_state* State, exec_list* ir) { // Framebuffer fetch { // Leave original fb ES2 fetch function as that's what the hlsl expects make_intrinsic_genType(ir, State, FRAMEBUFFER_FETCH_ES2, ir_invalid_opcode, IR_INTRINSIC_HALF, 0, 4, 4); // MRTs; first make intrinsics for each MRT, then a non-intrinsic version to use that (helps when converting to Metal) for (int i = 0; i < MAX_SIMULTANEOUS_RENDER_TARGETS; ++i) { char FunctionName[32]; #if PLATFORM_WINDOWS sprintf_s(FunctionName, 32, "%s%d", FRAMEBUFFER_FETCH_MRT, i); #else sprintf(FunctionName, "%s%d", FRAMEBUFFER_FETCH_MRT, i); #endif make_intrinsic_genType(ir, State, FunctionName, ir_invalid_opcode, IR_INTRINSIC_HALF, 0, 4, 4); } const auto* ReturnType = glsl_type::get_instance(GLSL_TYPE_HALF, 4, 1); ir_function* Func = new(State) ir_function(FRAMEBUFFER_FETCH_MRT); ir_function_signature* Sig = new(State) ir_function_signature(ReturnType); //Sig->is_builtin = true; Sig->is_defined = true; ir_variable* MRTIndex = new(State) ir_variable(glsl_type::int_type, "Arg0", ir_var_in); Sig->parameters.push_tail(MRTIndex); for (int i = 0; i < MAX_SIMULTANEOUS_RENDER_TARGETS; ++i) { // Inject: // if (Arg0 == i) FRAMEBUFFER_FETCH_MRT#i(); auto* Condition = new(State) ir_expression(ir_binop_equal, new(State) ir_dereference_variable((ir_variable*)Sig->parameters.get_head()), new(State) ir_constant(i)); auto* If = new(State) ir_if(Condition); char FunctionName[32]; #if PLATFORM_WINDOWS sprintf_s(FunctionName, 32, "%s%d", FRAMEBUFFER_FETCH_MRT, i); #else sprintf(FunctionName, "%s%d", FRAMEBUFFER_FETCH_MRT, i); #endif auto* IntrinsicSig = FCodeBackend::FindEntryPointFunction(ir, State, FunctionName); auto* ReturnValue = new(State) ir_variable(ReturnType, nullptr, ir_var_temporary); exec_list Empty; auto* Call = new(State) ir_call(IntrinsicSig, new(State) ir_dereference_variable(ReturnValue), &Empty); Call->use_builtin = true; If->then_instructions.push_tail(ReturnValue); If->then_instructions.push_tail(Call); If->then_instructions.push_tail(new(State) ir_return(new(State) ir_dereference_variable(ReturnValue))); Sig->body.push_tail(If); } Func->add_signature(Sig); State->symbols->add_global_function(Func); ir->push_tail(Func); } // Memory sync/barriers { make_intrinsic_genType(ir, State, GROUP_MEMORY_BARRIER, ir_invalid_opcode, 0, 0, 0, 0); make_intrinsic_genType(ir, State, GROUP_MEMORY_BARRIER_WITH_GROUP_SYNC, ir_invalid_opcode, 0, 0, 0, 0); make_intrinsic_genType(ir, State, DEVICE_MEMORY_BARRIER, ir_invalid_opcode, 0, 0, 0, 0); make_intrinsic_genType(ir, State, DEVICE_MEMORY_BARRIER_WITH_GROUP_SYNC, ir_invalid_opcode, 0, 0, 0, 0); make_intrinsic_genType(ir, State, ALL_MEMORY_BARRIER, ir_invalid_opcode, 0, 0, 0, 0); make_intrinsic_genType(ir, State, ALL_MEMORY_BARRIER_WITH_GROUP_SYNC, ir_invalid_opcode, 0, 0, 0, 0); } }