mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-30 09:35:40 +00:00
2005 lines
73 KiB
C++
2005 lines
73 KiB
C++
/*
|
|
* Copyright (C) 2014 Patrick Mours
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include "effect_lexer.hpp"
|
|
#include "effect_parser.hpp"
|
|
#include "effect_codegen.hpp"
|
|
#include <cctype> // std::toupper
|
|
#include <cassert>
|
|
#include <limits>
|
|
#include <functional>
|
|
#include <string_view>
|
|
|
|
struct on_scope_exit
|
|
{
|
|
template <typename F>
|
|
explicit on_scope_exit(F lambda) : leave(lambda) { }
|
|
~on_scope_exit() { leave(); }
|
|
|
|
std::function<void()> leave;
|
|
};
|
|
|
|
bool reshadefx::parser::parse(std::string input, codegen *backend)
|
|
{
|
|
_lexer.reset(new lexer(std::move(input)));
|
|
|
|
// Set backend for subsequent code-generation
|
|
_codegen = backend;
|
|
assert(backend != nullptr);
|
|
|
|
consume();
|
|
|
|
bool parse_success = true;
|
|
bool current_success = true;
|
|
|
|
while (!peek(tokenid::end_of_file))
|
|
{
|
|
parse_top(current_success);
|
|
if (!current_success)
|
|
parse_success = false;
|
|
}
|
|
|
|
return parse_success;
|
|
}
|
|
|
|
void reshadefx::parser::parse_top(bool &parse_success)
|
|
{
|
|
if (accept(tokenid::namespace_))
|
|
{
|
|
// Anonymous namespaces are not supported right now, so an identifier is a must
|
|
if (!expect(tokenid::identifier))
|
|
{
|
|
parse_success = false;
|
|
return;
|
|
}
|
|
|
|
const auto name = std::move(_token.literal_as_string);
|
|
|
|
if (!expect('{'))
|
|
{
|
|
parse_success = false;
|
|
return;
|
|
}
|
|
|
|
enter_namespace(name);
|
|
|
|
bool current_success = true;
|
|
bool parse_success_namespace = true;
|
|
|
|
// Recursively parse top level statements until the namespace is closed again
|
|
while (!peek('}')) // Empty namespaces are valid
|
|
{
|
|
parse_top(current_success);
|
|
if (!current_success)
|
|
parse_success_namespace = false;
|
|
}
|
|
|
|
leave_namespace();
|
|
|
|
parse_success = expect('}') && parse_success_namespace;
|
|
}
|
|
else if (accept(tokenid::struct_)) // Structure keyword found, parse the structure definition
|
|
{
|
|
// Structure definitions are terminated with a semicolon
|
|
parse_success = parse_struct() && expect(';');
|
|
}
|
|
else if (accept(tokenid::technique)) // Technique keyword found, parse the technique definition
|
|
{
|
|
parse_success = parse_technique();
|
|
}
|
|
else
|
|
{
|
|
if (type type; parse_type(type)) // Type found, this can be either a variable or a function declaration
|
|
{
|
|
parse_success = expect(tokenid::identifier);
|
|
if (!parse_success)
|
|
return;
|
|
|
|
if (peek('('))
|
|
{
|
|
const auto name = std::move(_token.literal_as_string);
|
|
// This is definitely a function declaration, so parse it
|
|
if (!parse_function(type, name))
|
|
{
|
|
// Insert dummy function into symbol table, so later references can be resolved despite the error
|
|
insert_symbol(name, { symbol_type::function, ~0u, { type::t_function } }, true);
|
|
parse_success = false;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// There may be multiple variable names after the type, handle them all
|
|
unsigned int count = 0;
|
|
do {
|
|
if (count++ > 0 && !(expect(',') && expect(tokenid::identifier)))
|
|
{
|
|
parse_success = false;
|
|
return;
|
|
}
|
|
const auto name = std::move(_token.literal_as_string);
|
|
if (!parse_variable(type, name, true))
|
|
{
|
|
// Insert dummy variable into symbol table, so later references can be resolved despite the error
|
|
insert_symbol(name, { symbol_type::variable, ~0u, type }, true);
|
|
// Skip the rest of the statement
|
|
consume_until(';');
|
|
parse_success = false;
|
|
return;
|
|
}
|
|
} while (!peek(';'));
|
|
|
|
// Variable declarations are terminated with a semicolon
|
|
parse_success = expect(';');
|
|
}
|
|
}
|
|
else if (accept(';')) // Ignore single semicolons in the source
|
|
{
|
|
parse_success = true;
|
|
}
|
|
else
|
|
{
|
|
// Unexpected token in source stream, consume and report an error about it
|
|
consume();
|
|
// Only add another error message if succeeded parsing previously
|
|
// This is done to avoid walls of error messages because of consequential errors following a top-level syntax mistake
|
|
if (parse_success)
|
|
error(_token.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token.id) + '\'');
|
|
parse_success = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool reshadefx::parser::parse_statement(bool scoped)
|
|
{
|
|
if (!_codegen->is_in_block())
|
|
return error(_token_next.location, 0, "unreachable code"), false;
|
|
|
|
unsigned int loop_control = 0;
|
|
unsigned int selection_control = 0;
|
|
|
|
// Read any loop and branch control attributes first
|
|
while (accept('['))
|
|
{
|
|
enum control_mask
|
|
{
|
|
unroll = 0x1,
|
|
dont_unroll = 0x2,
|
|
flatten = (0x1 << 4),
|
|
dont_flatten = (0x2 << 4),
|
|
switch_force_case = (0x4 << 4),
|
|
switch_call = (0x8 << 4)
|
|
};
|
|
|
|
const auto attribute = std::move(_token_next.literal_as_string);
|
|
|
|
if (!expect(tokenid::identifier) || !expect(']'))
|
|
return false;
|
|
|
|
if (attribute == "unroll")
|
|
loop_control |= unroll;
|
|
else if (attribute == "loop" || attribute == "fastopt")
|
|
loop_control |= dont_unroll;
|
|
else if (attribute == "flatten")
|
|
selection_control |= flatten;
|
|
else if (attribute == "branch")
|
|
selection_control |= dont_flatten;
|
|
else if (attribute == "forcecase")
|
|
selection_control |= switch_force_case;
|
|
else if (attribute == "call")
|
|
selection_control |= switch_call;
|
|
else
|
|
warning(_token.location, 0, "unknown attribute");
|
|
|
|
if ((loop_control & (unroll | dont_unroll)) == (unroll | dont_unroll))
|
|
return error(_token.location, 3524, "can't use loop and unroll attributes together"), false;
|
|
if ((selection_control & (flatten | dont_flatten)) == (flatten | dont_flatten))
|
|
return error(_token.location, 3524, "can't use branch and flatten attributes together"), false;
|
|
}
|
|
|
|
// Shift by two so that the possible values are 0x01 for 'flatten' and 0x02 for 'dont_flatten', equivalent to 'unroll' and 'dont_unroll'
|
|
selection_control >>= 4;
|
|
|
|
if (peek('{')) // Parse statement block
|
|
return parse_statement_block(scoped);
|
|
else if (accept(';')) // Ignore empty statements
|
|
return true;
|
|
|
|
// Most statements with the exception of declarations are only valid inside functions
|
|
if (_codegen->is_in_function())
|
|
{
|
|
assert(_current_function != nullptr);
|
|
|
|
const auto location = _token_next.location;
|
|
|
|
if (accept(tokenid::if_))
|
|
{
|
|
codegen::id true_block = _codegen->create_block(); // Block which contains the statements executed when the condition is true
|
|
codegen::id false_block = _codegen->create_block(); // Block which contains the statements executed when the condition is false
|
|
const codegen::id merge_block = _codegen->create_block(); // Block that is executed after the branch re-merged with the current control flow
|
|
|
|
expression condition;
|
|
if (!expect('(') || !parse_expression(condition) || !expect(')'))
|
|
return false;
|
|
else if (!condition.type.is_scalar())
|
|
return error(condition.location, 3019, "if statement conditional expressions must evaluate to a scalar"), false;
|
|
|
|
// Load condition and convert to boolean value as required by 'OpBranchConditional'
|
|
condition.add_cast_operation({ type::t_bool, 1, 1 });
|
|
|
|
const codegen::id condition_value = _codegen->emit_load(condition);
|
|
const codegen::id condition_block = _codegen->leave_block_and_branch_conditional(condition_value, true_block, false_block);
|
|
|
|
{ // Then block of the if statement
|
|
_codegen->enter_block(true_block);
|
|
|
|
if (!parse_statement(true))
|
|
return false;
|
|
|
|
true_block = _codegen->leave_block_and_branch(merge_block);
|
|
}
|
|
{ // Else block of the if statement
|
|
_codegen->enter_block(false_block);
|
|
|
|
if (accept(tokenid::else_) && !parse_statement(true))
|
|
return false;
|
|
|
|
false_block = _codegen->leave_block_and_branch(merge_block);
|
|
}
|
|
|
|
_codegen->enter_block(merge_block);
|
|
|
|
// Emit structured control flow for an if statement and connect all basic blocks
|
|
_codegen->emit_if(location, condition_value, condition_block, true_block, false_block, selection_control);
|
|
|
|
return true;
|
|
}
|
|
|
|
if (accept(tokenid::switch_))
|
|
{
|
|
const codegen::id merge_block = _codegen->create_block(); // Block that is executed after the switch re-merged with the current control flow
|
|
|
|
expression selector_exp;
|
|
if (!expect('(') || !parse_expression(selector_exp) || !expect(')'))
|
|
return false;
|
|
else if (!selector_exp.type.is_scalar())
|
|
return error(selector_exp.location, 3019, "switch statement expression must evaluate to a scalar"), false;
|
|
|
|
// Load selector and convert to integral value as required by switch instruction
|
|
selector_exp.add_cast_operation({ type::t_int, 1, 1 });
|
|
|
|
const auto selector_value = _codegen->emit_load(selector_exp);
|
|
const auto selector_block = _codegen->leave_block_and_switch(selector_value, merge_block);
|
|
|
|
if (!expect('{'))
|
|
return false;
|
|
|
|
_loop_break_target_stack.push_back(merge_block);
|
|
on_scope_exit _([this]() { _loop_break_target_stack.pop_back(); });
|
|
|
|
bool parse_success = true;
|
|
// The default case jumps to the end of the switch statement if not overwritten
|
|
codegen::id default_label = merge_block, default_block = merge_block;
|
|
codegen::id current_label = _codegen->create_block();
|
|
std::vector<codegen::id> case_literal_and_labels, case_blocks;
|
|
size_t last_case_label_index = 0;
|
|
|
|
// Enter first switch statement body block
|
|
_codegen->enter_block(current_label);
|
|
|
|
while (!peek(tokenid::end_of_file))
|
|
{
|
|
while (accept(tokenid::case_) || accept(tokenid::default_))
|
|
{
|
|
if (_token.id == tokenid::case_)
|
|
{
|
|
expression case_label;
|
|
if (!parse_expression(case_label))
|
|
return consume_until('}'), false;
|
|
else if (!case_label.type.is_scalar() || !case_label.type.is_integral() || !case_label.is_constant)
|
|
return error(case_label.location, 3020, "invalid type for case expression - value must be an integer scalar"), consume_until('}'), false;
|
|
|
|
// Check for duplicate case values
|
|
for (size_t i = 0; i < case_literal_and_labels.size(); i += 2)
|
|
{
|
|
if (case_literal_and_labels[i] == case_label.constant.as_uint[0])
|
|
{
|
|
parse_success = false;
|
|
error(case_label.location, 3532, "duplicate case " + std::to_string(case_label.constant.as_uint[0]));
|
|
break;
|
|
}
|
|
}
|
|
|
|
case_blocks.emplace_back(); // This is set to the actual block below
|
|
case_literal_and_labels.push_back(case_label.constant.as_uint[0]);
|
|
case_literal_and_labels.push_back(current_label);
|
|
}
|
|
else
|
|
{
|
|
// Check if the default label was already changed by a previous 'default' statement
|
|
if (default_label != merge_block)
|
|
{
|
|
parse_success = false;
|
|
error(_token.location, 3532, "duplicate default in switch statement");
|
|
}
|
|
|
|
default_label = current_label;
|
|
default_block = 0; // This is set to the actual block below
|
|
}
|
|
|
|
if (!expect(':'))
|
|
return consume_until('}'), false;
|
|
}
|
|
|
|
// It is valid for no statement to follow if this is the last label in the switch body
|
|
const bool end_of_switch = peek('}');
|
|
|
|
if (!end_of_switch && !parse_statement(true))
|
|
return consume_until('}'), false;
|
|
|
|
// Handle fall-through case and end of switch statement
|
|
if (peek(tokenid::case_) || peek(tokenid::default_) || end_of_switch)
|
|
{
|
|
if (_codegen->is_in_block()) // Disallow fall-through for now
|
|
{
|
|
parse_success = false;
|
|
error(_token_next.location, 3533, "non-empty case statements must have break or return");
|
|
}
|
|
|
|
const codegen::id next_label = end_of_switch ? merge_block : _codegen->create_block();
|
|
// This is different from 'current_label', since there may have been branching logic inside the case, which would have changed the active block
|
|
const codegen::id current_block = _codegen->leave_block_and_branch(next_label);
|
|
|
|
if (0 == default_block)
|
|
default_block = current_block;
|
|
for (size_t i = last_case_label_index; i < case_blocks.size(); ++i)
|
|
// Need to use the initial label for the switch table, but the current block to merge all the block data
|
|
case_blocks[i] = current_block;
|
|
|
|
current_label = next_label;
|
|
_codegen->enter_block(current_label);
|
|
|
|
if (end_of_switch) // We reached the end, nothing more to do
|
|
break;
|
|
|
|
last_case_label_index = case_blocks.size();
|
|
}
|
|
}
|
|
|
|
if (case_literal_and_labels.empty() && default_label == merge_block)
|
|
warning(location, 5002, "switch statement contains no 'case' or 'default' labels");
|
|
|
|
// Emit structured control flow for a switch statement and connect all basic blocks
|
|
_codegen->emit_switch(location, selector_value, selector_block, default_label, default_block, case_literal_and_labels, case_blocks, selection_control);
|
|
|
|
return expect('}') && parse_success;
|
|
}
|
|
|
|
if (accept(tokenid::for_))
|
|
{
|
|
if (!expect('('))
|
|
return false;
|
|
|
|
enter_scope();
|
|
on_scope_exit _([this]() { leave_scope(); });
|
|
|
|
// Parse initializer first
|
|
if (type type; parse_type(type))
|
|
{
|
|
unsigned int count = 0;
|
|
do { // There may be multiple declarations behind a type, so loop through them
|
|
if (count++ > 0 && !expect(','))
|
|
return false;
|
|
if (!expect(tokenid::identifier) || !parse_variable(type, std::move(_token.literal_as_string)))
|
|
return false;
|
|
} while (!peek(';'));
|
|
}
|
|
else
|
|
{
|
|
// Initializer can also contain an expression if not a variable declaration list and not empty
|
|
if (!peek(';'))
|
|
{
|
|
expression expression;
|
|
if (!parse_expression(expression))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!expect(';'))
|
|
return false;
|
|
|
|
const codegen::id merge_block = _codegen->create_block(); // Block that is executed after the loop
|
|
const codegen::id header_label = _codegen->create_block(); // Pointer to the loop merge instruction
|
|
const codegen::id continue_label = _codegen->create_block(); // Pointer to the continue block
|
|
codegen::id loop_block = _codegen->create_block(); // Pointer to the main loop body block
|
|
codegen::id condition_block = _codegen->create_block(); // Pointer to the condition check
|
|
codegen::id condition_value = 0;
|
|
|
|
// End current block by branching to the next label
|
|
const codegen::id prev_block = _codegen->leave_block_and_branch(header_label);
|
|
|
|
{ // Begin loop block (this header is used for explicit structured control flow)
|
|
_codegen->enter_block(header_label);
|
|
|
|
_codegen->leave_block_and_branch(condition_block);
|
|
}
|
|
|
|
{ // Parse condition block
|
|
_codegen->enter_block(condition_block);
|
|
|
|
if (!peek(';'))
|
|
{
|
|
expression condition;
|
|
if (!parse_expression(condition))
|
|
return false;
|
|
|
|
if (!condition.type.is_scalar())
|
|
return error(condition.location, 3019, "scalar value expected"), false;
|
|
|
|
// Evaluate condition and branch to the right target
|
|
condition.add_cast_operation({ type::t_bool, 1, 1 });
|
|
|
|
condition_value = _codegen->emit_load(condition);
|
|
|
|
condition_block = _codegen->leave_block_and_branch_conditional(condition_value, loop_block, merge_block);
|
|
}
|
|
else // It is valid for there to be no condition expression
|
|
{
|
|
condition_block = _codegen->leave_block_and_branch(loop_block);
|
|
}
|
|
|
|
if (!expect(';'))
|
|
return false;
|
|
}
|
|
|
|
{ // Parse loop continue block into separate block so it can be appended to the end down the line
|
|
_codegen->enter_block(continue_label);
|
|
|
|
if (!peek(')'))
|
|
{
|
|
expression continue_exp;
|
|
if (!parse_expression(continue_exp))
|
|
return false;
|
|
}
|
|
|
|
if (!expect(')'))
|
|
return false;
|
|
|
|
// Branch back to the loop header at the end of the continue block
|
|
_codegen->leave_block_and_branch(header_label);
|
|
}
|
|
|
|
{ // Parse loop body block
|
|
_codegen->enter_block(loop_block);
|
|
|
|
_loop_break_target_stack.push_back(merge_block);
|
|
_loop_continue_target_stack.push_back(continue_label);
|
|
|
|
const bool parse_success = parse_statement(false);
|
|
|
|
_loop_break_target_stack.pop_back();
|
|
_loop_continue_target_stack.pop_back();
|
|
|
|
if (!parse_success)
|
|
return false;
|
|
|
|
loop_block = _codegen->leave_block_and_branch(continue_label);
|
|
}
|
|
|
|
// Add merge block label to the end of the loop
|
|
_codegen->enter_block(merge_block);
|
|
|
|
// Emit structured control flow for a loop statement and connect all basic blocks
|
|
_codegen->emit_loop(location, condition_value, prev_block, header_label, condition_block, loop_block, continue_label, loop_control);
|
|
|
|
return true;
|
|
}
|
|
|
|
if (accept(tokenid::while_))
|
|
{
|
|
enter_scope();
|
|
on_scope_exit _([this]() { leave_scope(); });
|
|
|
|
const codegen::id merge_block = _codegen->create_block();
|
|
const codegen::id header_label = _codegen->create_block();
|
|
const codegen::id continue_label = _codegen->create_block();
|
|
codegen::id loop_block = _codegen->create_block();
|
|
codegen::id condition_block = _codegen->create_block();
|
|
codegen::id condition_value = 0;
|
|
|
|
// End current block by branching to the next label
|
|
const codegen::id prev_block = _codegen->leave_block_and_branch(header_label);
|
|
|
|
{ // Begin loop block
|
|
_codegen->enter_block(header_label);
|
|
|
|
_codegen->leave_block_and_branch(condition_block);
|
|
}
|
|
|
|
{ // Parse condition block
|
|
_codegen->enter_block(condition_block);
|
|
|
|
expression condition;
|
|
if (!expect('(') || !parse_expression(condition) || !expect(')'))
|
|
return false;
|
|
else if (!condition.type.is_scalar())
|
|
return error(condition.location, 3019, "scalar value expected"), false;
|
|
|
|
// Evaluate condition and branch to the right target
|
|
condition.add_cast_operation({ type::t_bool, 1, 1 });
|
|
|
|
condition_value = _codegen->emit_load(condition);
|
|
|
|
condition_block = _codegen->leave_block_and_branch_conditional(condition_value, loop_block, merge_block);
|
|
}
|
|
|
|
{ // Parse loop body block
|
|
_codegen->enter_block(loop_block);
|
|
|
|
_loop_break_target_stack.push_back(merge_block);
|
|
_loop_continue_target_stack.push_back(continue_label);
|
|
|
|
const bool parse_success = parse_statement(false);
|
|
|
|
_loop_break_target_stack.pop_back();
|
|
_loop_continue_target_stack.pop_back();
|
|
|
|
if (!parse_success)
|
|
return false;
|
|
|
|
loop_block = _codegen->leave_block_and_branch(continue_label);
|
|
}
|
|
|
|
{ // Branch back to the loop header in empty continue block
|
|
_codegen->enter_block(continue_label);
|
|
|
|
_codegen->leave_block_and_branch(header_label);
|
|
}
|
|
|
|
// Add merge block label to the end of the loop
|
|
_codegen->enter_block(merge_block);
|
|
|
|
// Emit structured control flow for a loop statement and connect all basic blocks
|
|
_codegen->emit_loop(location, condition_value, prev_block, header_label, condition_block, loop_block, continue_label, loop_control);
|
|
|
|
return true;
|
|
}
|
|
|
|
if (accept(tokenid::do_))
|
|
{
|
|
const codegen::id merge_block = _codegen->create_block();
|
|
const codegen::id header_label = _codegen->create_block();
|
|
const codegen::id continue_label = _codegen->create_block();
|
|
codegen::id loop_block = _codegen->create_block();
|
|
codegen::id condition_value = 0;
|
|
|
|
// End current block by branching to the next label
|
|
const codegen::id prev_block = _codegen->leave_block_and_branch(header_label);
|
|
|
|
{ // Begin loop block
|
|
_codegen->enter_block(header_label);
|
|
|
|
_codegen->leave_block_and_branch(loop_block);
|
|
}
|
|
|
|
{ // Parse loop body block
|
|
_codegen->enter_block(loop_block);
|
|
|
|
_loop_break_target_stack.push_back(merge_block);
|
|
_loop_continue_target_stack.push_back(continue_label);
|
|
|
|
const bool parse_success = parse_statement(true);
|
|
|
|
_loop_break_target_stack.pop_back();
|
|
_loop_continue_target_stack.pop_back();
|
|
|
|
if (!parse_success)
|
|
return false;
|
|
|
|
loop_block = _codegen->leave_block_and_branch(continue_label);
|
|
}
|
|
|
|
{ // Continue block does the condition evaluation
|
|
_codegen->enter_block(continue_label);
|
|
|
|
expression condition;
|
|
if (!expect(tokenid::while_) || !expect('(') || !parse_expression(condition) || !expect(')') || !expect(';'))
|
|
return false;
|
|
else if (!condition.type.is_scalar())
|
|
return error(condition.location, 3019, "scalar value expected"), false;
|
|
|
|
// Evaluate condition and branch to the right target
|
|
condition.add_cast_operation({ type::t_bool, 1, 1 });
|
|
|
|
condition_value = _codegen->emit_load(condition);
|
|
|
|
_codegen->leave_block_and_branch_conditional(condition_value, header_label, merge_block);
|
|
}
|
|
|
|
// Add merge block label to the end of the loop
|
|
_codegen->enter_block(merge_block);
|
|
|
|
// Emit structured control flow for a loop statement and connect all basic blocks
|
|
_codegen->emit_loop(location, condition_value, prev_block, header_label, 0, loop_block, continue_label, loop_control);
|
|
|
|
return true;
|
|
}
|
|
|
|
if (accept(tokenid::break_))
|
|
{
|
|
if (_loop_break_target_stack.empty())
|
|
return error(location, 3518, "break must be inside loop"), false;
|
|
|
|
// Branch to the break target of the inner most loop on the stack
|
|
_codegen->leave_block_and_branch(_loop_break_target_stack.back(), 1);
|
|
|
|
return expect(';');
|
|
}
|
|
|
|
if (accept(tokenid::continue_))
|
|
{
|
|
if (_loop_continue_target_stack.empty())
|
|
return error(location, 3519, "continue must be inside loop"), false;
|
|
|
|
// Branch to the continue target of the inner most loop on the stack
|
|
_codegen->leave_block_and_branch(_loop_continue_target_stack.back(), 2);
|
|
|
|
return expect(';');
|
|
}
|
|
|
|
if (accept(tokenid::return_))
|
|
{
|
|
const type &ret_type = _current_function->return_type;
|
|
|
|
if (!peek(';'))
|
|
{
|
|
expression expression;
|
|
if (!parse_expression(expression))
|
|
return consume_until(';'), false;
|
|
|
|
// Cannot return to void
|
|
if (ret_type.is_void())
|
|
// Consume the semicolon that follows the return expression so that parsing may continue
|
|
return error(location, 3079, "void functions cannot return a value"), accept(';'), false;
|
|
|
|
// Cannot return arrays from a function
|
|
if (expression.type.is_array() || !type::rank(expression.type, ret_type))
|
|
return error(location, 3017, "expression (" + expression.type.description() + ") does not match function return type (" + ret_type.description() + ')'), accept(';'), false;
|
|
|
|
// Load return value and perform implicit cast to function return type
|
|
if (expression.type.components() > ret_type.components())
|
|
warning(expression.location, 3206, "implicit truncation of vector type");
|
|
|
|
expression.add_cast_operation(ret_type);
|
|
|
|
const auto return_value = _codegen->emit_load(expression);
|
|
|
|
_codegen->leave_block_and_return(return_value);
|
|
}
|
|
else if (!ret_type.is_void())
|
|
{
|
|
// No return value was found, but the function expects one
|
|
error(location, 3080, "function must return a value");
|
|
|
|
// Consume the semicolon that follows the return expression so that parsing may continue
|
|
accept(';');
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
_codegen->leave_block_and_return();
|
|
}
|
|
|
|
return expect(';');
|
|
}
|
|
|
|
if (accept(tokenid::discard_))
|
|
{
|
|
// Leave the current function block
|
|
_codegen->leave_block_and_kill();
|
|
|
|
return expect(';');
|
|
}
|
|
}
|
|
|
|
// Handle variable declarations
|
|
if (type type; parse_type(type))
|
|
{
|
|
unsigned int count = 0;
|
|
do { // There may be multiple declarations behind a type, so loop through them
|
|
if (count++ > 0 && !expect(','))
|
|
// Try to consume the rest of the declaration so that parsing may continue despite the error
|
|
return consume_until(';'), false;
|
|
if (!expect(tokenid::identifier) || !parse_variable(type, std::move(_token.literal_as_string)))
|
|
return consume_until(';'), false;
|
|
} while (!peek(';'));
|
|
|
|
return expect(';');
|
|
}
|
|
|
|
// Handle expression statements
|
|
if (expression expression; parse_expression(expression))
|
|
return expect(';'); // A statement has to be terminated with a semicolon
|
|
|
|
// Gracefully consume any remaining characters until the statement would usually end, so that parsing may continue despite the error
|
|
consume_until(';');
|
|
|
|
return false;
|
|
}
|
|
bool reshadefx::parser::parse_statement_block(bool scoped)
|
|
{
|
|
if (!expect('{'))
|
|
return false;
|
|
|
|
if (scoped)
|
|
enter_scope();
|
|
|
|
// Parse statements until the end of the block is reached
|
|
while (!peek('}') && !peek(tokenid::end_of_file))
|
|
{
|
|
if (!parse_statement(true))
|
|
{
|
|
if (scoped)
|
|
leave_scope();
|
|
|
|
// Ignore the rest of this block
|
|
unsigned int level = 0;
|
|
|
|
while (!peek(tokenid::end_of_file))
|
|
{
|
|
if (accept('{'))
|
|
{
|
|
++level;
|
|
}
|
|
else if (accept('}'))
|
|
{
|
|
if (level-- == 0)
|
|
break;
|
|
} // These braces are necessary to match the 'else' to the correct 'if' statement
|
|
else
|
|
{
|
|
consume();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (scoped)
|
|
leave_scope();
|
|
|
|
return expect('}');
|
|
}
|
|
|
|
bool reshadefx::parser::parse_type(type &type)
|
|
{
|
|
type.qualifiers = 0;
|
|
|
|
accept_type_qualifiers(type);
|
|
|
|
if (!accept_type_class(type))
|
|
return false;
|
|
|
|
if (type.is_integral() && (type.has(type::q_centroid) || type.has(type::q_noperspective)))
|
|
return error(_token.location, 4576, "signature specifies invalid interpolation mode for integer component type"), false;
|
|
else if (type.has(type::q_centroid) && !type.has(type::q_noperspective))
|
|
type.qualifiers |= type::q_linear;
|
|
|
|
return true;
|
|
}
|
|
bool reshadefx::parser::parse_array_size(type &type)
|
|
{
|
|
// Reset array length to zero before checking if one exists
|
|
type.array_length = 0;
|
|
|
|
if (accept('['))
|
|
{
|
|
if (accept(']'))
|
|
{
|
|
// No length expression, so this is an unsized array
|
|
type.array_length = -1;
|
|
}
|
|
else if (expression expression; parse_expression(expression) && expect(']'))
|
|
{
|
|
if (!expression.is_constant || !(expression.type.is_scalar() && expression.type.is_integral()))
|
|
return error(expression.location, 3058, "array dimensions must be literal scalar expressions"), false;
|
|
|
|
type.array_length = expression.constant.as_uint[0];
|
|
|
|
if (type.array_length < 1 || type.array_length > 65536)
|
|
return error(expression.location, 3059, "array dimension must be between 1 and 65536"), false;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Multi-dimensional arrays are not supported
|
|
if (peek('['))
|
|
return error(_token_next.location, 3119, "arrays cannot be multi-dimensional"), false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool reshadefx::parser::parse_annotations(std::vector<annotation> &annotations)
|
|
{
|
|
// Check if annotations exist and return early if none do
|
|
if (!accept('<'))
|
|
return true;
|
|
|
|
bool parse_success = true;
|
|
|
|
while (!peek('>'))
|
|
{
|
|
if (type type; accept_type_class(type))
|
|
warning(_token.location, 4717, "type prefixes for annotations are deprecated and ignored");
|
|
|
|
if (!expect(tokenid::identifier))
|
|
return consume_until('>'), false;
|
|
|
|
auto name = std::move(_token.literal_as_string);
|
|
|
|
if (expression expression; !expect('=') || !parse_expression_multary(expression) || !expect(';'))
|
|
return consume_until('>'), false;
|
|
else if (expression.is_constant)
|
|
annotations.push_back({ expression.type, std::move(name), std::move(expression.constant) });
|
|
else // Continue parsing annotations despite this not being a constant, since the syntax is still correct
|
|
parse_success = false,
|
|
error(expression.location, 3011, "value must be a literal expression");
|
|
}
|
|
|
|
return expect('>') && parse_success;
|
|
}
|
|
|
|
bool reshadefx::parser::parse_struct()
|
|
{
|
|
const auto location = std::move(_token.location);
|
|
|
|
struct_info info;
|
|
// The structure name is optional
|
|
if (accept(tokenid::identifier))
|
|
info.name = std::move(_token.literal_as_string);
|
|
else
|
|
info.name = "_anonymous_struct_" + std::to_string(location.line) + '_' + std::to_string(location.column);
|
|
|
|
info.unique_name = 'S' + current_scope().name + info.name;
|
|
std::replace(info.unique_name.begin(), info.unique_name.end(), ':', '_');
|
|
|
|
if (!expect('{'))
|
|
return false;
|
|
|
|
bool parse_success = true;
|
|
|
|
while (!peek('}')) // Empty structures are possible
|
|
{
|
|
struct_member_info member;
|
|
|
|
if (!parse_type(member.type))
|
|
return error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected struct member type"), consume_until('}'), accept(';'), false;
|
|
|
|
unsigned int count = 0;
|
|
do {
|
|
if (count++ > 0 && !expect(','))
|
|
return consume_until('}'), accept(';'), false;
|
|
|
|
if (!expect(tokenid::identifier))
|
|
return consume_until('}'), accept(';'), false;
|
|
|
|
member.name = std::move(_token.literal_as_string);
|
|
member.location = std::move(_token.location);
|
|
|
|
if (member.type.is_void())
|
|
parse_success = false,
|
|
error(member.location, 3038, '\'' + member.name + "': struct members cannot be void");
|
|
if (member.type.is_struct()) // Nesting structures would make input/output argument flattening more complicated, so prevent it for now
|
|
parse_success = false,
|
|
error(member.location, 3090, '\'' + member.name + "': nested struct members are not supported");
|
|
|
|
if (member.type.has(type::q_in) || member.type.has(type::q_out))
|
|
parse_success = false,
|
|
error(member.location, 3055, '\'' + member.name + "': struct members cannot be declared 'in' or 'out'");
|
|
if (member.type.has(type::q_const))
|
|
parse_success = false,
|
|
error(member.location, 3035, '\'' + member.name + "': struct members cannot be declared 'const'");
|
|
if (member.type.has(type::q_extern))
|
|
parse_success = false,
|
|
error(member.location, 3006, '\'' + member.name + "': struct members cannot be declared 'extern'");
|
|
if (member.type.has(type::q_static))
|
|
parse_success = false,
|
|
error(member.location, 3007, '\'' + member.name + "': struct members cannot be declared 'static'");
|
|
if (member.type.has(type::q_uniform))
|
|
parse_success = false,
|
|
error(member.location, 3047, '\'' + member.name + "': struct members cannot be declared 'uniform'");
|
|
if (member.type.has(type::q_groupshared))
|
|
parse_success = false,
|
|
error(member.location, 3010, '\'' + member.name + "': struct members cannot be declared 'groupshared'");
|
|
|
|
// Modify member specific type, so that following members in the declaration list are not affected by this
|
|
if (!parse_array_size(member.type))
|
|
return consume_until('}'), accept(';'), false;
|
|
else if (member.type.array_length < 0)
|
|
parse_success = false,
|
|
error(member.location, 3072, '\'' + member.name + "': array dimensions of struct members must be explicit");
|
|
|
|
// Structure members may have semantics to use them as input/output types
|
|
if (accept(':'))
|
|
{
|
|
if (!expect(tokenid::identifier))
|
|
return consume_until('}'), accept(';'), false;
|
|
|
|
member.semantic = std::move(_token.literal_as_string);
|
|
// Make semantic upper case to simplify comparison later on
|
|
std::transform(member.semantic.begin(), member.semantic.end(), member.semantic.begin(),
|
|
[](std::string::value_type c) {
|
|
return static_cast<std::string::value_type>(std::toupper(c));
|
|
});
|
|
|
|
if (member.semantic.compare(0, 3, "SV_") != 0)
|
|
{
|
|
// Always numerate semantics, so that e.g. TEXCOORD and TEXCOORD0 point to the same location
|
|
if (const char c = member.semantic.back(); c < '0' || c > '9')
|
|
member.semantic += '0';
|
|
|
|
if (member.type.is_integral() && !member.type.has(type::q_nointerpolation))
|
|
{
|
|
member.type.qualifiers |= type::q_nointerpolation; // Integer fields do not interpolate, so make this explicit (to avoid issues with GLSL)
|
|
warning(member.location, 4568, '\'' + member.name + "': integer fields have the 'nointerpolation' qualifier by default");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Remove optional trailing zero from system value semantics, so that e.g. SV_POSITION and SV_POSITION0 mean the same thing
|
|
if (member.semantic.back() == '0' && (member.semantic[member.semantic.size() - 2] < '0' || member.semantic[member.semantic.size() - 2] > '9'))
|
|
member.semantic.pop_back();
|
|
}
|
|
}
|
|
|
|
// Save member name and type for book keeping
|
|
info.member_list.push_back(member);
|
|
} while (!peek(';'));
|
|
|
|
if (!expect(';'))
|
|
return consume_until('}'), accept(';'), false;
|
|
}
|
|
|
|
// Empty structures are valid, but not usually intended, so emit a warning
|
|
if (info.member_list.empty())
|
|
warning(location, 5001, "struct has no members");
|
|
|
|
// Define the structure now that information about all the member types was gathered
|
|
const auto id = _codegen->define_struct(location, info);
|
|
|
|
// Insert the symbol into the symbol table
|
|
const symbol symbol = { symbol_type::structure, id };
|
|
|
|
if (!insert_symbol(info.name, symbol, true))
|
|
return error(location, 3003, "redefinition of '" + info.name + '\''), false;
|
|
|
|
return expect('}') && parse_success;
|
|
}
|
|
|
|
bool reshadefx::parser::parse_function(type type, std::string name)
|
|
{
|
|
const auto location = std::move(_token.location);
|
|
|
|
if (!expect('(')) // Functions always have a parameter list
|
|
return false;
|
|
if (type.qualifiers != 0)
|
|
return error(location, 3047, '\'' + name + "': function return type cannot have any qualifiers"), false;
|
|
|
|
function_info info;
|
|
info.name = name;
|
|
info.unique_name = 'F' + current_scope().name + name;
|
|
std::replace(info.unique_name.begin(), info.unique_name.end(), ':', '_');
|
|
|
|
info.return_type = type;
|
|
_current_function = &info;
|
|
|
|
bool parse_success = true;
|
|
bool expect_parenthesis = true;
|
|
|
|
// Enter function scope (and leave it again when finished parsing this function)
|
|
enter_scope();
|
|
on_scope_exit _([this]() {
|
|
leave_scope();
|
|
_codegen->leave_function();
|
|
_current_function = nullptr;
|
|
});
|
|
|
|
while (!peek(')'))
|
|
{
|
|
if (!info.parameter_list.empty() && !expect(','))
|
|
{
|
|
parse_success = false;
|
|
expect_parenthesis = false;
|
|
consume_until(')');
|
|
break;
|
|
}
|
|
|
|
struct_member_info param;
|
|
|
|
if (!parse_type(param.type))
|
|
{
|
|
error(_token_next.location, 3000, "syntax error: unexpected '" + token::id_to_name(_token_next.id) + "', expected parameter type");
|
|
parse_success = false;
|
|
expect_parenthesis = false;
|
|
consume_until(')');
|
|
break;
|
|
}
|
|
|
|
if (!expect(tokenid::identifier))
|
|
{
|
|
parse_success = false;
|
|
expect_parenthesis = false;
|
|
consume_until(')');
|
|
break;
|
|
}
|
|
|
|
param.name = std::move(_token.literal_as_string);
|
|
param.location = std::move(_token.location);
|
|
|
|
if (param.type.is_void())
|
|
parse_success = false,
|
|
error(param.location, 3038, '\'' + param.name + "': function parameters cannot be void");
|
|
|
|
if (param.type.has(type::q_extern))
|
|
parse_success = false,
|
|
error(param.location, 3006, '\'' + param.name + "': function parameters cannot be declared 'extern'");
|
|
if (param.type.has(type::q_static))
|
|
parse_success = false,
|
|
error(param.location, 3007, '\'' + param.name + "': function parameters cannot be declared 'static'");
|
|
if (param.type.has(type::q_uniform))
|
|
parse_success = false,
|
|
error(param.location, 3047, '\'' + param.name + "': function parameters cannot be declared 'uniform', consider placing in global scope instead");
|
|
if (param.type.has(type::q_groupshared))
|
|
parse_success = false,
|
|
error(param.location, 3010, '\'' + param.name + "': function parameters cannot be declared 'groupshared'");
|
|
|
|
if (param.type.has(type::q_out) && param.type.has(type::q_const))
|
|
parse_success = false,
|
|
error(param.location, 3046, '\'' + param.name + "': output parameters cannot be declared 'const'");
|
|
else if (!param.type.has(type::q_out))
|
|
param.type.qualifiers |= type::q_in; // Function parameters are implicitly 'in' if not explicitly defined as 'out'
|
|
|
|
if (!parse_array_size(param.type))
|
|
{
|
|
parse_success = false;
|
|
expect_parenthesis = false;
|
|
consume_until(')');
|
|
break;
|
|
}
|
|
else if (param.type.array_length < 0)
|
|
{
|
|
parse_success = false;
|
|
error(param.location, 3072, '\'' + param.name + "': array dimensions of function parameters must be explicit");
|
|
}
|
|
|
|
// Handle parameter type semantic
|
|
if (accept(':'))
|
|
{
|
|
if (!expect(tokenid::identifier))
|
|
{
|
|
parse_success = false;
|
|
expect_parenthesis = false;
|
|
consume_until(')');
|
|
break;
|
|
}
|
|
|
|
param.semantic = std::move(_token.literal_as_string);
|
|
// Make semantic upper case to simplify comparison later on
|
|
std::transform(param.semantic.begin(), param.semantic.end(), param.semantic.begin(),
|
|
[](std::string::value_type c) {
|
|
return static_cast<std::string::value_type>(std::toupper(c));
|
|
});
|
|
|
|
if (param.semantic.compare(0, 3, "SV_") != 0)
|
|
{
|
|
// Always numerate semantics, so that e.g. TEXCOORD and TEXCOORD0 point to the same location
|
|
if (const char c = param.semantic.back(); c < '0' || c > '9')
|
|
param.semantic += '0';
|
|
|
|
if (param.type.is_integral() && !param.type.has(type::q_nointerpolation))
|
|
{
|
|
param.type.qualifiers |= type::q_nointerpolation; // Integer parameters do not interpolate, so make this explicit (to avoid issues with GLSL)
|
|
warning(param.location, 4568, '\'' + param.name + "': integer parameters have the 'nointerpolation' qualifier by default");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Remove optional trailing zero from system value semantics, so that e.g. SV_POSITION and SV_POSITION0 mean the same thing
|
|
if (param.semantic.back() == '0' && (param.semantic[param.semantic.size() - 2] < '0' || param.semantic[param.semantic.size() - 2] > '9'))
|
|
param.semantic.pop_back();
|
|
}
|
|
}
|
|
|
|
info.parameter_list.push_back(std::move(param));
|
|
}
|
|
|
|
if (expect_parenthesis && !expect(')'))
|
|
return false;
|
|
|
|
// Handle return type semantic
|
|
if (accept(':'))
|
|
{
|
|
if (!expect(tokenid::identifier))
|
|
return false;
|
|
if (type.is_void())
|
|
return error(_token.location, 3076, '\'' + name + "': void function cannot have a semantic"), false;
|
|
|
|
info.return_semantic = std::move(_token.literal_as_string);
|
|
// Make semantic upper case to simplify comparison later on
|
|
std::transform(info.return_semantic.begin(), info.return_semantic.end(), info.return_semantic.begin(),
|
|
[](std::string::value_type c) {
|
|
return static_cast<std::string::value_type>(std::toupper(c));
|
|
});
|
|
}
|
|
|
|
// Check if this is a function declaration without a body
|
|
if (accept(';'))
|
|
return error(location, 3510, '\'' + name + "': function is missing an implementation"), false;
|
|
|
|
// Define the function now that information about the declaration was gathered
|
|
const auto id = _codegen->define_function(location, info);
|
|
|
|
// Insert the function and parameter symbols into the symbol table and update current function pointer to the permanent one
|
|
symbol symbol = { symbol_type::function, id, { type::t_function } };
|
|
symbol.function = _current_function = &_codegen->get_function(id);
|
|
|
|
if (!insert_symbol(name, symbol, true))
|
|
return error(location, 3003, "redefinition of '" + name + '\''), false;
|
|
|
|
for (const struct_member_info ¶m : info.parameter_list)
|
|
if (!insert_symbol(param.name, { symbol_type::variable, param.definition, param.type }))
|
|
return error(param.location, 3003, "redefinition of '" + param.name + '\''), false;
|
|
|
|
// A function has to start with a new block
|
|
_codegen->enter_block(_codegen->create_block());
|
|
|
|
if (!parse_statement_block(false))
|
|
parse_success = false;
|
|
|
|
// Add implicit return statement to the end of functions
|
|
if (_codegen->is_in_block())
|
|
_codegen->leave_block_and_return();
|
|
|
|
return parse_success;
|
|
}
|
|
|
|
bool reshadefx::parser::parse_variable(type type, std::string name, bool global)
|
|
{
|
|
const auto location = std::move(_token.location);
|
|
|
|
if (type.is_void())
|
|
return error(location, 3038, '\'' + name + "': variables cannot be void"), false;
|
|
if (type.has(type::q_in) || type.has(type::q_out))
|
|
return error(location, 3055, '\'' + name + "': variables cannot be declared 'in' or 'out'"), false;
|
|
|
|
// Local and global variables have different requirements
|
|
if (global)
|
|
{
|
|
// Check that type qualifier combinations are valid
|
|
if (type.has(type::q_static))
|
|
{
|
|
// Global variables that are 'static' cannot be of another storage class
|
|
if (type.has(type::q_uniform))
|
|
return error(location, 3007, '\'' + name + "': uniform global variables cannot be declared 'static'"), false;
|
|
// The 'volatile' qualifier is only valid memory object declarations that are storage images or uniform blocks
|
|
if (type.has(type::q_volatile))
|
|
return error(location, 3008, '\'' + name + "': global variables cannot be declared 'volatile'"), false;
|
|
}
|
|
else if (!type.has(type::q_groupshared))
|
|
{
|
|
// Make all global variables 'uniform' by default, since they should be externally visible without the 'static' keyword
|
|
if (!type.has(type::q_uniform) && !type.is_object())
|
|
warning(location, 5000, '\'' + name + "': global variables are considered 'uniform' by default");
|
|
|
|
// Global variables that are not 'static' are always 'extern' and 'uniform'
|
|
type.qualifiers |= type::q_extern | type::q_uniform;
|
|
|
|
// It is invalid to make 'uniform' variables constant, since they can be modified externally
|
|
if (type.has(type::q_const))
|
|
return error(location, 3035, '\'' + name + "': variables which are 'uniform' cannot be declared 'const'"), false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Static does not really have meaning on local variables
|
|
if (type.has(type::q_static))
|
|
type.qualifiers &= ~type::q_static;
|
|
|
|
if (type.has(type::q_extern))
|
|
return error(location, 3006, '\'' + name + "': local variables cannot be declared 'extern'"), false;
|
|
if (type.has(type::q_uniform))
|
|
return error(location, 3047, '\'' + name + "': local variables cannot be declared 'uniform'"), false;
|
|
if (type.has(type::q_groupshared))
|
|
return error(location, 3010, '\'' + name + "': local variables cannot be declared 'groupshared'"), false;
|
|
|
|
if (type.is_object())
|
|
return error(location, 3038, '\'' + name + "': local variables cannot be texture, sampler or storage objects"), false;
|
|
}
|
|
|
|
// The variable name may be followed by an optional array size expression
|
|
if (!parse_array_size(type))
|
|
return false;
|
|
|
|
bool parse_success = true;
|
|
expression initializer;
|
|
texture_info texture_info;
|
|
sampler_info sampler_info;
|
|
storage_info storage_info;
|
|
|
|
if (accept(':'))
|
|
{
|
|
if (!expect(tokenid::identifier))
|
|
return false;
|
|
else if (!global) // Only global variables can have a semantic
|
|
return error(_token.location, 3043, '\'' + name + "': local variables cannot have semantics"), false;
|
|
|
|
std::string &semantic = texture_info.semantic;
|
|
semantic = std::move(_token.literal_as_string);
|
|
|
|
// Make semantic upper case to simplify comparison later on
|
|
std::transform(semantic.begin(), semantic.end(), semantic.begin(),
|
|
[](std::string::value_type c) {
|
|
return static_cast<std::string::value_type>(std::toupper(c));
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// Global variables can have optional annotations
|
|
if (global && !parse_annotations(sampler_info.annotations))
|
|
parse_success = false;
|
|
|
|
// Variables without a semantic may have an optional initializer
|
|
if (accept('='))
|
|
{
|
|
if (!parse_expression_assignment(initializer))
|
|
return false;
|
|
|
|
if (type.has(type::q_groupshared))
|
|
return error(initializer.location, 3009, '\'' + name + "': variables declared 'groupshared' cannot have an initializer"), false;
|
|
// TODO: This could be resolved by initializing these at the beginning of the entry point
|
|
if (global && !initializer.is_constant)
|
|
return error(initializer.location, 3011, '\'' + name + "': initial value must be a literal expression"), false;
|
|
|
|
// Check type compatibility
|
|
if ((type.array_length >= 0 && initializer.type.array_length != type.array_length) || !type::rank(initializer.type, type))
|
|
return error(initializer.location, 3017, '\'' + name + "': initial value (" + initializer.type.description() + ") does not match variable type (" + type.description() + ')'), false;
|
|
if ((initializer.type.rows < type.rows || initializer.type.cols < type.cols) && !initializer.type.is_scalar())
|
|
return error(initializer.location, 3017, '\'' + name + "': cannot implicitly convert these vector types (from " + initializer.type.description() + " to " + type.description() + ')'), false;
|
|
|
|
// Deduce array size from the initializer expression
|
|
if (initializer.type.is_array())
|
|
type.array_length = initializer.type.array_length;
|
|
|
|
// Perform implicit cast from initializer expression to variable type
|
|
if (initializer.type.components() > type.components())
|
|
warning(initializer.location, 3206, "implicit truncation of vector type");
|
|
|
|
initializer.add_cast_operation(type);
|
|
|
|
if (type.has(type::q_static))
|
|
initializer.type.qualifiers |= type::q_static;
|
|
}
|
|
else if (type.is_numeric() || type.is_struct()) // Numeric variables without an initializer need special handling
|
|
{
|
|
if (type.has(type::q_const)) // Constants have to have an initial value
|
|
return error(location, 3012, '\'' + name + "': missing initial value"), false;
|
|
else if (!type.has(type::q_uniform)) // Zero initialize all global variables
|
|
initializer.reset_to_rvalue_constant(location, {}, type);
|
|
}
|
|
else if (global && accept('{')) // Textures and samplers can have a property block attached to their declaration
|
|
{
|
|
// Non-numeric variables cannot be constants
|
|
if (type.has(type::q_const))
|
|
return error(location, 3035, '\'' + name + "': this variable type cannot be declared 'const'"), false;
|
|
|
|
while (!peek('}'))
|
|
{
|
|
if (!expect(tokenid::identifier))
|
|
return consume_until('}'), false;
|
|
|
|
const auto property_name = std::move(_token.literal_as_string);
|
|
const auto property_location = std::move(_token.location);
|
|
|
|
if (!expect('='))
|
|
return consume_until('}'), false;
|
|
|
|
backup();
|
|
|
|
expression expression;
|
|
|
|
if (accept(tokenid::identifier)) // Handle special enumeration names for property values
|
|
{
|
|
// Transform identifier to uppercase to do case-insensitive comparison
|
|
std::transform(_token.literal_as_string.begin(), _token.literal_as_string.end(), _token.literal_as_string.begin(),
|
|
[](std::string::value_type c) {
|
|
return static_cast<std::string::value_type>(std::toupper(c));
|
|
});
|
|
|
|
static const std::unordered_map<std::string_view, uint32_t> s_enum_values = {
|
|
{ "NONE", 0 }, { "POINT", 0 },
|
|
{ "LINEAR", 1 },
|
|
{ "WRAP", uint32_t(texture_address_mode::wrap) }, { "REPEAT", uint32_t(texture_address_mode::wrap) },
|
|
{ "MIRROR", uint32_t(texture_address_mode::mirror) },
|
|
{ "CLAMP", uint32_t(texture_address_mode::clamp) },
|
|
{ "BORDER", uint32_t(texture_address_mode::border) },
|
|
{ "R8", uint32_t(texture_format::r8) },
|
|
{ "R16", uint32_t(texture_format::r16) },
|
|
{ "R16F", uint32_t(texture_format::r16f) },
|
|
{ "R32I", uint32_t(texture_format::r32i) },
|
|
{ "R32U", uint32_t(texture_format::r32u) },
|
|
{ "R32F", uint32_t(texture_format::r32f) },
|
|
{ "RG8", uint32_t(texture_format::rg8) }, { "R8G8", uint32_t(texture_format::rg8) },
|
|
{ "RG16", uint32_t(texture_format::rg16) }, { "R16G16", uint32_t(texture_format::rg16) },
|
|
{ "RG16F", uint32_t(texture_format::rg16f) }, { "R16G16F", uint32_t(texture_format::rg16f) },
|
|
{ "RG32F", uint32_t(texture_format::rg32f) }, { "R32G32F", uint32_t(texture_format::rg32f) },
|
|
{ "RGBA8", uint32_t(texture_format::rgba8) }, { "R8G8B8A8", uint32_t(texture_format::rgba8) },
|
|
{ "RGBA16", uint32_t(texture_format::rgba16) }, { "R16G16B16A16", uint32_t(texture_format::rgba16) },
|
|
{ "RGBA16F", uint32_t(texture_format::rgba16f) }, { "R16G16B16A16F", uint32_t(texture_format::rgba16f) },
|
|
{ "RGBA32F", uint32_t(texture_format::rgba32f) }, { "R32G32B32A32F", uint32_t(texture_format::rgba32f) },
|
|
{ "RGB10A2", uint32_t(texture_format::rgb10a2) }, { "R10G10B10A2", uint32_t(texture_format::rgb10a2) },
|
|
};
|
|
|
|
// Look up identifier in list of possible enumeration names
|
|
if (const auto it = s_enum_values.find(_token.literal_as_string);
|
|
it != s_enum_values.end())
|
|
expression.reset_to_rvalue_constant(_token.location, it->second);
|
|
else // No match found, so rewind to parser state before the identifier was consumed and try parsing it as a normal expression
|
|
restore();
|
|
}
|
|
|
|
// Parse right hand side as normal expression if no special enumeration name was matched already
|
|
if (!expression.is_constant && !parse_expression_multary(expression))
|
|
return consume_until('}'), false;
|
|
|
|
if (property_name == "Texture")
|
|
{
|
|
// Ignore invalid symbols that were added during error recovery
|
|
if (expression.base == 0xFFFFFFFF)
|
|
return consume_until('}'), false;
|
|
|
|
if (!expression.type.is_texture())
|
|
return error(expression.location, 3020, "type mismatch, expected texture name"), consume_until('}'), false;
|
|
|
|
if (type.is_sampler() || type.is_storage())
|
|
{
|
|
reshadefx::texture_info &target_info = _codegen->get_texture(expression.base);
|
|
if (type.is_storage())
|
|
// Texture is used as storage
|
|
target_info.storage_access = true;
|
|
|
|
texture_info = target_info;
|
|
sampler_info.texture_name = target_info.unique_name;
|
|
storage_info.texture_name = target_info.unique_name;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!expression.is_constant || !expression.type.is_scalar())
|
|
return error(expression.location, 3538, "value must be a literal scalar expression"), consume_until('}'), false;
|
|
|
|
// All states below expect the value to be of an integer type
|
|
expression.add_cast_operation({ type::t_int, 1, 1 });
|
|
const int value = expression.constant.as_int[0];
|
|
|
|
if (value < 0) // There is little use for negative values, so warn in those cases
|
|
warning(expression.location, 3571, "negative value specified for property '" + property_name + '\'');
|
|
|
|
if (type.is_texture())
|
|
{
|
|
if (property_name == "Width")
|
|
texture_info.width = value > 0 ? value : 1;
|
|
else if (type.texture_dimension() >= 2 && property_name == "Height")
|
|
texture_info.height = value > 0 ? value : 1;
|
|
else if (type.texture_dimension() >= 3 && property_name == "Depth")
|
|
texture_info.depth = value > 0 && value <= std::numeric_limits<uint16_t>::max() ? static_cast<uint16_t>(value) : 1;
|
|
else if (property_name == "MipLevels")
|
|
// Also ensures negative values do not cause problems
|
|
texture_info.levels = value > 0 && value <= std::numeric_limits<uint16_t>::max() ? static_cast<uint16_t>(value) : 1;
|
|
else if (property_name == "Format")
|
|
texture_info.format = static_cast<texture_format>(value);
|
|
else
|
|
return error(property_location, 3004, "unrecognized property '" + property_name + '\''), consume_until('}'), false;
|
|
}
|
|
else if (type.is_sampler())
|
|
{
|
|
if (property_name == "SRGBTexture" || property_name == "SRGBReadEnable")
|
|
sampler_info.srgb = value != 0;
|
|
else if (property_name == "AddressU")
|
|
sampler_info.address_u = static_cast<texture_address_mode>(value);
|
|
else if (property_name == "AddressV")
|
|
sampler_info.address_v = static_cast<texture_address_mode>(value);
|
|
else if (property_name == "AddressW")
|
|
sampler_info.address_w = static_cast<texture_address_mode>(value);
|
|
else if (property_name == "MinFilter")
|
|
sampler_info.filter = static_cast<filter_mode>((uint32_t(sampler_info.filter) & 0x0F) | ((value << 4) & 0x30)); // Combine sampler filter components into a single filter enumeration value
|
|
else if (property_name == "MagFilter")
|
|
sampler_info.filter = static_cast<filter_mode>((uint32_t(sampler_info.filter) & 0x33) | ((value << 2) & 0x0C));
|
|
else if (property_name == "MipFilter")
|
|
sampler_info.filter = static_cast<filter_mode>((uint32_t(sampler_info.filter) & 0x3C) | (value & 0x03));
|
|
else if (property_name == "MinLOD" || property_name == "MaxMipLevel")
|
|
sampler_info.min_lod = static_cast<float>(value);
|
|
else if (property_name == "MaxLOD")
|
|
sampler_info.max_lod = static_cast<float>(value);
|
|
else if (property_name == "MipLODBias" || property_name == "MipMapLodBias")
|
|
sampler_info.lod_bias = static_cast<float>(value);
|
|
else
|
|
return error(property_location, 3004, "unrecognized property '" + property_name + '\''), consume_until('}'), false;
|
|
}
|
|
else if (type.is_storage())
|
|
{
|
|
if (property_name == "MipLOD" || property_name == "MipLevel")
|
|
storage_info.level = value > 0 && value < std::numeric_limits<uint16_t>::max() ? static_cast<uint16_t>(value) : 0;
|
|
else
|
|
return error(property_location, 3004, "unrecognized property '" + property_name + '\''), consume_until('}'), false;
|
|
}
|
|
}
|
|
|
|
if (!expect(';'))
|
|
return consume_until('}'), false;
|
|
}
|
|
|
|
if (!expect('}'))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// At this point the array size should be known (either from the declaration or the initializer)
|
|
if (type.array_length < 0)
|
|
return error(location, 3074, '\'' + name + "': implicit array missing initial value"), false;
|
|
|
|
symbol symbol;
|
|
|
|
// Variables with a constant initializer and constant type are named constants
|
|
// Skip this for very large arrays though, to avoid large amounts of duplicated values when that array constant is accessed with a dynamic index
|
|
if (type.is_numeric() && type.has(type::q_const) && initializer.is_constant && type.array_length < 100)
|
|
{
|
|
// Named constants are special symbols
|
|
symbol = { symbol_type::constant, 0, type, initializer.constant };
|
|
}
|
|
else if (type.is_texture())
|
|
{
|
|
assert(global);
|
|
|
|
texture_info.name = name;
|
|
texture_info.type = static_cast<texture_type>(type.texture_dimension());
|
|
|
|
// Add namespace scope to avoid name clashes
|
|
texture_info.unique_name = 'V' + current_scope().name + name;
|
|
std::replace(texture_info.unique_name.begin(), texture_info.unique_name.end(), ':', '_');
|
|
|
|
texture_info.annotations = std::move(sampler_info.annotations);
|
|
|
|
symbol = { symbol_type::variable, 0, type };
|
|
symbol.id = _codegen->define_texture(location, texture_info);
|
|
}
|
|
// Samplers are actually combined image samplers
|
|
else if (type.is_sampler())
|
|
{
|
|
assert(global);
|
|
|
|
if (sampler_info.texture_name.empty())
|
|
return error(location, 3012, '\'' + name + "': missing 'Texture' property"), false;
|
|
if (type.texture_dimension() != static_cast<unsigned int>(texture_info.type))
|
|
return error(location, 3521, '\'' + name + "': type mismatch between texture and sampler type"), false;
|
|
if (sampler_info.srgb && texture_info.format != texture_format::rgba8)
|
|
return error(location, 4582, '\'' + name + "': texture does not support sRGB sampling (only textures with RGBA8 format do)"), false;
|
|
|
|
if (texture_info.format == texture_format::r32i ?
|
|
!type.is_integral() || !type.is_signed() :
|
|
texture_info.format == texture_format::r32u ?
|
|
!type.is_integral() || !type.is_unsigned() :
|
|
!type.is_floating_point())
|
|
return error(location, 4582, '\'' + name + "': type mismatch between texture format and sampler element type"), false;
|
|
|
|
sampler_info.name = name;
|
|
sampler_info.type = type;
|
|
|
|
// Add namespace scope to avoid name clashes
|
|
sampler_info.unique_name = 'V' + current_scope().name + name;
|
|
std::replace(sampler_info.unique_name.begin(), sampler_info.unique_name.end(), ':', '_');
|
|
|
|
symbol = { symbol_type::variable, 0, type };
|
|
symbol.id = _codegen->define_sampler(location, texture_info, sampler_info);
|
|
}
|
|
else if (type.is_storage())
|
|
{
|
|
assert(global);
|
|
|
|
if (storage_info.texture_name.empty())
|
|
return error(location, 3012, '\'' + name + "': missing 'Texture' property"), false;
|
|
if (type.texture_dimension() != static_cast<unsigned int>(texture_info.type))
|
|
return error(location, 3521, '\'' + name + "': type mismatch between texture and storage type"), false;
|
|
|
|
if (texture_info.format == texture_format::r32i ?
|
|
!type.is_integral() || !type.is_signed() :
|
|
texture_info.format == texture_format::r32u ?
|
|
!type.is_integral() || !type.is_unsigned() :
|
|
!type.is_floating_point())
|
|
return error(location, 4582, '\'' + name + "': type mismatch between texture format and storage element type"), false;
|
|
|
|
storage_info.name = name;
|
|
storage_info.type = type;
|
|
|
|
// Add namespace scope to avoid name clashes
|
|
storage_info.unique_name = 'V' + current_scope().name + name;
|
|
std::replace(storage_info.unique_name.begin(), storage_info.unique_name.end(), ':', '_');
|
|
|
|
if (storage_info.level > texture_info.levels - 1)
|
|
storage_info.level = texture_info.levels - 1;
|
|
|
|
symbol = { symbol_type::variable, 0, type };
|
|
symbol.id = _codegen->define_storage(location, texture_info, storage_info);
|
|
}
|
|
// Uniform variables are put into a global uniform buffer structure
|
|
else if (type.has(type::q_uniform))
|
|
{
|
|
assert(global);
|
|
|
|
uniform_info uniform_info;
|
|
uniform_info.name = name;
|
|
uniform_info.type = type;
|
|
|
|
uniform_info.annotations = std::move(sampler_info.annotations);
|
|
|
|
uniform_info.initializer_value = std::move(initializer.constant);
|
|
uniform_info.has_initializer_value = initializer.is_constant;
|
|
|
|
symbol = { symbol_type::variable, 0, type };
|
|
symbol.id = _codegen->define_uniform(location, uniform_info);
|
|
}
|
|
// All other variables are separate entities
|
|
else
|
|
{
|
|
// Update global variable names to contain the namespace scope to avoid name clashes
|
|
std::string unique_name = global ? 'V' + current_scope().name + name : name;
|
|
std::replace(unique_name.begin(), unique_name.end(), ':', '_');
|
|
|
|
symbol = { symbol_type::variable, 0, type };
|
|
symbol.id = _codegen->define_variable(location, type, std::move(unique_name), global,
|
|
// Shared variables cannot have an initializer
|
|
type.has(type::q_groupshared) ? 0 : _codegen->emit_load(initializer));
|
|
}
|
|
|
|
// Insert the symbol into the symbol table
|
|
if (!insert_symbol(name, symbol, global))
|
|
return error(location, 3003, "redefinition of '" + name + '\''), false;
|
|
|
|
return parse_success;
|
|
}
|
|
|
|
bool reshadefx::parser::parse_technique()
|
|
{
|
|
if (!expect(tokenid::identifier))
|
|
return false;
|
|
|
|
technique_info info;
|
|
info.name = std::move(_token.literal_as_string);
|
|
|
|
bool parse_success = parse_annotations(info.annotations);
|
|
|
|
if (!expect('{'))
|
|
return false;
|
|
|
|
while (!peek('}'))
|
|
{
|
|
if (pass_info pass; parse_technique_pass(pass))
|
|
info.passes.push_back(std::move(pass));
|
|
else {
|
|
parse_success = false;
|
|
if (!peek(tokenid::pass) && !peek('}')) // If there is another pass definition following, try to parse that despite the error
|
|
return consume_until('}'), false;
|
|
}
|
|
}
|
|
|
|
_codegen->define_technique(std::move(info));
|
|
|
|
return expect('}') && parse_success;
|
|
}
|
|
bool reshadefx::parser::parse_technique_pass(pass_info &info)
|
|
{
|
|
if (!expect(tokenid::pass))
|
|
return false;
|
|
|
|
const auto pass_location = std::move(_token.location);
|
|
|
|
// Passes can have an optional name
|
|
if (accept(tokenid::identifier))
|
|
info.name = std::move(_token.literal_as_string);
|
|
|
|
bool parse_success = true;
|
|
bool targets_support_srgb = true;
|
|
function_info vs_info, ps_info, cs_info;
|
|
|
|
if (!expect('{'))
|
|
return false;
|
|
|
|
while (!peek('}'))
|
|
{
|
|
// Parse pass states
|
|
if (!expect(tokenid::identifier))
|
|
return consume_until('}'), false;
|
|
|
|
auto location = std::move(_token.location);
|
|
const auto state = std::move(_token.literal_as_string);
|
|
|
|
if (!expect('='))
|
|
return consume_until('}'), false;
|
|
|
|
const bool is_shader_state = state == "VertexShader" || state == "PixelShader" || state == "ComputeShader";
|
|
const bool is_texture_state = state.compare(0, 12, "RenderTarget") == 0 && (state.size() == 12 || (state[12] >= '0' && state[12] < '8'));
|
|
|
|
// Shader and render target assignment looks up values in the symbol table, so handle those separately from the other states
|
|
if (is_shader_state || is_texture_state)
|
|
{
|
|
std::string identifier;
|
|
scoped_symbol symbol;
|
|
if (!accept_symbol(identifier, symbol))
|
|
return consume_until('}'), false;
|
|
|
|
location = std::move(_token.location);
|
|
|
|
int num_threads[3] = { 1, 1, 1 };
|
|
if (accept('<'))
|
|
{
|
|
expression x, y, z;
|
|
if (!parse_expression_multary(x, 8) || !expect(',') || !parse_expression_multary(y, 8))
|
|
return consume_until('}'), false;
|
|
|
|
// Parse optional third dimension (defaults to 1)
|
|
z.reset_to_rvalue_constant({}, 1);
|
|
if (accept(',') && !parse_expression_multary(z, 8))
|
|
return consume_until('}'), false;
|
|
|
|
if (!x.is_constant)
|
|
return error(x.location, 3011, "value must be a literal expression"), consume_until('}'), false;
|
|
if (!y.is_constant)
|
|
return error(y.location, 3011, "value must be a literal expression"), consume_until('}'), false;
|
|
if (!z.is_constant)
|
|
return error(z.location, 3011, "value must be a literal expression"), consume_until('}'), false;
|
|
x.add_cast_operation({ type::t_int, 1, 1 });
|
|
y.add_cast_operation({ type::t_int, 1, 1 });
|
|
z.add_cast_operation({ type::t_int, 1, 1 });
|
|
num_threads[0] = x.constant.as_int[0];
|
|
num_threads[1] = y.constant.as_int[0];
|
|
num_threads[2] = z.constant.as_int[0];
|
|
|
|
if (!expect('>'))
|
|
return consume_until('}'), false;
|
|
}
|
|
|
|
// Ignore invalid symbols that were added during error recovery
|
|
if (symbol.id != 0xFFFFFFFF)
|
|
{
|
|
if (is_shader_state)
|
|
{
|
|
if (!symbol.id)
|
|
parse_success = false,
|
|
error(location, 3501, "undeclared identifier '" + identifier + "', expected function name");
|
|
else if (!symbol.type.is_function())
|
|
parse_success = false,
|
|
error(location, 3020, "type mismatch, expected function name");
|
|
else {
|
|
// Look up the matching function info for this function definition
|
|
function_info &function_info = _codegen->get_function(symbol.id);
|
|
|
|
// We potentially need to generate a special entry point function which translates between function parameters and input/output variables
|
|
switch (state[0])
|
|
{
|
|
case 'V':
|
|
vs_info = function_info;
|
|
_codegen->define_entry_point(vs_info, shader_type::vs);
|
|
info.vs_entry_point = vs_info.unique_name;
|
|
break;
|
|
case 'P':
|
|
ps_info = function_info;
|
|
_codegen->define_entry_point(ps_info, shader_type::ps);
|
|
info.ps_entry_point = ps_info.unique_name;
|
|
break;
|
|
case 'C':
|
|
cs_info = function_info;
|
|
_codegen->define_entry_point(cs_info, shader_type::cs, num_threads);
|
|
info.cs_entry_point = cs_info.unique_name;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(is_texture_state);
|
|
|
|
if (!symbol.id)
|
|
parse_success = false,
|
|
error(location, 3004, "undeclared identifier '" + identifier + "', expected texture name");
|
|
else if (!symbol.type.is_texture())
|
|
parse_success = false,
|
|
error(location, 3020, "type mismatch, expected texture name");
|
|
else if (symbol.type.texture_dimension() != 2)
|
|
parse_success = false,
|
|
error(location, 3020, "cannot use texture" + std::to_string(symbol.type.texture_dimension()) + "D as render target");
|
|
else {
|
|
reshadefx::texture_info &target_info = _codegen->get_texture(symbol.id);
|
|
// Texture is used as a render target
|
|
target_info.render_target = true;
|
|
|
|
// Verify that all render targets in this pass have the same dimensions
|
|
if (info.viewport_width != 0 && info.viewport_height != 0 && (target_info.width != info.viewport_width || target_info.height != info.viewport_height))
|
|
parse_success = false,
|
|
error(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) + ')');
|
|
|
|
info.viewport_width = target_info.width;
|
|
info.viewport_height = target_info.height;
|
|
|
|
const auto target_index = state.size() > 12 ? (state[12] - '0') : 0;
|
|
info.render_target_names[target_index] = target_info.unique_name;
|
|
|
|
// Only RGBA8 format supports sRGB writes across all APIs
|
|
if (target_info.format != texture_format::rgba8)
|
|
targets_support_srgb = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
parse_success = false;
|
|
}
|
|
}
|
|
else // Handle the rest of the pass states
|
|
{
|
|
backup();
|
|
|
|
expression expression;
|
|
|
|
if (accept(tokenid::identifier)) // Handle special enumeration names for pass states
|
|
{
|
|
// Transform identifier to uppercase to do case-insensitive comparison
|
|
std::transform(_token.literal_as_string.begin(), _token.literal_as_string.end(), _token.literal_as_string.begin(),
|
|
[](std::string::value_type c) {
|
|
return static_cast<std::string::value_type>(std::toupper(c));
|
|
});
|
|
|
|
static const std::unordered_map<std::string_view, uint32_t> s_enum_values = {
|
|
{ "NONE", 0 }, { "ZERO", 0 }, { "ONE", 1 },
|
|
{ "ADD", uint32_t(pass_blend_op::add) },
|
|
{ "SUBTRACT", uint32_t(pass_blend_op::subtract) },
|
|
{ "REVSUBTRACT", uint32_t(pass_blend_op::reverse_subtract) },
|
|
{ "MIN", uint32_t(pass_blend_op::min) },
|
|
{ "MAX", uint32_t(pass_blend_op::max) },
|
|
{ "SRCCOLOR", uint32_t(pass_blend_factor::source_color) },
|
|
{ "INVSRCCOLOR", uint32_t(pass_blend_factor::one_minus_source_color) },
|
|
{ "DESTCOLOR", uint32_t(pass_blend_factor::dest_color) },
|
|
{ "INVDESTCOLOR", uint32_t(pass_blend_factor::one_minus_dest_color) },
|
|
{ "SRCALPHA", uint32_t(pass_blend_factor::source_alpha) },
|
|
{ "INVSRCALPHA", uint32_t(pass_blend_factor::one_minus_source_alpha) },
|
|
{ "DESTALPHA", uint32_t(pass_blend_factor::dest_alpha) },
|
|
{ "INVDESTALPHA", uint32_t(pass_blend_factor::one_minus_dest_alpha) },
|
|
{ "KEEP", uint32_t(pass_stencil_op::keep) },
|
|
{ "REPLACE", uint32_t(pass_stencil_op::replace) },
|
|
{ "INVERT", uint32_t(pass_stencil_op::invert) },
|
|
{ "INCR", uint32_t(pass_stencil_op::increment) },
|
|
{ "INCRSAT", uint32_t(pass_stencil_op::increment_saturate) },
|
|
{ "DECR", uint32_t(pass_stencil_op::decrement) },
|
|
{ "DECRSAT", uint32_t(pass_stencil_op::decrement_saturate) },
|
|
{ "NEVER", uint32_t(pass_stencil_func::never) },
|
|
{ "EQUAL", uint32_t(pass_stencil_func::equal) },
|
|
{ "NEQUAL", uint32_t(pass_stencil_func::not_equal) }, { "NOTEQUAL", uint32_t(pass_stencil_func::not_equal) },
|
|
{ "LESS", uint32_t(pass_stencil_func::less) },
|
|
{ "GREATER", uint32_t(pass_stencil_func::greater) },
|
|
{ "LEQUAL", uint32_t(pass_stencil_func::less_equal) }, { "LESSEQUAL", uint32_t(pass_stencil_func::less_equal) },
|
|
{ "GEQUAL", uint32_t(pass_stencil_func::greater_equal) }, { "GREATEREQUAL", uint32_t(pass_stencil_func::greater_equal) },
|
|
{ "ALWAYS", uint32_t(pass_stencil_func::always) },
|
|
{ "POINTS", uint32_t(primitive_topology::point_list) },
|
|
{ "POINTLIST", uint32_t(primitive_topology::point_list) },
|
|
{ "LINES", uint32_t(primitive_topology::line_list) },
|
|
{ "LINELIST", uint32_t(primitive_topology::line_list) },
|
|
{ "LINESTRIP", uint32_t(primitive_topology::line_strip) },
|
|
{ "TRIANGLES", uint32_t(primitive_topology::triangle_list) },
|
|
{ "TRIANGLELIST", uint32_t(primitive_topology::triangle_list) },
|
|
{ "TRIANGLESTRIP", uint32_t(primitive_topology::triangle_strip) },
|
|
};
|
|
|
|
// Look up identifier in list of possible enumeration names
|
|
if (const auto it = s_enum_values.find(_token.literal_as_string);
|
|
it != s_enum_values.end())
|
|
expression.reset_to_rvalue_constant(_token.location, it->second);
|
|
else // No match found, so rewind to parser state before the identifier was consumed and try parsing it as a normal expression
|
|
restore();
|
|
}
|
|
|
|
// Parse right hand side as normal expression if no special enumeration name was matched already
|
|
if (!expression.is_constant && !parse_expression_multary(expression))
|
|
return consume_until('}'), false;
|
|
else if (!expression.is_constant || !expression.type.is_scalar())
|
|
parse_success = false,
|
|
error(expression.location, 3011, "pass state value must be a literal scalar expression");
|
|
|
|
// All states below expect the value to be of an unsigned integer type
|
|
expression.add_cast_operation({ type::t_uint, 1, 1 });
|
|
const unsigned int value = expression.constant.as_uint[0];
|
|
|
|
#define SET_STATE_VALUE_INDEXED(name, info_name, value) \
|
|
else if (constexpr size_t name##_len = sizeof(#name) - 1; state.compare(0, name##_len, #name) == 0 && (state.size() == name##_len || (state[name##_len] >= '0' && state[name##_len] < ('0' + static_cast<char>(std::size(info.info_name)))))) \
|
|
{ \
|
|
if (state.size() != name##_len) \
|
|
info.info_name[state[name##_len] - '0'] = (value); \
|
|
else \
|
|
for (int i = 0; i < static_cast<int>(std::size(info.info_name)); ++i) \
|
|
info.info_name[i] = (value); \
|
|
}
|
|
|
|
if (state == "SRGBWriteEnable")
|
|
info.srgb_write_enable = (value != 0);
|
|
SET_STATE_VALUE_INDEXED(BlendEnable, blend_enable, value != 0)
|
|
else if (state == "StencilEnable")
|
|
info.stencil_enable = (value != 0);
|
|
else if (state == "ClearRenderTargets")
|
|
info.clear_render_targets = (value != 0);
|
|
SET_STATE_VALUE_INDEXED(ColorWriteMask, color_write_mask, value & 0xFF)
|
|
SET_STATE_VALUE_INDEXED(RenderTargetWriteMask, color_write_mask, value & 0xFF)
|
|
else if (state == "StencilReadMask" || state == "StencilMask")
|
|
info.stencil_read_mask = value & 0xFF;
|
|
else if (state == "StencilWriteMask")
|
|
info.stencil_write_mask = value & 0xFF;
|
|
SET_STATE_VALUE_INDEXED(BlendOp, blend_op, static_cast<pass_blend_op>(value))
|
|
SET_STATE_VALUE_INDEXED(BlendOpAlpha, blend_op_alpha, static_cast<pass_blend_op>(value))
|
|
SET_STATE_VALUE_INDEXED(SrcBlend, src_blend, static_cast<pass_blend_factor>(value))
|
|
SET_STATE_VALUE_INDEXED(SrcBlendAlpha, src_blend_alpha, static_cast<pass_blend_factor>(value))
|
|
SET_STATE_VALUE_INDEXED(DestBlend, dest_blend, static_cast<pass_blend_factor>(value))
|
|
SET_STATE_VALUE_INDEXED(DestBlendAlpha, dest_blend_alpha, static_cast<pass_blend_factor>(value))
|
|
else if (state == "StencilFunc")
|
|
info.stencil_comparison_func = static_cast<pass_stencil_func>(value);
|
|
else if (state == "StencilRef")
|
|
info.stencil_reference_value = value;
|
|
else if (state == "StencilPass" || state == "StencilPassOp")
|
|
info.stencil_op_pass = static_cast<pass_stencil_op>(value);
|
|
else if (state == "StencilFail" || state == "StencilFailOp")
|
|
info.stencil_op_fail = static_cast<pass_stencil_op>(value);
|
|
else if (state == "StencilZFail" || state == "StencilDepthFail" || state == "StencilDepthFailOp")
|
|
info.stencil_op_depth_fail = static_cast<pass_stencil_op>(value);
|
|
else if (state == "VertexCount")
|
|
info.num_vertices = value;
|
|
else if (state == "PrimitiveType" || state == "PrimitiveTopology")
|
|
info.topology = static_cast<primitive_topology>(value);
|
|
else if (state == "DispatchSizeX")
|
|
info.viewport_width = value;
|
|
else if (state == "DispatchSizeY")
|
|
info.viewport_height = value;
|
|
else if (state == "DispatchSizeZ")
|
|
info.viewport_dispatch_z = value;
|
|
else if (state == "GenerateMipmaps" || state == "GenerateMipMaps")
|
|
info.generate_mipmaps = (value != 0);
|
|
else
|
|
parse_success = false,
|
|
error(location, 3004, "unrecognized pass state '" + state + '\'');
|
|
|
|
#undef SET_STATE_VALUE_INDEXED
|
|
}
|
|
|
|
if (!expect(';'))
|
|
return consume_until('}'), false;
|
|
}
|
|
|
|
if (parse_success)
|
|
{
|
|
if (!info.cs_entry_point.empty())
|
|
{
|
|
if (info.viewport_width == 0 || info.viewport_height == 0)
|
|
{
|
|
parse_success = false;
|
|
error(pass_location, 3012, "pass is missing 'DispatchSizeX' or 'DispatchSizeY' property");
|
|
}
|
|
|
|
if (!info.vs_entry_point.empty())
|
|
warning(pass_location, 3089, "pass is specifying both 'VertexShader' and 'ComputeShader' which cannot be used together");
|
|
if (!info.ps_entry_point.empty())
|
|
warning(pass_location, 3089, "pass is specifying both 'PixelShader' and 'ComputeShader' which cannot be used together");
|
|
|
|
for (codegen::id id : cs_info.referenced_samplers)
|
|
info.samplers.push_back(_codegen->get_sampler(id));
|
|
for (codegen::id id : cs_info.referenced_storages)
|
|
info.storages.push_back(_codegen->get_storage(id));
|
|
}
|
|
else if (info.vs_entry_point.empty() || info.ps_entry_point.empty())
|
|
{
|
|
parse_success = false;
|
|
|
|
if (info.vs_entry_point.empty())
|
|
error(pass_location, 3012, "pass is missing 'VertexShader' property");
|
|
if (info.ps_entry_point.empty())
|
|
error(pass_location, 3012, "pass is missing 'PixelShader' property");
|
|
}
|
|
else
|
|
{
|
|
// Verify that shader signatures between VS and PS match (both semantics and interpolation qualifiers)
|
|
std::unordered_map<std::string_view, type> vs_semantic_mapping;
|
|
if (vs_info.return_semantic.empty())
|
|
{
|
|
if (!vs_info.return_type.is_void() && !vs_info.return_type.is_struct())
|
|
{
|
|
parse_success = false;
|
|
error(pass_location, 3503, '\'' + vs_info.name + "': function return value is missing semantics");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vs_semantic_mapping[vs_info.return_semantic] = vs_info.return_type;
|
|
}
|
|
|
|
for (const struct_member_info ¶m : vs_info.parameter_list)
|
|
{
|
|
if (param.semantic.empty())
|
|
{
|
|
if (!param.type.is_struct())
|
|
{
|
|
parse_success = false;
|
|
if (param.type.has(type::q_in))
|
|
error(pass_location, 3502, '\'' + vs_info.name + "': input parameter '" + param.name + "' is missing semantics");
|
|
else
|
|
error(pass_location, 3503, '\'' + vs_info.name + "': output parameter '" + param.name + "' is missing semantics");
|
|
}
|
|
}
|
|
else if (param.type.has(type::q_out))
|
|
{
|
|
vs_semantic_mapping[param.semantic] = param.type;
|
|
}
|
|
}
|
|
|
|
if (ps_info.return_semantic.empty())
|
|
{
|
|
if (!ps_info.return_type.is_void() && !ps_info.return_type.is_struct())
|
|
{
|
|
parse_success = false;
|
|
error(pass_location, 3503, '\'' + ps_info.name + "': function return value is missing semantics");
|
|
}
|
|
}
|
|
|
|
for (const struct_member_info ¶m : ps_info.parameter_list)
|
|
{
|
|
if (param.semantic.empty())
|
|
{
|
|
if (!param.type.is_struct())
|
|
{
|
|
parse_success = false;
|
|
if (param.type.has(type::q_in))
|
|
error(pass_location, 3502, '\'' + ps_info.name + "': input parameter '" + param.name + "' is missing semantics");
|
|
else
|
|
error(pass_location, 3503, '\'' + ps_info.name + "': output parameter '" + param.name + "' is missing semantics");
|
|
}
|
|
}
|
|
else if (param.type.has(type::q_in))
|
|
{
|
|
if (const auto it = vs_semantic_mapping.find(param.semantic);
|
|
it == vs_semantic_mapping.end() || it->second != param.type)
|
|
warning(pass_location, 4576, '\'' + ps_info.name + "': input parameter '" + param.name + "' semantic does not match vertex shader one");
|
|
else if (((it->second.qualifiers ^ param.type.qualifiers) & (type::q_linear | type::q_noperspective | type::q_centroid | type::q_nointerpolation)) != 0)
|
|
parse_success = false,
|
|
error( pass_location, 4568, '\'' + ps_info.name + "': input parameter '" + param.name + "' interpolation qualifiers do not match vertex shader ones");
|
|
}
|
|
}
|
|
|
|
for (codegen::id id : vs_info.referenced_samplers)
|
|
info.samplers.push_back(_codegen->get_sampler(id));
|
|
for (codegen::id id : ps_info.referenced_samplers)
|
|
info.samplers.push_back(_codegen->get_sampler(id));
|
|
if (!vs_info.referenced_storages.empty() || !ps_info.referenced_storages.empty())
|
|
{
|
|
parse_success = false;
|
|
error(pass_location, 3667, "storage writes are only valid in compute shaders");
|
|
}
|
|
}
|
|
|
|
// Verify render target format supports sRGB writes if enabled
|
|
if (info.srgb_write_enable && !targets_support_srgb)
|
|
parse_success = false,
|
|
error(pass_location, 4582, "one or more render target(s) do not support sRGB writes (only textures with RGBA8 format do)");
|
|
}
|
|
|
|
return expect('}') && parse_success;
|
|
}
|