Path: blob/master/dep/reshadefx/src/effect_parser_stmt.cpp
4246 views
/*1* Copyright (C) 2014 Patrick Mours2* SPDX-License-Identifier: BSD-3-Clause3*/45#include "effect_lexer.hpp"6#include "effect_parser.hpp"7#include "effect_codegen.hpp"8#include <cctype> // std::toupper9#include <cassert>10#include <climits>11#include <algorithm> // std::max, std::replace, std::transform12#include <limits>13#include <string_view>1415template <typename ENTER_TYPE, typename LEAVE_TYPE>16struct scope_guard17{18explicit scope_guard(ENTER_TYPE &&enter_lambda, LEAVE_TYPE &&leave_lambda) :19leave_lambda(std::forward<LEAVE_TYPE>(leave_lambda)) { enter_lambda(); }20~scope_guard() { leave_lambda(); }2122private:23LEAVE_TYPE leave_lambda;24};2526bool reshadefx::parser::parse(std::string input, codegen *backend)27{28_lexer = std::make_unique<lexer>(std::move(input));2930// Set backend for subsequent code-generation31_codegen = backend;32assert(backend != nullptr);3334consume();3536bool parse_success = true;37bool current_success = true;3839while (!peek(tokenid::end_of_file))40{41if (!parse_top(current_success))42return false;43if (!current_success)44parse_success = false;45}4647if (parse_success)48backend->optimize_bindings();4950return parse_success;51}5253bool reshadefx::parser::parse_top(bool &parse_success)54{55if (accept(tokenid::namespace_))56{57// Anonymous namespaces are not supported right now, so an identifier is a must58if (!expect(tokenid::identifier))59return false;6061const std::string name = std::move(_token.literal_as_string);6263if (!expect('{'))64return false;6566enter_namespace(name);6768bool current_success = true;69bool parse_success_namespace = true;7071// Recursively parse top level statements until the namespace is closed again72while (!peek('}')) // Empty namespaces are valid73{74if (!parse_top(current_success))75return false;76if (!current_success)77parse_success_namespace = false;78}7980leave_namespace();8182parse_success = expect('}') && parse_success_namespace;83}84else if (accept(tokenid::struct_)) // Structure keyword found, parse the structure definition85{86// Structure definitions are terminated with a semicolon87parse_success = parse_struct() && expect(';');88}89else if (accept(tokenid::technique)) // Technique keyword found, parse the technique definition90{91parse_success = parse_technique();92}93else94{95location attribute_location;96shader_type stype = shader_type::unknown;97int num_threads[3] = { 0, 0, 0 };9899// Read any function attributes first100while (accept('['))101{102if (!expect(tokenid::identifier))103return false;104105const std::string attribute = std::move(_token.literal_as_string);106107if (attribute == "shader")108{109attribute_location = _token_next.location;110111if (!expect('(') || !expect(tokenid::string_literal))112return false;113114if (_token.literal_as_string == "vertex")115stype = shader_type::vertex;116else if (_token.literal_as_string == "pixel")117stype = shader_type::pixel;118else if (_token.literal_as_string == "compute")119stype = shader_type::compute;120121if (!expect(')'))122return false;123}124else if (attribute == "numthreads")125{126attribute_location = _token_next.location;127128expression x, y, z;129if (!expect('(') || !parse_expression_multary(x, 8) || !expect(',') || !parse_expression_multary(y, 8) || !expect(',') || !parse_expression_multary(z, 8) || !expect(')'))130return false;131132if (!x.is_constant)133{134error(x.location, 3011, "value must be a literal expression");135parse_success = false;136}137if (!y.is_constant)138{139error(y.location, 3011, "value must be a literal expression");140parse_success = false;141}142if (!z.is_constant)143{144error(z.location, 3011, "value must be a literal expression");145parse_success = false;146}147x.add_cast_operation({ type::t_int, 1, 1 });148y.add_cast_operation({ type::t_int, 1, 1 });149z.add_cast_operation({ type::t_int, 1, 1 });150num_threads[0] = x.constant.as_int[0];151num_threads[1] = y.constant.as_int[0];152num_threads[2] = z.constant.as_int[0];153}154else155{156warning(_token.location, 0, "unknown attribute '" + attribute + "'");157}158159if (!expect(']'))160return false;161}162163if (type type = {}; parse_type(type)) // Type found, this can be either a variable or a function declaration164{165parse_success = expect(tokenid::identifier);166if (!parse_success)167return true;168169if (peek('('))170{171const std::string name = std::move(_token.literal_as_string);172173// This is definitely a function declaration, so parse it174if (!parse_function(type, name, stype, num_threads))175{176// Insert dummy function into symbol table, so later references can be resolved despite the error177insert_symbol(name, { symbol_type::function, UINT32_MAX, { type::t_function } }, true);178parse_success = false;179return true;180}181}182else183{184if (!attribute_location.source.empty())185{186error(attribute_location, 0, "attribute is valid only on functions");187parse_success = false;188}189190// There may be multiple variable names after the type, handle them all191unsigned int count = 0;192do193{194if (count++ > 0 && !(expect(',') && expect(tokenid::identifier)))195{196parse_success = false;197return false;198}199200const std::string name = std::move(_token.literal_as_string);201202if (!parse_variable(type, name, true))203{204// Insert dummy variable into symbol table, so later references can be resolved despite the error205insert_symbol(name, { symbol_type::variable, UINT32_MAX, type }, true);206// Skip the rest of the statement207consume_until(';');208parse_success = false;209return true;210}211}212while (!peek(';'));213214// Variable declarations are terminated with a semicolon215parse_success = expect(';');216}217}218else if (accept(';')) // Ignore single semicolons in the source219{220parse_success = true;221}222else223{224// Unexpected token in source stream, consume and report an error about it225consume();226// Only add another error message if succeeded parsing previously227// This is done to avoid walls of error messages because of consequential errors following a top-level syntax mistake228if (parse_success)229error(_token.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token.id) + '\'');230parse_success = false;231}232}233234return true;235}236237bool reshadefx::parser::parse_statement(bool scoped)238{239if (!_codegen->is_in_block())240{241error(_token_next.location, 0, "unreachable code");242return false;243}244245unsigned int loop_control = 0;246unsigned int selection_control = 0;247248// Read any loop and branch control attributes first249while (accept('['))250{251enum control_mask252{253unroll = 0x1,254dont_unroll = 0x2,255flatten = (0x1 << 4),256dont_flatten = (0x2 << 4),257switch_force_case = (0x4 << 4),258switch_call = (0x8 << 4)259};260261const std::string attribute = std::move(_token_next.literal_as_string);262263if (!expect(tokenid::identifier) || !expect(']'))264return false;265266if (attribute == "unroll")267loop_control |= unroll;268else if (attribute == "loop" || attribute == "fastopt")269loop_control |= dont_unroll;270else if (attribute == "flatten")271selection_control |= flatten;272else if (attribute == "branch")273selection_control |= dont_flatten;274else if (attribute == "forcecase")275selection_control |= switch_force_case;276else if (attribute == "call")277selection_control |= switch_call;278else279warning(_token.location, 0, "unknown attribute '" + attribute + "'");280281if ((loop_control & (unroll | dont_unroll)) == (unroll | dont_unroll))282{283error(_token.location, 3524, "can't use loop and unroll attributes together");284return false;285}286if ((selection_control & (flatten | dont_flatten)) == (flatten | dont_flatten))287{288error(_token.location, 3524, "can't use branch and flatten attributes together");289return false;290}291}292293// Shift by two so that the possible values are 0x01 for 'flatten' and 0x02 for 'dont_flatten', equivalent to 'unroll' and 'dont_unroll'294selection_control >>= 4;295296if (peek('{')) // Parse statement block297return parse_statement_block(scoped);298299if (accept(';')) // Ignore empty statements300return true;301302// Most statements with the exception of declarations are only valid inside functions303if (_codegen->is_in_function())304{305const location statement_location = _token_next.location;306307if (accept(tokenid::if_))308{309codegen::id true_block = _codegen->create_block(); // Block which contains the statements executed when the condition is true310codegen::id false_block = _codegen->create_block(); // Block which contains the statements executed when the condition is false311const codegen::id merge_block = _codegen->create_block(); // Block that is executed after the branch re-merged with the current control flow312313expression condition_exp;314if (!expect('(') || !parse_expression(condition_exp) || !expect(')'))315return false;316317if (!condition_exp.type.is_scalar())318{319error(condition_exp.location, 3019, "if statement conditional expressions must evaluate to a scalar");320return false;321}322323// Load condition and convert to boolean value as required by 'OpBranchConditional' in SPIR-V324condition_exp.add_cast_operation({ type::t_bool, 1, 1 });325326const codegen::id condition_value = _codegen->emit_load(condition_exp);327const codegen::id condition_block = _codegen->leave_block_and_branch_conditional(condition_value, true_block, false_block);328329{ // Then block of the if statement330_codegen->enter_block(true_block);331332if (!parse_statement(true))333return false;334335true_block = _codegen->leave_block_and_branch(merge_block);336}337{ // Else block of the if statement338_codegen->enter_block(false_block);339340if (accept(tokenid::else_) && !parse_statement(true))341return false;342343false_block = _codegen->leave_block_and_branch(merge_block);344}345346_codegen->enter_block(merge_block);347348// Emit structured control flow for an if statement and connect all basic blocks349_codegen->emit_if(statement_location, condition_value, condition_block, true_block, false_block, selection_control);350351return true;352}353354if (accept(tokenid::switch_))355{356const codegen::id merge_block = _codegen->create_block(); // Block that is executed after the switch re-merged with the current control flow357358expression selector_exp;359if (!expect('(') || !parse_expression(selector_exp) || !expect(')'))360return false;361362if (!selector_exp.type.is_scalar())363{364error(selector_exp.location, 3019, "switch statement expression must evaluate to a scalar");365return false;366}367368// Load selector and convert to integral value as required by switch instruction369selector_exp.add_cast_operation({ type::t_int, 1, 1 });370371const codegen::id selector_value = _codegen->emit_load(selector_exp);372const codegen::id selector_block = _codegen->leave_block_and_switch(selector_value, merge_block);373374if (!expect('{'))375return false;376377scope_guard _(378[this, merge_block]() {379_loop_break_target_stack.push_back(merge_block);380},381[this]() {382_loop_break_target_stack.pop_back();383});384385bool parse_success = true;386// The default case jumps to the end of the switch statement if not overwritten387codegen::id default_label = merge_block, default_block = merge_block;388codegen::id current_label = _codegen->create_block();389std::vector<codegen::id> case_literal_and_labels, case_blocks;390size_t last_case_label_index = 0;391392// Enter first switch statement body block393_codegen->enter_block(current_label);394395while (!peek(tokenid::end_of_file))396{397while (accept(tokenid::case_) || accept(tokenid::default_))398{399if (_token.id == tokenid::case_)400{401expression case_label;402if (!parse_expression(case_label))403{404consume_until('}');405return false;406}407408if (!case_label.type.is_scalar() || !case_label.type.is_integral() || !case_label.is_constant)409{410error(case_label.location, 3020, "invalid type for case expression - value must be an integer scalar");411consume_until('}');412return false;413}414415// Check for duplicate case values416for (size_t i = 0; i < case_literal_and_labels.size(); i += 2)417{418if (case_literal_and_labels[i] == case_label.constant.as_uint[0])419{420parse_success = false;421error(case_label.location, 3532, "duplicate case " + std::to_string(case_label.constant.as_uint[0]));422break;423}424}425426case_blocks.emplace_back(); // This is set to the actual block below427case_literal_and_labels.push_back(case_label.constant.as_uint[0]);428case_literal_and_labels.push_back(current_label);429}430else431{432// Check if the default label was already changed by a previous 'default' statement433if (default_label != merge_block)434{435parse_success = false;436error(_token.location, 3532, "duplicate default in switch statement");437}438439default_label = current_label;440default_block = 0; // This is set to the actual block below441}442443if (!expect(':'))444{445consume_until('}');446return false;447}448}449450// It is valid for no statement to follow if this is the last label in the switch body451const bool end_of_switch = peek('}');452453if (!end_of_switch && !parse_statement(true))454{455consume_until('}');456return false;457}458459// Handle fall-through case and end of switch statement460if (peek(tokenid::case_) || peek(tokenid::default_) || end_of_switch)461{462if (_codegen->is_in_block()) // Disallow fall-through for now463{464parse_success = false;465error(_token_next.location, 3533, "non-empty case statements must have break or return");466}467468const codegen::id next_label = end_of_switch ? merge_block : _codegen->create_block();469// This is different from 'current_label', since there may have been branching logic inside the case, which would have changed the active block470const codegen::id current_block = _codegen->leave_block_and_branch(next_label);471472if (0 == default_block)473default_block = current_block;474for (size_t i = last_case_label_index; i < case_blocks.size(); ++i)475// Need to use the initial label for the switch table, but the current block to merge all the block data476case_blocks[i] = current_block;477478current_label = next_label;479_codegen->enter_block(current_label);480481if (end_of_switch) // We reached the end, nothing more to do482break;483484last_case_label_index = case_blocks.size();485}486}487488if (case_literal_and_labels.empty() && default_label == merge_block)489warning(statement_location, 5002, "switch statement contains no 'case' or 'default' labels");490491// Emit structured control flow for a switch statement and connect all basic blocks492_codegen->emit_switch(statement_location, selector_value, selector_block, default_label, default_block, case_literal_and_labels, case_blocks, selection_control);493494return expect('}') && parse_success;495}496497if (accept(tokenid::for_))498{499if (!expect('('))500return false;501502scope_guard _(503[this]() { enter_scope(); },504[this]() { leave_scope(); });505506// Parse initializer first507if (type type = {}; parse_type(type))508{509unsigned int count = 0;510do511{512// There may be multiple declarations behind a type, so loop through them513if (count++ > 0 && !expect(','))514return false;515516if (!expect(tokenid::identifier) || !parse_variable(type, std::move(_token.literal_as_string)))517return false;518}519while (!peek(';'));520}521else522{523// Initializer can also contain an expression if not a variable declaration list and not empty524if (!peek(';'))525{526expression initializer_exp;527if (!parse_expression(initializer_exp))528return false;529}530}531532if (!expect(';'))533return false;534535const codegen::id merge_block = _codegen->create_block(); // Block that is executed after the loop536const codegen::id header_label = _codegen->create_block(); // Pointer to the loop merge instruction537const codegen::id continue_label = _codegen->create_block(); // Pointer to the continue block538codegen::id loop_block = _codegen->create_block(); // Pointer to the main loop body block539codegen::id condition_block = _codegen->create_block(); // Pointer to the condition check540codegen::id condition_value = 0;541542// End current block by branching to the next label543const codegen::id prev_block = _codegen->leave_block_and_branch(header_label);544545{ // Begin loop block (this header is used for explicit structured control flow)546_codegen->enter_block(header_label);547548_codegen->leave_block_and_branch(condition_block);549}550551{ // Parse condition block552_codegen->enter_block(condition_block);553554if (!peek(';'))555{556expression condition_exp;557if (!parse_expression(condition_exp))558return false;559560if (!condition_exp.type.is_scalar())561{562error(condition_exp.location, 3019, "scalar value expected");563return false;564}565566// Evaluate condition and branch to the right target567condition_exp.add_cast_operation({ type::t_bool, 1, 1 });568569condition_value = _codegen->emit_load(condition_exp);570condition_block = _codegen->leave_block_and_branch_conditional(condition_value, loop_block, merge_block);571}572else // It is valid for there to be no condition expression573{574condition_block = _codegen->leave_block_and_branch(loop_block);575}576577if (!expect(';'))578return false;579}580581{ // Parse loop continue block into separate block so it can be appended to the end down the line582_codegen->enter_block(continue_label);583584if (!peek(')'))585{586expression continue_exp;587if (!parse_expression(continue_exp))588return false;589}590591if (!expect(')'))592return false;593594// Branch back to the loop header at the end of the continue block595_codegen->leave_block_and_branch(header_label);596}597598{ // Parse loop body block599_codegen->enter_block(loop_block);600601_loop_break_target_stack.push_back(merge_block);602_loop_continue_target_stack.push_back(continue_label);603604const bool parse_success = parse_statement(false);605606_loop_break_target_stack.pop_back();607_loop_continue_target_stack.pop_back();608609if (!parse_success)610return false;611612loop_block = _codegen->leave_block_and_branch(continue_label);613}614615// Add merge block label to the end of the loop616_codegen->enter_block(merge_block);617618// Emit structured control flow for a loop statement and connect all basic blocks619_codegen->emit_loop(statement_location, condition_value, prev_block, header_label, condition_block, loop_block, continue_label, loop_control);620621return true;622}623624if (accept(tokenid::while_))625{626scope_guard _(627[this]() { enter_scope(); },628[this]() { leave_scope(); });629630const codegen::id merge_block = _codegen->create_block();631const codegen::id header_label = _codegen->create_block();632const codegen::id continue_label = _codegen->create_block();633codegen::id loop_block = _codegen->create_block();634codegen::id condition_block = _codegen->create_block();635codegen::id condition_value = 0;636637// End current block by branching to the next label638const codegen::id prev_block = _codegen->leave_block_and_branch(header_label);639640{ // Begin loop block641_codegen->enter_block(header_label);642643_codegen->leave_block_and_branch(condition_block);644}645646{ // Parse condition block647_codegen->enter_block(condition_block);648649expression condition_exp;650if (!expect('(') || !parse_expression(condition_exp) || !expect(')'))651return false;652653if (!condition_exp.type.is_scalar())654{655error(condition_exp.location, 3019, "scalar value expected");656return false;657}658659// Evaluate condition and branch to the right target660condition_exp.add_cast_operation({ type::t_bool, 1, 1 });661662condition_value = _codegen->emit_load(condition_exp);663condition_block = _codegen->leave_block_and_branch_conditional(condition_value, loop_block, merge_block);664}665666{ // Parse loop body block667_codegen->enter_block(loop_block);668669_loop_break_target_stack.push_back(merge_block);670_loop_continue_target_stack.push_back(continue_label);671672const bool parse_success = parse_statement(false);673674_loop_break_target_stack.pop_back();675_loop_continue_target_stack.pop_back();676677if (!parse_success)678return false;679680loop_block = _codegen->leave_block_and_branch(continue_label);681}682683{ // Branch back to the loop header in empty continue block684_codegen->enter_block(continue_label);685686_codegen->leave_block_and_branch(header_label);687}688689// Add merge block label to the end of the loop690_codegen->enter_block(merge_block);691692// Emit structured control flow for a loop statement and connect all basic blocks693_codegen->emit_loop(statement_location, condition_value, prev_block, header_label, condition_block, loop_block, continue_label, loop_control);694695return true;696}697698if (accept(tokenid::do_))699{700const codegen::id merge_block = _codegen->create_block();701const codegen::id header_label = _codegen->create_block();702const codegen::id continue_label = _codegen->create_block();703codegen::id loop_block = _codegen->create_block();704codegen::id condition_value = 0;705706// End current block by branching to the next label707const codegen::id prev_block = _codegen->leave_block_and_branch(header_label);708709{ // Begin loop block710_codegen->enter_block(header_label);711712_codegen->leave_block_and_branch(loop_block);713}714715{ // Parse loop body block716_codegen->enter_block(loop_block);717718_loop_break_target_stack.push_back(merge_block);719_loop_continue_target_stack.push_back(continue_label);720721const bool parse_success = parse_statement(true);722723_loop_break_target_stack.pop_back();724_loop_continue_target_stack.pop_back();725726if (!parse_success)727return false;728729loop_block = _codegen->leave_block_and_branch(continue_label);730}731732{ // Continue block does the condition evaluation733_codegen->enter_block(continue_label);734735expression condition_exp;736if (!expect(tokenid::while_) || !expect('(') || !parse_expression(condition_exp) || !expect(')') || !expect(';'))737return false;738739if (!condition_exp.type.is_scalar())740{741error(condition_exp.location, 3019, "scalar value expected");742return false;743}744745// Evaluate condition and branch to the right target746condition_exp.add_cast_operation({ type::t_bool, 1, 1 });747748condition_value = _codegen->emit_load(condition_exp);749750_codegen->leave_block_and_branch_conditional(condition_value, header_label, merge_block);751}752753// Add merge block label to the end of the loop754_codegen->enter_block(merge_block);755756// Emit structured control flow for a loop statement and connect all basic blocks757_codegen->emit_loop(statement_location, condition_value, prev_block, header_label, 0, loop_block, continue_label, loop_control);758759return true;760}761762if (accept(tokenid::break_))763{764if (_loop_break_target_stack.empty())765{766error(statement_location, 3518, "break must be inside loop");767return false;768}769770// Branch to the break target of the inner most loop on the stack771_codegen->leave_block_and_branch(_loop_break_target_stack.back(), 1);772773return expect(';');774}775776if (accept(tokenid::continue_))777{778if (_loop_continue_target_stack.empty())779{780error(statement_location, 3519, "continue must be inside loop");781return false;782}783784// Branch to the continue target of the inner most loop on the stack785_codegen->leave_block_and_branch(_loop_continue_target_stack.back(), 2);786787return expect(';');788}789790if (accept(tokenid::return_))791{792const type &return_type = _codegen->_current_function->return_type;793794if (!peek(';'))795{796expression return_exp;797if (!parse_expression(return_exp))798{799consume_until(';');800return false;801}802803// Cannot return to void804if (return_type.is_void())805{806error(statement_location, 3079, "void functions cannot return a value");807// Consume the semicolon that follows the return expression so that parsing may continue808accept(';');809return false;810}811812// Cannot return arrays from a function813if (return_exp.type.is_array() || !type::rank(return_exp.type, return_type))814{815error(statement_location, 3017, "expression (" + return_exp.type.description() + ") does not match function return type (" + return_type.description() + ')');816accept(';');817return false;818}819820// Load return value and perform implicit cast to function return type821if (return_exp.type.components() > return_type.components())822warning(return_exp.location, 3206, "implicit truncation of vector type");823824return_exp.add_cast_operation(return_type);825826const codegen::id return_value = _codegen->emit_load(return_exp);827828_codegen->leave_block_and_return(return_value);829}830else if (!return_type.is_void())831{832// No return value was found, but the function expects one833error(statement_location, 3080, "function must return a value");834835// Consume the semicolon that follows the return expression so that parsing may continue836accept(';');837838return false;839}840else841{842_codegen->leave_block_and_return();843}844845return expect(';');846}847848if (accept(tokenid::discard_))849{850// Leave the current function block851_codegen->leave_block_and_kill();852853return expect(';');854}855}856857// Handle variable declarations858if (type type = {}; parse_type(type))859{860unsigned int count = 0;861do862{863// There may be multiple declarations behind a type, so loop through them864if (count++ > 0 && !expect(','))865{866// Try to consume the rest of the declaration so that parsing may continue despite the error867consume_until(';');868return false;869}870871if (!expect(tokenid::identifier) || !parse_variable(type, std::move(_token.literal_as_string)))872{873consume_until(';');874return false;875}876}877while (!peek(';'));878879return expect(';');880}881882// Handle expression statements883expression statement_exp;884if (parse_expression(statement_exp))885return expect(';'); // A statement has to be terminated with a semicolon886887// Gracefully consume any remaining characters until the statement would usually end, so that parsing may continue despite the error888consume_until(';');889890return false;891}892bool reshadefx::parser::parse_statement_block(bool scoped)893{894if (!expect('{'))895return false;896897if (scoped)898enter_scope();899900// Parse statements until the end of the block is reached901while (!peek('}') && !peek(tokenid::end_of_file))902{903if (!parse_statement(true))904{905if (scoped)906leave_scope();907908// Ignore the rest of this block909unsigned int level = 0;910911while (!peek(tokenid::end_of_file))912{913if (accept('{'))914{915++level;916}917else if (accept('}'))918{919if (level-- == 0)920break;921} // These braces are necessary to match the 'else' to the correct 'if' statement922else923{924consume();925}926}927928return false;929}930}931932if (scoped)933leave_scope();934935return expect('}');936}937938bool reshadefx::parser::parse_type(type &type)939{940type.qualifiers = 0;941accept_type_qualifiers(type);942943if (!accept_type_class(type))944return false;945946if (type.is_integral() && (type.has(type::q_centroid) || type.has(type::q_noperspective)))947{948error(_token.location, 4576, "signature specifies invalid interpolation mode for integer component type");949return false;950}951952if (type.has(type::q_centroid) && !type.has(type::q_noperspective))953type.qualifiers |= type::q_linear;954955return true;956}957bool reshadefx::parser::parse_array_length(type &type)958{959// Reset array length to zero before checking if one exists960type.array_length = 0;961962if (accept('['))963{964if (accept(']'))965{966// No length expression, so this is an unbounded array967type.array_length = 0xFFFFFFFF;968}969else if (expression length_exp; parse_expression(length_exp) && expect(']'))970{971if (!length_exp.is_constant || !(length_exp.type.is_scalar() && length_exp.type.is_integral()))972{973error(length_exp.location, 3058, "array dimensions must be literal scalar expressions");974return false;975}976977type.array_length = length_exp.constant.as_uint[0];978979if (type.array_length < 1 || type.array_length > 65536)980{981error(length_exp.location, 3059, "array dimension must be between 1 and 65536");982return false;983}984}985else986{987return false;988}989}990991// Multi-dimensional arrays are not supported992if (peek('['))993{994error(_token_next.location, 3119, "arrays cannot be multi-dimensional");995return false;996}997998return true;999}10001001bool reshadefx::parser::parse_annotations(std::vector<annotation> &annotations)1002{1003// Check if annotations exist and return early if none do1004if (!accept('<'))1005return true;10061007bool parse_success = true;10081009while (!peek('>'))1010{1011if (type type /* = {} */; accept_type_class(type))1012warning(_token.location, 4717, "type prefixes for annotations are deprecated and ignored");10131014if (!expect(tokenid::identifier))1015{1016consume_until('>');1017return false;1018}10191020std::string name = std::move(_token.literal_as_string);10211022expression annotation_exp;1023if (!expect('=') || !parse_expression_multary(annotation_exp) || !expect(';'))1024{1025consume_until('>');1026return false;1027}10281029if (annotation_exp.is_constant)1030{1031annotations.push_back({ annotation_exp.type, std::move(name), std::move(annotation_exp.constant) });1032}1033else // Continue parsing annotations despite this not being a constant, since the syntax is still correct1034{1035parse_success = false;1036error(annotation_exp.location, 3011, "value must be a literal expression");1037}1038}10391040return expect('>') && parse_success;1041}10421043bool reshadefx::parser::parse_struct()1044{1045const location struct_location = std::move(_token.location);10461047struct_type info;1048// The structure name is optional1049if (accept(tokenid::identifier))1050info.name = std::move(_token.literal_as_string);1051else1052info.name = "_anonymous_struct_" + std::to_string(struct_location.line) + '_' + std::to_string(struct_location.column);10531054info.unique_name = 'S' + current_scope().name + info.name;1055std::replace(info.unique_name.begin(), info.unique_name.end(), ':', '_');10561057if (!expect('{'))1058return false;10591060bool parse_success = true;10611062while (!peek('}')) // Empty structures are possible1063{1064member_type member;10651066if (!parse_type(member.type))1067{1068error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected struct member type");1069consume_until('}');1070accept(';');1071return false;1072}10731074unsigned int count = 0;1075do1076{1077if ((count++ > 0 && !expect(',')) || !expect(tokenid::identifier))1078{1079consume_until('}');1080accept(';');1081return false;1082}10831084member.name = std::move(_token.literal_as_string);1085member.location = std::move(_token.location);10861087if (member.type.is_void())1088{1089parse_success = false;1090error(member.location, 3038, '\'' + member.name + "': struct members cannot be void");1091}1092if (member.type.is_struct()) // Nesting structures would make input/output argument flattening more complicated, so prevent it for now1093{1094parse_success = false;1095error(member.location, 3090, '\'' + member.name + "': nested struct members are not supported");1096}10971098if (member.type.has(type::q_in) || member.type.has(type::q_out))1099{1100parse_success = false;1101error(member.location, 3055, '\'' + member.name + "': struct members cannot be declared 'in' or 'out'");1102}1103if (member.type.has(type::q_const))1104{1105parse_success = false;1106error(member.location, 3035, '\'' + member.name + "': struct members cannot be declared 'const'");1107}1108if (member.type.has(type::q_extern))1109{1110parse_success = false;1111error(member.location, 3006, '\'' + member.name + "': struct members cannot be declared 'extern'");1112}1113if (member.type.has(type::q_static))1114{1115parse_success = false;1116error(member.location, 3007, '\'' + member.name + "': struct members cannot be declared 'static'");1117}1118if (member.type.has(type::q_uniform))1119{1120parse_success = false;1121error(member.location, 3047, '\'' + member.name + "': struct members cannot be declared 'uniform'");1122}1123if (member.type.has(type::q_groupshared))1124{1125parse_success = false;1126error(member.location, 3010, '\'' + member.name + "': struct members cannot be declared 'groupshared'");1127}11281129// Modify member specific type, so that following members in the declaration list are not affected by this1130if (!parse_array_length(member.type))1131{1132consume_until('}');1133accept(';');1134return false;1135}11361137if (member.type.is_unbounded_array())1138{1139parse_success = false;1140error(member.location, 3072, '\'' + member.name + "': array dimensions of struct members must be explicit");1141}11421143// Structure members may have semantics to use them as input/output types1144if (accept(':'))1145{1146if (!expect(tokenid::identifier))1147{1148consume_until('}');1149accept(';');1150return false;1151}11521153member.semantic = std::move(_token.literal_as_string);1154// Make semantic upper case to simplify comparison later on1155std::transform(member.semantic.begin(), member.semantic.end(), member.semantic.begin(),1156[](std::string::value_type c) {1157return static_cast<std::string::value_type>(std::toupper(c));1158});11591160if (member.semantic.compare(0, 3, "SV_") != 0)1161{1162// Always numerate semantics, so that e.g. TEXCOORD and TEXCOORD0 point to the same location1163if (const char c = member.semantic.back(); c < '0' || c > '9')1164member.semantic += '0';11651166if (member.type.is_integral() && !member.type.has(type::q_nointerpolation))1167{1168member.type.qualifiers |= type::q_nointerpolation; // Integer fields do not interpolate, so make this explicit (to avoid issues with GLSL)1169warning(member.location, 4568, '\'' + member.name + "': integer fields have the 'nointerpolation' qualifier by default");1170}1171}1172else1173{1174// Remove optional trailing zero from system value semantics, so that e.g. SV_POSITION and SV_POSITION0 mean the same thing1175if (member.semantic.back() == '0' && (member.semantic[member.semantic.size() - 2] < '0' || member.semantic[member.semantic.size() - 2] > '9'))1176member.semantic.pop_back();1177}1178}11791180// Save member name and type for bookkeeping1181info.member_list.push_back(member);1182}1183while (!peek(';'));11841185if (!expect(';'))1186{1187consume_until('}');1188accept(';');1189return false;1190}1191}11921193// Empty structures are valid, but not usually intended, so emit a warning1194if (info.member_list.empty())1195warning(struct_location, 5001, "struct has no members");11961197// Define the structure now that information about all the member types was gathered1198const codegen::id id = _codegen->define_struct(struct_location, info);11991200// Insert the symbol into the symbol table1201symbol symbol = { symbol_type::structure, id };12021203if (!insert_symbol(info.name, symbol, true))1204{1205error(struct_location, 3003, "redefinition of '" + info.name + '\'');1206return false;1207}12081209return expect('}') && parse_success;1210}12111212bool reshadefx::parser::parse_function(type type, std::string name, shader_type stype, int num_threads[3])1213{1214const location function_location = std::move(_token.location);12151216if (!expect('(')) // Functions always have a parameter list1217return false;12181219if (type.qualifiers != 0)1220{1221error(function_location, 3047, '\'' + name + "': function return type cannot have any qualifiers");1222return false;1223}12241225function info;1226info.name = name;1227info.unique_name = 'F' + current_scope().name + name;1228std::replace(info.unique_name.begin(), info.unique_name.end(), ':', '_');12291230info.return_type = type;1231info.type = stype;1232info.num_threads[0] = num_threads[0];1233info.num_threads[1] = num_threads[1];1234info.num_threads[2] = num_threads[2];12351236_codegen->_current_function = &info;12371238bool parse_success = true;1239bool expect_parenthesis = true;12401241// Enter function scope (and leave it again when parsing this function finished)1242scope_guard _(1243[this]() {1244enter_scope();1245},1246[this]() {1247leave_scope();1248_codegen->leave_function();1249});12501251while (!peek(')'))1252{1253if (!info.parameter_list.empty() && !expect(','))1254{1255parse_success = false;1256expect_parenthesis = false;1257consume_until(')');1258break;1259}12601261member_type param;12621263if (!parse_type(param.type))1264{1265error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected parameter type");1266parse_success = false;1267expect_parenthesis = false;1268consume_until(')');1269break;1270}12711272if (!expect(tokenid::identifier))1273{1274parse_success = false;1275expect_parenthesis = false;1276consume_until(')');1277break;1278}12791280param.name = std::move(_token.literal_as_string);1281param.location = std::move(_token.location);12821283if (param.type.is_void())1284{1285parse_success = false;1286error(param.location, 3038, '\'' + param.name + "': function parameters cannot be void");1287}12881289if (param.type.has(type::q_extern))1290{1291parse_success = false;1292error(param.location, 3006, '\'' + param.name + "': function parameters cannot be declared 'extern'");1293}1294if (param.type.has(type::q_static))1295{1296parse_success = false;1297error(param.location, 3007, '\'' + param.name + "': function parameters cannot be declared 'static'");1298}1299if (param.type.has(type::q_uniform))1300{1301parse_success = false;1302error(param.location, 3047, '\'' + param.name + "': function parameters cannot be declared 'uniform', consider placing in global scope instead");1303}1304if (param.type.has(type::q_groupshared))1305{1306parse_success = false;1307error(param.location, 3010, '\'' + param.name + "': function parameters cannot be declared 'groupshared'");1308}13091310if (param.type.has(type::q_out) && param.type.has(type::q_const))1311{1312parse_success = false;1313error(param.location, 3046, '\'' + param.name + "': output parameters cannot be declared 'const'");1314}1315else if (!param.type.has(type::q_out))1316{1317// Function parameters are implicitly 'in' if not explicitly defined as 'out'1318param.type.qualifiers |= type::q_in;1319}13201321if (!parse_array_length(param.type))1322{1323parse_success = false;1324expect_parenthesis = false;1325consume_until(')');1326break;1327}13281329if (param.type.is_unbounded_array())1330{1331parse_success = false;1332error(param.location, 3072, '\'' + param.name + "': array dimensions of function parameters must be explicit");1333param.type.array_length = 0;1334}13351336// Handle parameter type semantic1337if (accept(':'))1338{1339if (!expect(tokenid::identifier))1340{1341parse_success = false;1342expect_parenthesis = false;1343consume_until(')');1344break;1345}13461347param.semantic = std::move(_token.literal_as_string);1348// Make semantic upper case to simplify comparison later on1349std::transform(param.semantic.begin(), param.semantic.end(), param.semantic.begin(),1350[](std::string::value_type c) {1351return static_cast<std::string::value_type>(std::toupper(c));1352});13531354if (param.semantic.compare(0, 3, "SV_") != 0)1355{1356// Always numerate semantics, so that e.g. TEXCOORD and TEXCOORD0 point to the same location1357if (const char c = param.semantic.back(); c < '0' || c > '9')1358param.semantic += '0';13591360if (param.type.is_integral() && !param.type.has(type::q_nointerpolation))1361{1362param.type.qualifiers |= type::q_nointerpolation; // Integer parameters do not interpolate, so make this explicit (to avoid issues with GLSL)1363warning(param.location, 4568, '\'' + param.name + "': integer parameters have the 'nointerpolation' qualifier by default");1364}1365}1366else1367{1368// Remove optional trailing zero from system value semantics, so that e.g. SV_POSITION and SV_POSITION0 mean the same thing1369if (param.semantic.back() == '0' && (param.semantic[param.semantic.size() - 2] < '0' || param.semantic[param.semantic.size() - 2] > '9'))1370param.semantic.pop_back();1371}1372}13731374// Handle default argument1375if (accept('='))1376{1377expression default_value_exp;1378if (!parse_expression_multary(default_value_exp))1379{1380parse_success = false;1381expect_parenthesis = false;1382consume_until(')');1383break;1384}13851386default_value_exp.add_cast_operation(param.type);13871388if (!default_value_exp.is_constant)1389{1390parse_success = false;1391error(default_value_exp.location, 3011, '\'' + param.name + "': value must be a literal expression");1392}13931394param.default_value = std::move(default_value_exp.constant);1395param.has_default_value = true;1396}1397else1398{1399if (!info.parameter_list.empty() && info.parameter_list.back().has_default_value)1400{1401parse_success = false;1402error(param.location, 3044, '\'' + name + "': missing default value for parameter '" + param.name + '\'');1403}1404}14051406info.parameter_list.push_back(std::move(param));1407}14081409if (expect_parenthesis && !expect(')'))1410return false;14111412// Handle return type semantic1413if (accept(':'))1414{1415if (!expect(tokenid::identifier))1416return false;14171418if (type.is_void())1419{1420error(_token.location, 3076, '\'' + name + "': void function cannot have a semantic");1421return false;1422}14231424info.return_semantic = std::move(_token.literal_as_string);1425// Make semantic upper case to simplify comparison later on1426std::transform(info.return_semantic.begin(), info.return_semantic.end(), info.return_semantic.begin(),1427[](std::string::value_type c) {1428return static_cast<std::string::value_type>(std::toupper(c));1429});1430}14311432// Check if this is a function declaration without a body1433if (accept(';'))1434{1435error(function_location, 3510, '\'' + name + "': function is missing an implementation");1436return false;1437}14381439// Define the function now that information about the declaration was gathered1440const codegen::id id = _codegen->define_function(function_location, info);14411442// Insert the function and parameter symbols into the symbol table and update current function pointer to the permanent one1443symbol symbol = { symbol_type::function, id, { type::t_function } };1444symbol.function = &_codegen->get_function(id);14451446if (!insert_symbol(name, symbol, true))1447{1448error(function_location, 3003, "redefinition of '" + name + '\'');1449return false;1450}14511452for (const member_type ¶m : info.parameter_list)1453{1454if (!insert_symbol(param.name, { symbol_type::variable, param.id, param.type }))1455{1456error(param.location, 3003, "redefinition of '" + param.name + '\'');1457return false;1458}1459}14601461// A function has to start with a new block1462_codegen->enter_block(_codegen->create_block());14631464if (!parse_statement_block(false))1465parse_success = false;14661467// Add implicit return statement to the end of functions1468if (_codegen->is_in_block())1469_codegen->leave_block_and_return();14701471return parse_success;1472}14731474bool reshadefx::parser::parse_variable(type type, std::string name, bool global)1475{1476const location variable_location = std::move(_token.location);14771478if (type.is_void())1479{1480error(variable_location, 3038, '\'' + name + "': variables cannot be void");1481return false;1482}1483if (type.has(type::q_in) || type.has(type::q_out))1484{1485error(variable_location, 3055, '\'' + name + "': variables cannot be declared 'in' or 'out'");1486return false;1487}14881489// Local and global variables have different requirements1490if (global)1491{1492// Check that type qualifier combinations are valid1493if (type.has(type::q_static))1494{1495// Global variables that are 'static' cannot be of another storage class1496if (type.has(type::q_uniform))1497{1498error(variable_location, 3007, '\'' + name + "': uniform global variables cannot be declared 'static'");1499return false;1500}1501// The 'volatile' qualifier is only valid memory object declarations that are storage images or uniform blocks1502if (type.has(type::q_volatile))1503{1504error(variable_location, 3008, '\'' + name + "': global variables cannot be declared 'volatile'");1505return false;1506}1507}1508else if (!type.has(type::q_groupshared))1509{1510// Make all global variables 'uniform' by default, since they should be externally visible without the 'static' keyword1511if (!type.has(type::q_uniform) && !type.is_object())1512warning(variable_location, 5000, '\'' + name + "': global variables are considered 'uniform' by default");15131514// Global variables that are not 'static' are always 'extern' and 'uniform'1515type.qualifiers |= type::q_extern | type::q_uniform;15161517// It is invalid to make 'uniform' variables constant, since they can be modified externally1518if (type.has(type::q_const))1519{1520error(variable_location, 3035, '\'' + name + "': variables which are 'uniform' cannot be declared 'const'");1521return false;1522}1523}1524}1525else1526{1527// Static does not really have meaning on local variables1528if (type.has(type::q_static))1529type.qualifiers &= ~type::q_static;15301531if (type.has(type::q_extern))1532{1533error(variable_location, 3006, '\'' + name + "': local variables cannot be declared 'extern'");1534return false;1535}1536if (type.has(type::q_uniform))1537{1538error(variable_location, 3047, '\'' + name + "': local variables cannot be declared 'uniform'");1539return false;1540}1541if (type.has(type::q_groupshared))1542{1543error(variable_location, 3010, '\'' + name + "': local variables cannot be declared 'groupshared'");1544return false;1545}15461547if (type.is_object())1548{1549error(variable_location, 3038, '\'' + name + "': local variables cannot be texture, sampler or storage objects");1550return false;1551}1552}15531554// The variable name may be followed by an optional array size expression1555if (!parse_array_length(type))1556return false;15571558bool parse_success = true;1559expression initializer;1560texture texture_info;1561sampler sampler_info;1562storage storage_info;15631564if (accept(':'))1565{1566if (!expect(tokenid::identifier))1567return false;15681569if (!global) // Only global variables can have a semantic1570{1571error(_token.location, 3043, '\'' + name + "': local variables cannot have semantics");1572return false;1573}15741575std::string &semantic = texture_info.semantic;1576semantic = std::move(_token.literal_as_string);15771578// Make semantic upper case to simplify comparison later on1579std::transform(semantic.begin(), semantic.end(), semantic.begin(),1580[](std::string::value_type c) {1581return static_cast<std::string::value_type>(std::toupper(c));1582});1583}1584else1585{1586// Global variables can have optional annotations1587if (global && !parse_annotations(sampler_info.annotations))1588parse_success = false;15891590// Variables without a semantic may have an optional initializer1591if (accept('='))1592{1593if (!parse_expression_assignment(initializer))1594return false;15951596if (type.has(type::q_groupshared))1597{1598error(initializer.location, 3009, '\'' + name + "': variables declared 'groupshared' cannot have an initializer");1599return false;1600}16011602// TODO: This could be resolved by initializing these at the beginning of the entry point1603if (global && !initializer.is_constant)1604{1605error(initializer.location, 3011, '\'' + name + "': initial value must be a literal expression");1606return false;1607}16081609// Check type compatibility1610if ((!type.is_unbounded_array() && initializer.type.array_length != type.array_length) || !type::rank(initializer.type, type))1611{1612error(initializer.location, 3017, '\'' + name + "': initial value (" + initializer.type.description() + ") does not match variable type (" + type.description() + ')');1613return false;1614}1615if ((initializer.type.rows < type.rows || initializer.type.cols < type.cols) && !initializer.type.is_scalar())1616{1617error(initializer.location, 3017, '\'' + name + "': cannot implicitly convert these vector types (from " + initializer.type.description() + " to " + type.description() + ')');1618return false;1619}16201621// Deduce array size from the initializer expression1622if (initializer.type.is_array())1623type.array_length = initializer.type.array_length;16241625// Perform implicit cast from initializer expression to variable type1626if (initializer.type.components() > type.components())1627warning(initializer.location, 3206, "implicit truncation of vector type");16281629initializer.add_cast_operation(type);16301631if (type.has(type::q_static))1632initializer.type.qualifiers |= type::q_static;1633}1634else if (type.is_numeric() || type.is_struct()) // Numeric variables without an initializer need special handling1635{1636if (type.has(type::q_const)) // Constants have to have an initial value1637{1638error(variable_location, 3012, '\'' + name + "': missing initial value");1639return false;1640}16411642if (!type.has(type::q_uniform)) // Zero initialize all global variables1643initializer.reset_to_rvalue_constant(variable_location, {}, type);1644}1645else if (global && accept('{')) // Textures and samplers can have a property block attached to their declaration1646{1647// Non-numeric variables cannot be constants1648if (type.has(type::q_const))1649{1650error(variable_location, 3035, '\'' + name + "': this variable type cannot be declared 'const'");1651return false;1652}16531654while (!peek('}'))1655{1656if (!expect(tokenid::identifier))1657{1658consume_until('}');1659return false;1660}16611662location property_location = std::move(_token.location);1663const std::string property_name = std::move(_token.literal_as_string);16641665if (!expect('='))1666{1667consume_until('}');1668return false;1669}16701671backup();16721673expression property_exp;16741675if (accept(tokenid::identifier)) // Handle special enumeration names for property values1676{1677// Transform identifier to uppercase to do case-insensitive comparison1678std::transform(_token.literal_as_string.begin(), _token.literal_as_string.end(), _token.literal_as_string.begin(),1679[](std::string::value_type c) {1680return static_cast<std::string::value_type>(std::toupper(c));1681});16821683static const std::unordered_map<std::string_view, uint32_t> s_enum_values = {1684{ "NONE", 0 }, { "POINT", 0 },1685{ "LINEAR", 1 },1686{ "ANISOTROPIC", 0x55 },1687{ "WRAP", uint32_t(texture_address_mode::wrap) }, { "REPEAT", uint32_t(texture_address_mode::wrap) },1688{ "MIRROR", uint32_t(texture_address_mode::mirror) },1689{ "CLAMP", uint32_t(texture_address_mode::clamp) },1690{ "BORDER", uint32_t(texture_address_mode::border) },1691{ "R8", uint32_t(texture_format::r8) },1692{ "R16", uint32_t(texture_format::r16) },1693{ "R16F", uint32_t(texture_format::r16f) },1694{ "R32I", uint32_t(texture_format::r32i) },1695{ "R32U", uint32_t(texture_format::r32u) },1696{ "R32F", uint32_t(texture_format::r32f) },1697{ "RG8", uint32_t(texture_format::rg8) }, { "R8G8", uint32_t(texture_format::rg8) },1698{ "RG16", uint32_t(texture_format::rg16) }, { "R16G16", uint32_t(texture_format::rg16) },1699{ "RG16F", uint32_t(texture_format::rg16f) }, { "R16G16F", uint32_t(texture_format::rg16f) },1700{ "RG32F", uint32_t(texture_format::rg32f) }, { "R32G32F", uint32_t(texture_format::rg32f) },1701{ "RGBA8", uint32_t(texture_format::rgba8) }, { "R8G8B8A8", uint32_t(texture_format::rgba8) },1702{ "RGBA16", uint32_t(texture_format::rgba16) }, { "R16G16B16A16", uint32_t(texture_format::rgba16) },1703{ "RGBA16F", uint32_t(texture_format::rgba16f) }, { "R16G16B16A16F", uint32_t(texture_format::rgba16f) },1704{ "RGBA32F", uint32_t(texture_format::rgba32f) }, { "R32G32B32A32F", uint32_t(texture_format::rgba32f) },1705{ "RGB10A2", uint32_t(texture_format::rgb10a2) }, { "R10G10B10A2", uint32_t(texture_format::rgb10a2) },1706};17071708// Look up identifier in list of possible enumeration names1709if (const auto it = s_enum_values.find(_token.literal_as_string);1710it != s_enum_values.end())1711property_exp.reset_to_rvalue_constant(_token.location, it->second);1712else // No match found, so rewind to parser state before the identifier was consumed and try parsing it as a normal expression1713restore();1714}17151716// Parse right hand side as normal expression if no special enumeration name was matched already1717if (!property_exp.is_constant && !parse_expression_multary(property_exp))1718{1719consume_until('}');1720return false;1721}17221723if (property_name == "Texture")1724{1725// Ignore invalid symbols that were added during error recovery1726if (property_exp.base == UINT32_MAX)1727{1728consume_until('}');1729return false;1730}17311732if (!property_exp.type.is_texture())1733{1734error(property_exp.location, 3020, "type mismatch, expected texture name");1735consume_until('}');1736return false;1737}17381739if (type.is_sampler() || type.is_storage())1740{1741texture &target_info = _codegen->get_texture(property_exp.base);1742if (type.is_storage())1743// Texture is used as storage1744target_info.storage_access = true;17451746texture_info = target_info;1747sampler_info.texture_name = target_info.unique_name;1748storage_info.texture_name = target_info.unique_name;1749}1750}1751else1752{1753if (!property_exp.is_constant || !property_exp.type.is_scalar())1754{1755error(property_exp.location, 3538, "value must be a literal scalar expression");1756consume_until('}');1757return false;1758}17591760// All states below expect the value to be of an integer type1761property_exp.add_cast_operation({ type::t_int, 1, 1 });1762const int value = property_exp.constant.as_int[0];17631764if (value < 0) // There is little use for negative values, so warn in those cases1765warning(property_exp.location, 3571, "negative value specified for property '" + property_name + '\'');17661767if (type.is_texture())1768{1769if (property_name == "Width")1770texture_info.width = value > 0 ? value : 1;1771else if (type.texture_dimension() >= 2 && property_name == "Height")1772texture_info.height = value > 0 ? value : 1;1773else if (type.texture_dimension() >= 3 && property_name == "Depth")1774texture_info.depth = value > 0 && value <= std::numeric_limits<uint16_t>::max() ? static_cast<uint16_t>(value) : 1;1775else if (property_name == "MipLevels")1776// Also ensures negative values do not cause problems1777texture_info.levels = value > 0 && value <= std::numeric_limits<uint16_t>::max() ? static_cast<uint16_t>(value) : 1;1778else if (property_name == "Format")1779texture_info.format = static_cast<texture_format>(value);1780else1781error(property_location, 3004, "unrecognized property '" + property_name + '\'');1782}1783else if (type.is_sampler())1784{1785if (property_name == "SRGBTexture" || property_name == "SRGBReadEnable")1786sampler_info.srgb = value != 0;1787else if (property_name == "AddressU")1788sampler_info.address_u = static_cast<texture_address_mode>(value);1789else if (property_name == "AddressV")1790sampler_info.address_v = static_cast<texture_address_mode>(value);1791else if (property_name == "AddressW")1792sampler_info.address_w = static_cast<texture_address_mode>(value);1793else if (property_name == "MinFilter")1794// Combine sampler filter components into a single filter enumeration value1795sampler_info.filter = static_cast<filter_mode>((uint32_t(sampler_info.filter) & 0x4F) | ((value & 0x03) << 4) | (value & 0x40));1796else if (property_name == "MagFilter")1797sampler_info.filter = static_cast<filter_mode>((uint32_t(sampler_info.filter) & 0x73) | ((value & 0x03) << 2) | (value & 0x40));1798else if (property_name == "MipFilter")1799sampler_info.filter = static_cast<filter_mode>((uint32_t(sampler_info.filter) & 0x7C) | ((value & 0x03) ) | (value & 0x40));1800else if (property_name == "MinLOD" || property_name == "MaxMipLevel")1801sampler_info.min_lod = static_cast<float>(value);1802else if (property_name == "MaxLOD")1803sampler_info.max_lod = static_cast<float>(value);1804else if (property_name == "MipLODBias" || property_name == "MipMapLodBias")1805sampler_info.lod_bias = static_cast<float>(value);1806else1807error(property_location, 3004, "unrecognized property '" + property_name + '\'');1808}1809else if (type.is_storage())1810{1811if (property_name == "MipLOD" || property_name == "MipLevel")1812storage_info.level = value > 0 && value < std::numeric_limits<uint16_t>::max() ? static_cast<uint16_t>(value) : 0;1813else1814error(property_location, 3004, "unrecognized property '" + property_name + '\'');1815}1816}18171818if (!expect(';'))1819{1820consume_until('}');1821return false;1822}1823}18241825if (!expect('}'))1826return false;1827}1828}18291830// At this point the array size should be known (either from the declaration or the initializer)1831if (type.is_unbounded_array())1832{1833error(variable_location, 3074, '\'' + name + "': implicit array missing initial value");1834return false;1835}18361837symbol symbol;18381839// Variables with a constant initializer and constant type are named constants1840// Skip this for very large arrays though, to avoid large amounts of duplicated values when that array constant is accessed with a dynamic index1841if (type.is_numeric() && type.has(type::q_const) && initializer.is_constant && type.array_length < 100)1842{1843// Named constants are special symbols1844symbol = { symbol_type::constant, 0, type, initializer.constant };1845}1846else if (type.is_texture())1847{1848assert(global);18491850texture_info.name = name;1851texture_info.type = static_cast<texture_type>(type.texture_dimension());18521853// Add namespace scope to avoid name clashes1854texture_info.unique_name = 'V' + current_scope().name + name;1855std::replace(texture_info.unique_name.begin(), texture_info.unique_name.end(), ':', '_');18561857texture_info.annotations = std::move(sampler_info.annotations);18581859const codegen::id id = _codegen->define_texture(variable_location, texture_info);1860symbol = { symbol_type::variable, id, type };1861}1862// Samplers are actually combined image samplers1863else if (type.is_sampler())1864{1865assert(global);18661867if (sampler_info.texture_name.empty())1868{1869error(variable_location, 3012, '\'' + name + "': missing 'Texture' property");1870return false;1871}1872if (type.texture_dimension() != static_cast<unsigned int>(texture_info.type))1873{1874error(variable_location, 3521, '\'' + name + "': type mismatch between texture and sampler type");1875return false;1876}1877if (sampler_info.srgb && texture_info.format != texture_format::rgba8)1878{1879error(variable_location, 4582, '\'' + name + "': texture does not support sRGB sampling (only textures with RGBA8 format do)");1880return false;1881}18821883if (texture_info.format == texture_format::r32i ?1884!type.is_integral() || !type.is_signed() :1885texture_info.format == texture_format::r32u ?1886!type.is_integral() || !type.is_unsigned() :1887!type.is_floating_point())1888{1889error(variable_location, 4582, '\'' + name + "': type mismatch between texture format and sampler element type");1890return false;1891}18921893sampler_info.name = name;1894sampler_info.type = type;18951896// Add namespace scope to avoid name clashes1897sampler_info.unique_name = 'V' + current_scope().name + name;1898std::replace(sampler_info.unique_name.begin(), sampler_info.unique_name.end(), ':', '_');18991900const codegen::id id = _codegen->define_sampler(variable_location, texture_info, sampler_info);1901symbol = { symbol_type::variable, id, type };1902}1903else if (type.is_storage())1904{1905assert(global);19061907if (storage_info.texture_name.empty())1908{1909error(variable_location, 3012, '\'' + name + "': missing 'Texture' property");1910return false;1911}1912if (type.texture_dimension() != static_cast<unsigned int>(texture_info.type))1913{1914error(variable_location, 3521, '\'' + name + "': type mismatch between texture and storage type");1915return false;1916}19171918if (texture_info.format == texture_format::r32i ?1919!type.is_integral() || !type.is_signed() :1920texture_info.format == texture_format::r32u ?1921!type.is_integral() || !type.is_unsigned() :1922!type.is_floating_point())1923{1924error(variable_location, 4582, '\'' + name + "': type mismatch between texture format and storage element type");1925return false;1926}19271928storage_info.name = name;1929storage_info.type = type;19301931// Add namespace scope to avoid name clashes1932storage_info.unique_name = 'V' + current_scope().name + name;1933std::replace(storage_info.unique_name.begin(), storage_info.unique_name.end(), ':', '_');19341935if (storage_info.level > texture_info.levels - 1)1936storage_info.level = texture_info.levels - 1;19371938const codegen::id id = _codegen->define_storage(variable_location, texture_info, storage_info);1939symbol = { symbol_type::variable, id, type };1940}1941// Uniform variables are put into a global uniform buffer structure1942else if (type.has(type::q_uniform))1943{1944assert(global);19451946uniform uniform_info;1947uniform_info.name = name;1948uniform_info.type = type;19491950uniform_info.annotations = std::move(sampler_info.annotations);19511952uniform_info.initializer_value = std::move(initializer.constant);1953uniform_info.has_initializer_value = initializer.is_constant;19541955const codegen::id id = _codegen->define_uniform(variable_location, uniform_info);1956symbol = { symbol_type::variable, id, type };1957}1958// All other variables are separate entities1959else1960{1961// Update global variable names to contain the namespace scope to avoid name clashes1962std::string unique_name = global ? 'V' + current_scope().name + name : name;1963std::replace(unique_name.begin(), unique_name.end(), ':', '_');19641965symbol = { symbol_type::variable, 0, type };1966symbol.id = _codegen->define_variable(variable_location, type, std::move(unique_name), global,1967// Shared variables cannot have an initializer1968type.has(type::q_groupshared) ? 0 : _codegen->emit_load(initializer));1969}19701971// Insert the symbol into the symbol table1972if (!insert_symbol(name, symbol, global))1973{1974error(variable_location, 3003, "redefinition of '" + name + '\'');1975return false;1976}19771978return parse_success;1979}19801981bool reshadefx::parser::parse_technique()1982{1983if (!expect(tokenid::identifier))1984return false;19851986technique info;1987info.name = std::move(_token.literal_as_string);19881989bool parse_success = parse_annotations(info.annotations);19901991if (!expect('{'))1992return false;19931994while (!peek('}'))1995{1996pass pass;1997if (parse_technique_pass(pass))1998{1999info.passes.push_back(std::move(pass));2000}2001else2002{2003parse_success = false;2004if (!peek(tokenid::pass) && !peek('}')) // If there is another pass definition following, try to parse that despite the error2005{2006consume_until('}');2007return false;2008}2009}2010}20112012_codegen->define_technique(std::move(info));20132014return expect('}') && parse_success;2015}2016bool reshadefx::parser::parse_technique_pass(pass &info)2017{2018if (!expect(tokenid::pass))2019return false;20202021const location pass_location = std::move(_token.location);20222023// Passes can have an optional name2024if (accept(tokenid::identifier))2025info.name = std::move(_token.literal_as_string);20262027bool parse_success = true;2028bool targets_support_srgb = true;2029function vs_info = {}, ps_info = {}, cs_info = {};20302031if (!expect('{'))2032return false;20332034while (!peek('}'))2035{2036// Parse pass states2037if (!expect(tokenid::identifier))2038{2039consume_until('}');2040return false;2041}20422043location state_location = std::move(_token.location);2044const std::string state_name = std::move(_token.literal_as_string);20452046if (!expect('='))2047{2048consume_until('}');2049return false;2050}20512052const bool is_shader_state = state_name.size() > 6 && state_name.compare(state_name.size() - 6, 6, "Shader") == 0; // VertexShader, PixelShader, ComputeShader, ...2053const bool is_texture_state = state_name.compare(0, 12, "RenderTarget") == 0 && (state_name.size() == 12 || (state_name[12] >= '0' && state_name[12] < '8'));20542055// Shader and render target assignment looks up values in the symbol table, so handle those separately from the other states2056if (is_shader_state || is_texture_state)2057{2058std::string identifier;2059scoped_symbol symbol;2060if (!accept_symbol(identifier, symbol))2061{2062consume_until('}');2063return false;2064}20652066state_location = std::move(_token.location);20672068int num_threads[3] = { 0, 0, 0 };2069if (accept('<'))2070{2071expression x, y, z;2072if (!parse_expression_multary(x, 8) || !expect(',') || !parse_expression_multary(y, 8))2073{2074consume_until('}');2075return false;2076}20772078// Parse optional third dimension (defaults to 1)2079z.reset_to_rvalue_constant({}, 1);2080if (accept(',') && !parse_expression_multary(z, 8))2081{2082consume_until('}');2083return false;2084}20852086if (!x.is_constant)2087{2088error(x.location, 3011, "value must be a literal expression");2089consume_until('}');2090return false;2091}2092if (!y.is_constant)2093{2094error(y.location, 3011, "value must be a literal expression");2095consume_until('}');2096return false;2097}2098if (!z.is_constant)2099{2100error(z.location, 3011, "value must be a literal expression");2101consume_until('}');2102return false;2103}2104x.add_cast_operation({ type::t_int, 1, 1 });2105y.add_cast_operation({ type::t_int, 1, 1 });2106z.add_cast_operation({ type::t_int, 1, 1 });2107num_threads[0] = x.constant.as_int[0];2108num_threads[1] = y.constant.as_int[0];2109num_threads[2] = z.constant.as_int[0];21102111if (!expect('>'))2112{2113consume_until('}');2114return false;2115}2116}21172118// Ignore invalid symbols that were added during error recovery2119if (symbol.id != UINT32_MAX)2120{2121if (is_shader_state)2122{2123if (!symbol.id)2124{2125parse_success = false;2126error(state_location, 3501, "undeclared identifier '" + identifier + "', expected function name");2127}2128else if (!symbol.type.is_function())2129{2130parse_success = false;2131error(state_location, 3020, "type mismatch, expected function name");2132}2133else2134{2135// Look up the matching function info for this function definition2136const function &function_info = _codegen->get_function(symbol.id);21372138// We potentially need to generate a special entry point function which translates between function parameters and input/output variables2139switch (state_name[0])2140{2141case 'V':2142vs_info = function_info;2143if (vs_info.type != shader_type::unknown && vs_info.type != shader_type::vertex)2144{2145parse_success = false;2146error(state_location, 3020, "type mismatch, expected vertex shader function");2147break;2148}2149vs_info.type = shader_type::vertex;2150_codegen->define_entry_point(vs_info);2151info.vs_entry_point = vs_info.unique_name;2152break;2153case 'P':2154ps_info = function_info;2155if (ps_info.type != shader_type::unknown && ps_info.type != shader_type::pixel)2156{2157parse_success = false;2158error(state_location, 3020, "type mismatch, expected pixel shader function");2159break;2160}2161ps_info.type = shader_type::pixel;2162_codegen->define_entry_point(ps_info);2163info.ps_entry_point = ps_info.unique_name;2164break;2165case 'C':2166cs_info = function_info;2167if (cs_info.type != shader_type::unknown && cs_info.type != shader_type::compute)2168{2169parse_success = false;2170error(state_location, 3020, "type mismatch, expected compute shader function");2171break;2172}2173cs_info.type = shader_type::compute;2174// Only use number of threads from pass when specified, otherwise fall back to number specified on the function definition with an attribute2175if (num_threads[0] != 0)2176{2177cs_info.num_threads[0] = num_threads[0];2178cs_info.num_threads[1] = num_threads[1];2179cs_info.num_threads[2] = num_threads[2];2180}2181else2182{2183cs_info.num_threads[0] = std::max(cs_info.num_threads[0], 1);2184cs_info.num_threads[1] = std::max(cs_info.num_threads[1], 1);2185cs_info.num_threads[2] = std::max(cs_info.num_threads[2], 1);2186}2187_codegen->define_entry_point(cs_info);2188info.cs_entry_point = cs_info.unique_name;2189break;2190}2191}2192}2193else2194{2195assert(is_texture_state);21962197if (!symbol.id)2198{2199parse_success = false;2200error(state_location, 3004, "undeclared identifier '" + identifier + "', expected texture name");2201}2202else if (!symbol.type.is_texture())2203{2204parse_success = false;2205error(state_location, 3020, "type mismatch, expected texture name");2206}2207else if (symbol.type.texture_dimension() != 2)2208{2209parse_success = false;2210error(state_location, 3020, "cannot use texture" + std::to_string(symbol.type.texture_dimension()) + "D as render target");2211}2212else2213{2214texture &target_info = _codegen->get_texture(symbol.id);22152216if (target_info.semantic.empty())2217{2218// Texture is used as a render target2219target_info.render_target = true;22202221// Verify that all render targets in this pass have the same dimensions2222if (info.viewport_width != 0 && info.viewport_height != 0 && (target_info.width != info.viewport_width || target_info.height != info.viewport_height))2223{2224parse_success = false;2225error(state_location, 4545, "cannot use multiple render targets with different texture dimensions (is " + std::to_string(target_info.width) + 'x' + std::to_string(target_info.height) + ", but expected " + std::to_string(info.viewport_width) + 'x' + std::to_string(info.viewport_height) + ')');2226}22272228info.viewport_width = target_info.width;2229info.viewport_height = target_info.height;22302231const int target_index = state_name.size() > 12 ? (state_name[12] - '0') : 0;2232info.render_target_names[target_index] = target_info.unique_name;22332234// Only RGBA8 format supports sRGB writes across all APIs2235if (target_info.format != texture_format::rgba8)2236targets_support_srgb = false;2237}2238else2239{2240parse_success = false;2241error(state_location, 3020, "cannot use texture with semantic as render target");2242}2243}2244}2245}2246else2247{2248parse_success = false;2249}2250}2251else // Handle the rest of the pass states2252{2253backup();22542255expression state_exp;22562257if (accept(tokenid::identifier)) // Handle special enumeration names for pass states2258{2259// Transform identifier to uppercase to do case-insensitive comparison2260std::transform(_token.literal_as_string.begin(), _token.literal_as_string.end(), _token.literal_as_string.begin(),2261[](std::string::value_type c) {2262return static_cast<std::string::value_type>(std::toupper(c));2263});22642265static const std::unordered_map<std::string_view, uint32_t> s_enum_values = {2266{ "NONE", 0 }, { "ZERO", 0 }, { "ONE", 1 },2267{ "ADD", uint32_t(blend_op::add) },2268{ "SUBTRACT", uint32_t(blend_op::subtract) },2269{ "REVSUBTRACT", uint32_t(blend_op::reverse_subtract) },2270{ "MIN", uint32_t(blend_op::min) },2271{ "MAX", uint32_t(blend_op::max) },2272{ "SRCCOLOR", uint32_t(blend_factor::source_color) },2273{ "INVSRCCOLOR", uint32_t(blend_factor::one_minus_source_color) },2274{ "DESTCOLOR", uint32_t(blend_factor::dest_color) },2275{ "INVDESTCOLOR", uint32_t(blend_factor::one_minus_dest_color) },2276{ "SRCALPHA", uint32_t(blend_factor::source_alpha) },2277{ "INVSRCALPHA", uint32_t(blend_factor::one_minus_source_alpha) },2278{ "DESTALPHA", uint32_t(blend_factor::dest_alpha) },2279{ "INVDESTALPHA", uint32_t(blend_factor::one_minus_dest_alpha) },2280{ "KEEP", uint32_t(stencil_op::keep) },2281{ "REPLACE", uint32_t(stencil_op::replace) },2282{ "INVERT", uint32_t(stencil_op::invert) },2283{ "INCR", uint32_t(stencil_op::increment) },2284{ "INCRSAT", uint32_t(stencil_op::increment_saturate) },2285{ "DECR", uint32_t(stencil_op::decrement) },2286{ "DECRSAT", uint32_t(stencil_op::decrement_saturate) },2287{ "NEVER", uint32_t(stencil_func::never) },2288{ "EQUAL", uint32_t(stencil_func::equal) },2289{ "NEQUAL", uint32_t(stencil_func::not_equal) }, { "NOTEQUAL", uint32_t(stencil_func::not_equal) },2290{ "LESS", uint32_t(stencil_func::less) },2291{ "GREATER", uint32_t(stencil_func::greater) },2292{ "LEQUAL", uint32_t(stencil_func::less_equal) }, { "LESSEQUAL", uint32_t(stencil_func::less_equal) },2293{ "GEQUAL", uint32_t(stencil_func::greater_equal) }, { "GREATEREQUAL", uint32_t(stencil_func::greater_equal) },2294{ "ALWAYS", uint32_t(stencil_func::always) },2295{ "POINTS", uint32_t(primitive_topology::point_list) },2296{ "POINTLIST", uint32_t(primitive_topology::point_list) },2297{ "LINES", uint32_t(primitive_topology::line_list) },2298{ "LINELIST", uint32_t(primitive_topology::line_list) },2299{ "LINESTRIP", uint32_t(primitive_topology::line_strip) },2300{ "TRIANGLES", uint32_t(primitive_topology::triangle_list) },2301{ "TRIANGLELIST", uint32_t(primitive_topology::triangle_list) },2302{ "TRIANGLESTRIP", uint32_t(primitive_topology::triangle_strip) },2303};23042305// Look up identifier in list of possible enumeration names2306if (const auto it = s_enum_values.find(_token.literal_as_string);2307it != s_enum_values.end())2308state_exp.reset_to_rvalue_constant(_token.location, it->second);2309else // No match found, so rewind to parser state before the identifier was consumed and try parsing it as a normal expression2310restore();2311}23122313// Parse right hand side as normal expression if no special enumeration name was matched already2314if (!state_exp.is_constant && !parse_expression_multary(state_exp))2315{2316consume_until('}');2317return false;2318}23192320if (!state_exp.is_constant || !state_exp.type.is_scalar())2321{2322parse_success = false;2323error(state_exp.location, 3011, "pass state value must be a literal scalar expression");2324}23252326// All states below expect the value to be of an unsigned integer type2327state_exp.add_cast_operation({ type::t_uint, 1, 1 });2328const unsigned int value = state_exp.constant.as_uint[0];23292330#define SET_STATE_VALUE_INDEXED(name, info_name, value) \2331else if (constexpr size_t name##_len = sizeof(#name) - 1; state_name.compare(0, name##_len, #name) == 0 && \2332(state_name.size() == name##_len || (state_name[name##_len] >= '0' && state_name[name##_len] < ('0' + static_cast<char>(std::size(info.info_name)))))) \2333{ \2334if (state_name.size() != name##_len) \2335info.info_name[state_name[name##_len] - '0'] = (value); \2336else \2337for (int i = 0; i < static_cast<int>(std::size(info.info_name)); ++i) \2338info.info_name[i] = (value); \2339}23402341if (state_name == "SRGBWriteEnable")2342info.srgb_write_enable = (value != 0);2343SET_STATE_VALUE_INDEXED(BlendEnable, blend_enable, value != 0)2344else if (state_name == "StencilEnable")2345info.stencil_enable = (value != 0);2346else if (state_name == "ClearRenderTargets")2347info.clear_render_targets = (value != 0);2348SET_STATE_VALUE_INDEXED(ColorWriteMask, render_target_write_mask, value & 0xFF)2349SET_STATE_VALUE_INDEXED(RenderTargetWriteMask, render_target_write_mask, value & 0xFF)2350else if (state_name == "StencilReadMask" || state_name == "StencilMask")2351info.stencil_read_mask = value & 0xFF;2352else if (state_name == "StencilWriteMask")2353info.stencil_write_mask = value & 0xFF;2354SET_STATE_VALUE_INDEXED(BlendOp, color_blend_op, static_cast<blend_op>(value))2355SET_STATE_VALUE_INDEXED(BlendOpAlpha, alpha_blend_op, static_cast<blend_op>(value))2356SET_STATE_VALUE_INDEXED(SrcBlend, source_color_blend_factor, static_cast<blend_factor>(value))2357SET_STATE_VALUE_INDEXED(SrcBlendAlpha, source_alpha_blend_factor, static_cast<blend_factor>(value))2358SET_STATE_VALUE_INDEXED(DestBlend, dest_color_blend_factor, static_cast<blend_factor>(value))2359SET_STATE_VALUE_INDEXED(DestBlendAlpha, dest_alpha_blend_factor, static_cast<blend_factor>(value))2360else if (state_name == "StencilFunc")2361info.stencil_comparison_func = static_cast<stencil_func>(value);2362else if (state_name == "StencilRef")2363info.stencil_reference_value = value;2364else if (state_name == "StencilPass" || state_name == "StencilPassOp")2365info.stencil_pass_op = static_cast<stencil_op>(value);2366else if (state_name == "StencilFail" || state_name == "StencilFailOp")2367info.stencil_fail_op = static_cast<stencil_op>(value);2368else if (state_name == "StencilZFail" || state_name == "StencilDepthFail" || state_name == "StencilDepthFailOp")2369info.stencil_depth_fail_op = static_cast<stencil_op>(value);2370else if (state_name == "VertexCount")2371info.num_vertices = value;2372else if (state_name == "PrimitiveType" || state_name == "PrimitiveTopology")2373info.topology = static_cast<primitive_topology>(value);2374else if (state_name == "DispatchSizeX")2375info.viewport_width = value;2376else if (state_name == "DispatchSizeY")2377info.viewport_height = value;2378else if (state_name == "DispatchSizeZ")2379info.viewport_dispatch_z = value;2380else if (state_name == "GenerateMipmaps" || state_name == "GenerateMipMaps")2381info.generate_mipmaps = (value != 0);2382else2383error(state_location, 3004, "unrecognized pass state '" + state_name + '\'');23842385#undef SET_STATE_VALUE_INDEXED2386}23872388if (!expect(';'))2389{2390consume_until('}');2391return false;2392}2393}23942395if (parse_success)2396{2397if (!info.cs_entry_point.empty())2398{2399if (info.viewport_width == 0 || info.viewport_height == 0)2400{2401parse_success = false;2402error(pass_location, 3012, "pass is missing 'DispatchSizeX' or 'DispatchSizeY' property");2403}24042405if (!info.vs_entry_point.empty())2406warning(pass_location, 3089, "pass is specifying both 'VertexShader' and 'ComputeShader' which cannot be used together");2407if (!info.ps_entry_point.empty())2408warning(pass_location, 3089, "pass is specifying both 'PixelShader' and 'ComputeShader' which cannot be used together");2409}2410else2411{2412if (info.vs_entry_point.empty())2413{2414parse_success = false;2415error(pass_location, 3012, "pass is missing 'VertexShader' property");2416}24172418// Verify that shader signatures between VS and PS match (both semantics and interpolation qualifiers)2419std::unordered_map<std::string_view, type> vs_semantic_mapping;2420if (vs_info.return_semantic.empty())2421{2422if (!vs_info.return_type.is_void() && !vs_info.return_type.is_struct())2423{2424parse_success = false;2425error(pass_location, 3503, '\'' + vs_info.name + "': function return value is missing semantics");2426}2427}2428else2429{2430vs_semantic_mapping[vs_info.return_semantic] = vs_info.return_type;2431}24322433for (const member_type ¶m : vs_info.parameter_list)2434{2435if (param.semantic.empty())2436{2437if (!param.type.is_struct())2438{2439parse_success = false;2440if (param.type.has(type::q_in))2441error(pass_location, 3502, '\'' + vs_info.name + "': input parameter '" + param.name + "' is missing semantics");2442else2443error(pass_location, 3503, '\'' + vs_info.name + "': output parameter '" + param.name + "' is missing semantics");2444}2445}2446else if (param.type.has(type::q_out))2447{2448vs_semantic_mapping[param.semantic] = param.type;2449}2450}24512452if (ps_info.return_semantic.empty())2453{2454if (!ps_info.return_type.is_void() && !ps_info.return_type.is_struct())2455{2456parse_success = false;2457error(pass_location, 3503, '\'' + ps_info.name + "': function return value is missing semantics");2458}2459}24602461for (const member_type ¶m : ps_info.parameter_list)2462{2463if (param.semantic.empty())2464{2465if (!param.type.is_struct())2466{2467parse_success = false;2468if (param.type.has(type::q_in))2469error(pass_location, 3502, '\'' + ps_info.name + "': input parameter '" + param.name + "' is missing semantics");2470else2471error(pass_location, 3503, '\'' + ps_info.name + "': output parameter '" + param.name + "' is missing semantics");2472}2473}2474else if (param.type.has(type::q_in))2475{2476if (const auto it = vs_semantic_mapping.find(param.semantic);2477it == vs_semantic_mapping.end() || it->second != param.type)2478{2479warning(pass_location, 4576, '\'' + ps_info.name + "': input parameter '" + param.name + "' semantic does not match vertex shader one");2480}2481else if (((it->second.qualifiers ^ param.type.qualifiers) & (type::q_linear | type::q_noperspective | type::q_centroid | type::q_nointerpolation)) != 0)2482{2483parse_success = false;2484error( pass_location, 4568, '\'' + ps_info.name + "': input parameter '" + param.name + "' interpolation qualifiers do not match vertex shader ones");2485}2486}2487}24882489for (codegen::id id : vs_info.referenced_samplers)2490{2491const sampler &sampler = _codegen->get_sampler(id);2492if (std::find(std::begin(info.render_target_names), std::end(info.render_target_names), sampler.texture_name) != std::end(info.render_target_names))2493error(pass_location, 3020, '\'' + sampler.texture_name + "': cannot sample from texture that is also used as render target in the same pass");2494}2495for (codegen::id id : ps_info.referenced_samplers)2496{2497const sampler &sampler = _codegen->get_sampler(id);2498if (std::find(std::begin(info.render_target_names), std::end(info.render_target_names), sampler.texture_name) != std::end(info.render_target_names))2499error(pass_location, 3020, '\'' + sampler.texture_name + "': cannot sample from texture that is also used as render target in the same pass");2500}25012502if (!vs_info.referenced_storages.empty() || !ps_info.referenced_storages.empty())2503{2504parse_success = false;2505error(pass_location, 3667, "storage writes are only valid in compute shaders");2506}25072508// Verify render target format supports sRGB writes if enabled2509if (info.srgb_write_enable && !targets_support_srgb)2510{2511parse_success = false;2512error(pass_location, 4582, "one or more render target(s) do not support sRGB writes (only textures with RGBA8 format do)");2513}2514}2515}25162517return expect('}') && parse_success;2518}25192520void reshadefx::codegen::optimize_bindings()2521{2522struct sampler_group2523{2524std::vector<id> bindings;2525function *grouped_entry_point = nullptr;2526};2527struct entry_point_info2528{2529std::vector<sampler_group> sampler_groups;25302531static void compare_and_update_bindings(std::unordered_map<function *, entry_point_info> &per_entry_point, sampler_group &a, sampler_group &b, size_t binding)2532{2533for (; binding < std::min(a.bindings.size(), b.bindings.size()); ++binding)2534{2535if (a.bindings[binding] != b.bindings[binding])2536{2537if (a.bindings[binding] == 0)2538{2539b.bindings.insert(b.bindings.begin() + binding, 0);25402541if (b.grouped_entry_point != nullptr)2542for (sampler_group &c : per_entry_point.at(b.grouped_entry_point).sampler_groups)2543compare_and_update_bindings(per_entry_point, b, c, binding);2544continue;2545}25462547if (b.bindings[binding] == 0)2548{2549a.bindings.insert(a.bindings.begin() + binding, 0);25502551if (a.grouped_entry_point != nullptr)2552for (sampler_group &c : per_entry_point.at(a.grouped_entry_point).sampler_groups)2553compare_and_update_bindings(per_entry_point, a, c, binding);2554continue;2555}2556}2557}2558}2559};25602561std::unordered_map<function *, entry_point_info> per_entry_point;2562for (const auto &[name, type] : _module.entry_points)2563{2564per_entry_point.emplace(&get_function(name), entry_point_info {});2565}25662567std::unordered_map<id, int> usage_count;2568for (const auto &[entry_point, entry_point_info] : per_entry_point)2569{2570for (const id sampler_id : entry_point->referenced_samplers)2571usage_count[sampler_id]++;2572for (const id storage_id : entry_point->referenced_storages)2573usage_count[storage_id]++;2574}25752576// First sort bindings by usage and for each pass arrange them so that VS and PS use matching bindings for the objects they use (so that the same bindings can be used for both entry points).2577// If the entry points VS1 and PS1 use the following objects A, B and C:2578// - VS1: A B2579// - PS1: B C2580// Then this generates the following bindings:2581// - VS1: C A2582// - PS1: C 0 B25832584const auto usage_pred =2585[&](const id lhs, const id rhs) {2586return usage_count.at(lhs) > usage_count.at(rhs) || (usage_count.at(lhs) == usage_count.at(rhs) && lhs < rhs);2587};25882589for (const auto &[entry_point, entry_point_info] : per_entry_point)2590{2591std::sort(entry_point->referenced_samplers.begin(), entry_point->referenced_samplers.end(), usage_pred);2592std::sort(entry_point->referenced_storages.begin(), entry_point->referenced_storages.end(), usage_pred);2593}25942595for (const technique &tech : _module.techniques)2596{2597for (const pass &pass : tech.passes)2598{2599if (!pass.cs_entry_point.empty())2600{2601function &cs = get_function(pass.cs_entry_point);26022603sampler_group cs_sampler_info;2604cs_sampler_info.bindings = cs.referenced_samplers;2605per_entry_point.at(&cs).sampler_groups.push_back(std::move(cs_sampler_info));2606}2607else2608{2609function &vs = get_function(pass.vs_entry_point);26102611sampler_group vs_sampler_info;2612vs_sampler_info.bindings = vs.referenced_samplers;26132614if (!pass.ps_entry_point.empty())2615{2616function &ps = get_function(pass.ps_entry_point);26172618vs_sampler_info.grouped_entry_point = &ps;26192620sampler_group ps_sampler_info;2621ps_sampler_info.bindings = ps.referenced_samplers;2622ps_sampler_info.grouped_entry_point = &vs;26232624for (size_t binding = 0; binding < std::min(vs_sampler_info.bindings.size(), ps_sampler_info.bindings.size()); ++binding)2625{2626if (vs_sampler_info.bindings[binding] != ps_sampler_info.bindings[binding])2627{2628if (usage_pred(vs_sampler_info.bindings[binding], ps_sampler_info.bindings[binding]))2629ps_sampler_info.bindings.insert(ps_sampler_info.bindings.begin() + binding, 0);2630else2631vs_sampler_info.bindings.insert(vs_sampler_info.bindings.begin() + binding, 0);2632}2633}26342635per_entry_point.at(&ps).sampler_groups.push_back(std::move(ps_sampler_info));2636}26372638per_entry_point.at(&vs).sampler_groups.push_back(std::move(vs_sampler_info));2639}2640}2641}26422643// Next walk through all entry point groups and shift bindings as needed so that there are no mismatches across passes.2644// If the entry points VS1, PS1 and PS2 use the following bindings (notice the mismatches of VS1 between pass 0 and pass 1, as well as PS2 between pass 1 and pass 2):2645// - pass 02646// - VS1: C A2647// - PS1: C 0 B2648// - pass 12649// - VS1: C 0 A2650// - PS2: 0 D A2651// - pass 22652// - VS2: D2653// - PS2: D A2654// Then this generates the following final bindings:2655// - pass 02656// - VS1: C 0 A2657// - PS1: C 0 B2658// - pass 12659// - VS1: C 0 A2660// - PS2: 0 D A2661// - pass 22662// - VS2: 0 D2663// - PS2: 0 D A26642665for (auto &[entry_point, entry_point_info] : per_entry_point)2666{2667while (entry_point_info.sampler_groups.size() > 1)2668{2669entry_point_info::compare_and_update_bindings(per_entry_point, entry_point_info.sampler_groups[0], entry_point_info.sampler_groups[1], 0);2670entry_point_info.sampler_groups.erase(entry_point_info.sampler_groups.begin() + 1);2671}2672}26732674for (auto &[entry_point, entry_point_info] : per_entry_point)2675{2676if (entry_point_info.sampler_groups.empty())2677continue;26782679entry_point->referenced_samplers = std::move(entry_point_info.sampler_groups[0].bindings);2680}26812682// Finally apply the generated bindings to all passes26832684for (technique &tech : _module.techniques)2685{2686for (pass &pass : tech.passes)2687{2688std::vector<id> referenced_samplers;2689std::vector<id> referenced_storages;26902691if (!pass.cs_entry_point.empty())2692{2693const function &cs = get_function(pass.cs_entry_point);26942695referenced_samplers = cs.referenced_samplers;2696referenced_storages = cs.referenced_storages;2697}2698else2699{2700const function &vs = get_function(pass.vs_entry_point);27012702referenced_samplers = vs.referenced_samplers;27032704if (!pass.ps_entry_point.empty())2705{2706const function &ps = get_function(pass.ps_entry_point);27072708if (ps.referenced_samplers.size() > referenced_samplers.size())2709referenced_samplers.resize(ps.referenced_samplers.size());27102711for (uint32_t binding = 0; binding < ps.referenced_samplers.size(); ++binding)2712if (ps.referenced_samplers[binding] != 0)2713referenced_samplers[binding] = ps.referenced_samplers[binding];2714}2715}27162717for (uint32_t binding = 0; binding < referenced_samplers.size(); ++binding)2718{2719if (referenced_samplers[binding] == 0)2720continue;27212722const sampler &sampler = get_sampler(referenced_samplers[binding]);27232724texture_binding t;2725t.texture_name = sampler.texture_name;2726t.binding = binding;2727t.srgb = sampler.srgb;2728pass.texture_bindings.push_back(std::move(t));27292730if (binding >= _module.num_texture_bindings)2731_module.num_texture_bindings = binding + 1;27322733sampler_binding s;2734s.binding = binding;2735s.filter = sampler.filter;2736s.address_u = sampler.address_u;2737s.address_v = sampler.address_v;2738s.address_w = sampler.address_w;2739s.min_lod = sampler.min_lod;2740s.max_lod = sampler.max_lod;2741s.lod_bias = sampler.lod_bias;2742pass.sampler_bindings.push_back(std::move(s));27432744if (binding >= _module.num_sampler_bindings)2745_module.num_sampler_bindings = binding + 1;2746}27472748for (uint32_t binding = 0; binding < referenced_storages.size(); ++binding)2749{2750if (referenced_storages[binding] == 0)2751continue;27522753const storage &storage = get_storage(referenced_storages[binding]);27542755storage_binding u;2756u.texture_name = storage.texture_name;2757u.binding = binding;2758u.level = storage.level;2759pass.storage_bindings.push_back(std::move(u));27602761if (binding >= _module.num_storage_bindings)2762_module.num_storage_bindings = binding + 1;2763}2764}2765}2766}276727682769