Merge pull request #44874 from Chaosus/shader_warnings

Basic warning support implementation for the Godot Shading Language.
This commit is contained in:
Rémi Verschelde 2021-05-22 19:12:53 +02:00 committed by GitHub
commit 234a101eb3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 569 additions and 18 deletions

View file

@ -37,12 +37,18 @@
#include "editor/editor_node.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/project_settings_editor.h"
#include "editor/property_editor.h"
#include "servers/display_server.h"
#include "servers/rendering/shader_types.h"
/*** SHADER SCRIPT EDITOR ****/
static bool saved_warnings_enabled = false;
static bool saved_treat_warning_as_errors = false;
static Map<ShaderWarning::Code, bool> saved_warnings;
static uint32_t saved_warning_flags = 0U;
Ref<Shader> ShaderTextEditor::get_edited_shader() const {
return shader;
}
@ -82,6 +88,10 @@ void ShaderTextEditor::reload_text() {
update_line_and_column();
}
void ShaderTextEditor::set_warnings_panel(RichTextLabel *p_warnings_panel) {
warnings_panel = p_warnings_panel;
}
void ShaderTextEditor::_load_theme_settings() {
CodeEdit *text_editor = get_text_editor();
Color updated_marked_line_color = EDITOR_GET("text_editor/highlighting/mark_color");
@ -141,6 +151,12 @@ void ShaderTextEditor::_load_theme_settings() {
syntax_highlighter->clear_color_regions();
syntax_highlighter->add_color_region("/*", "*/", comment_color, false);
syntax_highlighter->add_color_region("//", "", comment_color, true);
if (warnings_panel) {
// Warnings panel
warnings_panel->add_theme_font_override("normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font("main", "EditorFonts"));
warnings_panel->add_theme_font_size_override("normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size("main_size", "EditorFonts"));
}
}
void ShaderTextEditor::_check_shader_mode() {
@ -187,6 +203,9 @@ void ShaderTextEditor::_validate_script() {
ShaderLanguage sl;
sl.enable_warning_checking(saved_warnings_enabled);
sl.set_warning_flags(saved_warning_flags);
Error err = sl.compile(code, ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())), ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())), ShaderLanguage::VaryingFunctionNames(), ShaderTypes::get_singleton()->get_types(), _get_global_variable_type);
if (err != OK) {
@ -197,7 +216,6 @@ void ShaderTextEditor::_validate_script() {
get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0));
}
get_text_editor()->set_line_background_color(sl.get_error_line() - 1, marked_line_color);
} else {
for (int i = 0; i < get_text_editor()->get_line_count(); i++) {
get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0));
@ -205,9 +223,60 @@ void ShaderTextEditor::_validate_script() {
set_error("");
}
if (warnings.size() > 0 || err != OK) {
warnings_panel->clear();
}
warnings.clear();
for (List<ShaderWarning>::Element *E = sl.get_warnings_ptr(); E; E = E->next()) {
warnings.push_back(E->get());
}
if (warnings.size() > 0 && err == OK) {
warnings.sort_custom<WarningsComparator>();
_update_warning_panel();
} else {
set_warning_nb(0);
}
emit_signal("script_changed");
}
void ShaderTextEditor::_update_warning_panel() {
int warning_count = 0;
warnings_panel->push_table(2);
for (int i = 0; i < warnings.size(); i++) {
ShaderWarning &w = warnings[i];
if (warning_count == 0) {
if (saved_treat_warning_as_errors) {
String error_text = "error(" + itos(w.get_line()) + "): " + w.get_message() + " " + TTR("Warnings should be fixed to prevent errors.");
set_error_pos(w.get_line() - 1, 0);
set_error(error_text);
get_text_editor()->set_line_background_color(w.get_line() - 1, marked_line_color);
}
}
warning_count++;
// First cell.
warnings_panel->push_cell();
warnings_panel->push_meta(w.get_line() - 1);
warnings_panel->push_color(warnings_panel->get_theme_color("warning_color", "Editor"));
warnings_panel->add_text(TTR("Line") + " " + itos(w.get_line()));
warnings_panel->add_text(" (" + w.get_name() + "):");
warnings_panel->pop(); // Color.
warnings_panel->pop(); // Meta goto.
warnings_panel->pop(); // Cell.
// Second cell.
warnings_panel->push_cell();
warnings_panel->add_text(w.get_message());
warnings_panel->pop(); // Cell.
}
warnings_panel->pop(); // Table.
set_warning_nb(warning_count);
}
void ShaderTextEditor::_bind_methods() {
}
@ -321,10 +390,6 @@ void ShaderEditor::_notification(int p_what) {
}
}
void ShaderEditor::_params_changed() {
shader_editor->_validate_script();
}
void ShaderEditor::_editor_settings_changed() {
shader_editor->update_editor_settings();
@ -333,8 +398,19 @@ void ShaderEditor::_editor_settings_changed() {
shader_editor->get_text_editor()->set_draw_executing_lines_gutter(false);
}
void ShaderEditor::_show_warnings_panel(bool p_show) {
warnings_panel->set_visible(p_show);
}
void ShaderEditor::_warning_clicked(Variant p_line) {
if (p_line.get_type() == Variant::INT) {
shader_editor->get_text_editor()->cursor_set_line(p_line.operator int64_t());
}
}
void ShaderEditor::_bind_methods() {
ClassDB::bind_method("_params_changed", &ShaderEditor::_params_changed);
ClassDB::bind_method("_show_warnings_panel", &ShaderEditor::_show_warnings_panel);
ClassDB::bind_method("_warning_clicked", &ShaderEditor::_warning_clicked);
}
void ShaderEditor::ensure_select_current() {
@ -352,6 +428,47 @@ void ShaderEditor::goto_line_selection(int p_line, int p_begin, int p_end) {
shader_editor->goto_line_selection(p_line, p_begin, p_end);
}
void ShaderEditor::_project_settings_changed() {
_update_warnings(true);
}
void ShaderEditor::_update_warnings(bool p_validate) {
bool changed = false;
bool warnings_enabled = GLOBAL_GET("debug/shader_language/warnings/enable").booleanize();
if (warnings_enabled != saved_warnings_enabled) {
saved_warnings_enabled = warnings_enabled;
changed = true;
}
bool treat_warning_as_errors = GLOBAL_GET("debug/shader_language/warnings/treat_warnings_as_errors").booleanize();
if (treat_warning_as_errors != saved_treat_warning_as_errors) {
saved_treat_warning_as_errors = treat_warning_as_errors;
changed = true;
}
bool update_flags = false;
for (int i = 0; i < ShaderWarning::WARNING_MAX; i++) {
ShaderWarning::Code code = (ShaderWarning::Code)i;
bool value = GLOBAL_GET("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code(code).to_lower());
if (saved_warnings[code] != value) {
saved_warnings[code] = value;
update_flags = true;
changed = true;
}
}
if (update_flags) {
saved_warning_flags = (uint32_t)ShaderWarning::get_flags_from_codemap(saved_warnings);
}
if (p_validate && changed && shader_editor && shader_editor->get_edited_shader().is_valid()) {
shader_editor->validate_script();
}
}
void ShaderEditor::_check_for_external_edit() {
if (shader.is_null() || !shader.is_valid()) {
return;
@ -523,13 +640,22 @@ void ShaderEditor::_make_context_menu(bool p_selection, Vector2 p_position) {
}
ShaderEditor::ShaderEditor(EditorNode *p_node) {
GLOBAL_DEF("debug/shader_language/warnings/enable", true);
GLOBAL_DEF("debug/shader_language/warnings/treat_warnings_as_errors", false);
for (int i = 0; i < (int)ShaderWarning::WARNING_MAX; i++) {
GLOBAL_DEF("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code((ShaderWarning::Code)i).to_lower(), true);
}
_update_warnings(false);
shader_editor = memnew(ShaderTextEditor);
shader_editor->set_v_size_flags(SIZE_EXPAND_FILL);
shader_editor->add_theme_constant_override("separation", 0);
shader_editor->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
shader_editor->connect("show_warnings_panel", callable_mp(this, &ShaderEditor::_show_warnings_panel));
shader_editor->connect("script_changed", callable_mp(this, &ShaderEditor::apply_shaders));
EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ShaderEditor::_editor_settings_changed));
ProjectSettingsEditor::get_singleton()->connect("confirmed", callable_mp(this, &ShaderEditor::_project_settings_changed));
shader_editor->get_text_editor()->set_callhint_settings(
EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"),
@ -614,7 +740,23 @@ ShaderEditor::ShaderEditor(EditorNode *p_node) {
hbc->add_child(goto_menu);
hbc->add_child(help_menu);
hbc->add_theme_style_override("panel", p_node->get_gui_base()->get_theme_stylebox("ScriptEditorPanel", "EditorStyles"));
main_container->add_child(shader_editor);
VSplitContainer *editor_box = memnew(VSplitContainer);
main_container->add_child(editor_box);
editor_box->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
editor_box->set_v_size_flags(SIZE_EXPAND_FILL);
editor_box->add_child(shader_editor);
warnings_panel = memnew(RichTextLabel);
warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE));
warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL);
warnings_panel->set_meta_underline(true);
warnings_panel->set_selection_enabled(true);
warnings_panel->set_focus_mode(FOCUS_CLICK);
warnings_panel->hide();
warnings_panel->connect("meta_clicked", callable_mp(this, &ShaderEditor::_warning_clicked));
editor_box->add_child(warnings_panel);
shader_editor->set_warnings_panel(warnings_panel);
goto_line_dialog = memnew(GotoLineDialog);
add_child(goto_line_dialog);

View file

@ -35,6 +35,7 @@
#include "editor/editor_plugin.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/panel_container.h"
#include "scene/gui/rich_text_label.h"
#include "scene/gui/tab_container.h"
#include "scene/gui/text_edit.h"
#include "scene/main/timer.h"
@ -46,10 +47,17 @@ class ShaderTextEditor : public CodeTextEditor {
Color marked_line_color = Color(1, 1, 1);
struct WarningsComparator {
_ALWAYS_INLINE_ bool operator()(const ShaderWarning &p_a, const ShaderWarning &p_b) const { return (p_a.get_line() < p_b.get_line()); }
};
Ref<CodeHighlighter> syntax_highlighter;
RichTextLabel *warnings_panel = nullptr;
Ref<Shader> shader;
List<ShaderWarning> warnings;
void _check_shader_mode();
void _update_warning_panel();
protected:
static void _bind_methods();
@ -61,6 +69,7 @@ public:
virtual void _validate_script() override;
void reload_text();
void set_warnings_panel(RichTextLabel *p_warnings_panel);
Ref<Shader> get_edited_shader() const;
void set_edited_shader(const Ref<Shader> &p_shader);
@ -102,6 +111,7 @@ class ShaderEditor : public PanelContainer {
PopupMenu *bookmarks_menu;
MenuButton *help_menu;
PopupMenu *context_menu;
RichTextLabel *warnings_panel = nullptr;
uint64_t idle;
GotoLineDialog *goto_line_dialog;
@ -111,13 +121,16 @@ class ShaderEditor : public PanelContainer {
ShaderTextEditor *shader_editor;
void _menu_option(int p_option);
void _params_changed();
mutable Ref<Shader> shader;
void _editor_settings_changed();
void _project_settings_changed();
void _check_for_external_edit();
void _reload_shader_from_disk();
void _show_warnings_panel(bool p_show);
void _warning_clicked(Variant p_line);
void _update_warnings(bool p_validate);
protected:
void _notification(int p_what);

View file

@ -33,6 +33,8 @@
#include "core/string/print_string.h"
#include "servers/rendering_server.h"
#define HAS_WARNING(flag) (warning_flags & flag)
static bool _is_text_char(char32_t c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
}
@ -901,6 +903,8 @@ bool ShaderLanguage::is_token_nonvoid_datatype(TokenType p_type) {
void ShaderLanguage::clear() {
current_function = StringName();
last_name = StringName();
last_type = IDENTIFIER_MAX;
completion_type = COMPLETION_NONE;
completion_block = nullptr;
@ -908,12 +912,20 @@ void ShaderLanguage::clear() {
completion_class = SubClassTag::TAG_GLOBAL;
completion_struct = StringName();
#ifdef DEBUG_ENABLED
used_constants.clear();
used_varyings.clear();
used_uniforms.clear();
used_functions.clear();
used_structs.clear();
warnings.clear();
#endif // DEBUG_ENABLED
error_line = 0;
tk_line = 1;
char_idx = 0;
error_set = false;
error_str = "";
last_const = false;
while (nodes) {
Node *n = nodes;
nodes = nodes->next;
@ -921,6 +933,35 @@ void ShaderLanguage::clear() {
}
}
#ifdef DEBUG_ENABLED
void ShaderLanguage::_parse_used_identifier(const StringName &p_identifier, IdentifierType p_type) {
switch (p_type) {
case IdentifierType::IDENTIFIER_CONSTANT:
if (HAS_WARNING(ShaderWarning::UNUSED_CONSTANT_FLAG) && used_constants.has(p_identifier)) {
used_constants[p_identifier].used = true;
}
break;
case IdentifierType::IDENTIFIER_VARYING:
if (HAS_WARNING(ShaderWarning::UNUSED_VARYING_FLAG) && used_varyings.has(p_identifier)) {
used_varyings[p_identifier].used = true;
}
break;
case IdentifierType::IDENTIFIER_UNIFORM:
if (HAS_WARNING(ShaderWarning::UNUSED_UNIFORM_FLAG) && used_uniforms.has(p_identifier)) {
used_uniforms[p_identifier].used = true;
}
break;
case IdentifierType::IDENTIFIER_FUNCTION:
if (HAS_WARNING(ShaderWarning::UNUSED_FUNCTION_FLAG) && used_functions.has(p_identifier)) {
used_functions[p_identifier].used = true;
}
break;
default:
break;
}
}
#endif // DEBUG_ENABLED
bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_reassign, const FunctionInfo &p_function_info, const StringName &p_identifier, DataType *r_data_type, IdentifierType *r_type, bool *r_is_const, int *r_array_size, StringName *r_struct_name, ConstantNode::Value *r_constant_value) {
if (p_function_info.built_ins.has(p_identifier)) {
if (r_data_type) {
@ -3602,6 +3643,11 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
if (shader->structs.has(identifier)) {
pstruct = shader->structs[identifier].shader_struct;
#ifdef DEBUG_ENABLED
if (check_warnings && HAS_WARNING(ShaderWarning::UNUSED_STRUCT_FLAG) && used_structs.has(identifier)) {
used_structs[identifier].used = true;
}
#endif // DEBUG_ENABLED
struct_init = true;
}
@ -3825,11 +3871,17 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
}
}
expr = func;
#ifdef DEBUG_ENABLED
if (check_warnings) {
_parse_used_identifier(name, IdentifierType::IDENTIFIER_FUNCTION);
}
#endif // DEBUG_ENABLED
}
} else {
//an identifier
last_const = false;
last_name = identifier;
last_type = IDENTIFIER_MAX;
_set_tkpos(pos);
DataType data_type;
@ -3874,12 +3926,16 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
}
}
}
last_const = is_const;
if (ident_type == IDENTIFIER_FUNCTION) {
_set_error("Can't use function as identifier: " + String(identifier));
return nullptr;
}
if (is_const) {
last_type = IDENTIFIER_CONSTANT;
} else {
last_type = ident_type;
}
}
Node *index_expression = nullptr;
@ -3953,7 +4009,6 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
arrname->assign_expression = assign_expression;
arrname->is_const = is_const;
expr = arrname;
} else {
VariableNode *varname = alloc_node<VariableNode>();
varname->name = identifier;
@ -3962,6 +4017,11 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
varname->struct_name = struct_name;
expr = varname;
}
#ifdef DEBUG_ENABLED
if (check_warnings) {
_parse_used_identifier(identifier, ident_type);
}
#endif // DEBUG_ENABLED
}
} else if (tk.type == TK_OP_ADD) {
continue; //this one does nothing
@ -4290,8 +4350,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
if (array_size > 0) {
tk = _get_token();
if (tk.type == TK_OP_ASSIGN) {
if (last_const) {
last_const = false;
if (last_type == IDENTIFIER_CONSTANT) {
_set_error("Constants cannot be modified.");
return nullptr;
}
@ -4648,9 +4707,10 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
bool unary = false;
bool ternary = false;
Operator op = expression[i].op;
int priority;
switch (expression[i].op) {
switch (op) {
case OP_EQUAL:
priority = 8;
break;
@ -4771,6 +4831,12 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
ERR_FAIL_V(nullptr); //unexpected operator
}
#if DEBUG_ENABLED
if (check_warnings && HAS_WARNING(ShaderWarning::FLOAT_COMPARISON_FLAG) && (op == OP_EQUAL || op == OP_NOT_EQUAL) && expression[i - 1].node->get_datatype() == TYPE_FLOAT && expression[i + 1].node->get_datatype() == TYPE_FLOAT) {
_add_line_warning(ShaderWarning::FLOAT_COMPARISON);
}
#endif // DEBUG_ENABLED
if (priority < min_priority) {
// < is used for left to right (default)
// <= is used for right to left
@ -5483,7 +5549,6 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
p_block->statements.push_back(vardecl);
p_block->variables[name] = var;
if (tk.type == TK_COMMA) {
if (p_block->block_type == BlockNode::BLOCK_TYPE_FOR) {
_set_error("Multiple declarations in 'for' loop are not implemented yet.");
@ -6313,7 +6378,11 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
}
shader->structs[st.name] = st;
shader->vstructs.push_back(st); // struct's order is important!
#ifdef DEBUG_ENABLED
if (check_warnings && HAS_WARNING(ShaderWarning::UNUSED_STRUCT_FLAG)) {
used_structs.insert(st.name, Usage(tk_line));
}
#endif // DEBUG_ENABLED
} break;
case TK_GLOBAL: {
tk = _get_token();
@ -6660,6 +6729,12 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
}
shader->uniforms[name] = uniform2;
#ifdef DEBUG_ENABLED
if (check_warnings && HAS_WARNING(ShaderWarning::UNUSED_UNIFORM_FLAG)) {
used_uniforms.insert(name, Usage(tk_line));
}
#endif // DEBUG_ENABLED
//reset scope for next uniform
uniform_scope = ShaderNode::Uniform::SCOPE_LOCAL;
@ -6703,6 +6778,11 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
}
shader->varyings[name] = varying;
#ifdef DEBUG_ENABLED
if (check_warnings && HAS_WARNING(ShaderWarning::UNUSED_VARYING_FLAG)) {
used_varyings.insert(name, Usage(tk_line));
}
#endif // DEBUG_ENABLED
}
} break;
@ -7049,6 +7129,11 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
shader->constants[name] = constant;
shader->vconstants.push_back(constant);
#ifdef DEBUG_ENABLED
if (check_warnings && HAS_WARNING(ShaderWarning::UNUSED_CONSTANT_FLAG)) {
used_constants.insert(name, Usage(tk_line));
}
#endif // DEBUG_ENABLED
if (tk.type == TK_COMMA) {
tk = _get_token();
@ -7110,6 +7195,12 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct
if (p_functions.has(name)) {
func_node->can_discard = p_functions[name].can_discard;
} else {
#ifdef DEBUG_ENABLED
if (check_warnings && HAS_WARNING(ShaderWarning::UNUSED_FUNCTION_FLAG)) {
used_functions.insert(name, Usage(tk_line));
}
#endif // DEBUG_ENABLED
}
func_node->body = alloc_node<BlockNode>();
@ -7443,6 +7534,33 @@ String ShaderLanguage::get_shader_type(const String &p_code) {
return String();
}
#ifdef DEBUG_ENABLED
void ShaderLanguage::_check_warning_accums() {
for (Map<ShaderWarning::Code, Map<StringName, Usage> *>::Element *E = warnings_check_map.front(); E; E = E->next()) {
for (const Map<StringName, Usage>::Element *U = (*E->get()).front(); U; U = U->next()) {
if (!U->get().used) {
_add_warning(E->key(), U->get().decl_line, U->key());
}
}
}
}
List<ShaderWarning>::Element *ShaderLanguage::get_warnings_ptr() {
return warnings.front();
}
void ShaderLanguage::enable_warning_checking(bool p_enabled) {
check_warnings = p_enabled;
}
bool ShaderLanguage::is_warning_checking_enabled() const {
return check_warnings;
}
void ShaderLanguage::set_warning_flags(uint32_t p_flags) {
warning_flags = p_flags;
}
uint32_t ShaderLanguage::get_warning_flags() const {
return warning_flags;
}
#endif // DEBUG_ENABLED
Error ShaderLanguage::compile(const String &p_code, const Map<StringName, FunctionInfo> &p_functions, const Vector<StringName> &p_render_modes, const VaryingFunctionNames &p_varying_function_names, const Set<String> &p_shader_types, GlobalVariableGetTypeFunc p_global_variable_type_func) {
clear();
@ -7455,6 +7573,12 @@ Error ShaderLanguage::compile(const String &p_code, const Map<StringName, Functi
shader = alloc_node<ShaderNode>();
Error err = _parse_shader(p_functions, p_render_modes, p_shader_types);
#ifdef DEBUG_ENABLED
if (check_warnings) {
_check_warning_accums();
}
#endif // DEBUG_ENABLED
if (err != OK) {
return err;
}
@ -7864,6 +7988,14 @@ ShaderLanguage::ShaderNode *ShaderLanguage::get_shader() {
ShaderLanguage::ShaderLanguage() {
nodes = nullptr;
completion_class = TAG_GLOBAL;
#if DEBUG_ENABLED
warnings_check_map.insert(ShaderWarning::UNUSED_CONSTANT, &used_constants);
warnings_check_map.insert(ShaderWarning::UNUSED_FUNCTION, &used_functions);
warnings_check_map.insert(ShaderWarning::UNUSED_STRUCT, &used_structs);
warnings_check_map.insert(ShaderWarning::UNUSED_UNIFORM, &used_uniforms);
warnings_check_map.insert(ShaderWarning::UNUSED_VARYING, &used_varyings);
#endif // DEBUG_ENABLED
}
ShaderLanguage::~ShaderLanguage() {

View file

@ -39,6 +39,10 @@
#include "core/typedefs.h"
#include "core/variant/variant.h"
#ifdef DEBUG_ENABLED
#include "shader_warnings.h"
#endif // DEBUG_ENABLED
class ShaderLanguage {
public:
struct TkPos {
@ -803,12 +807,42 @@ private:
String error_str;
int error_line;
#ifdef DEBUG_ENABLED
struct Usage {
int decl_line;
bool used = false;
Usage(int p_decl_line = -1) {
decl_line = p_decl_line;
}
};
Map<StringName, Usage> used_constants;
Map<StringName, Usage> used_varyings;
Map<StringName, Usage> used_uniforms;
Map<StringName, Usage> used_functions;
Map<StringName, Usage> used_structs;
Map<ShaderWarning::Code, Map<StringName, Usage> *> warnings_check_map;
List<ShaderWarning> warnings;
bool check_warnings = false;
uint32_t warning_flags;
void _add_line_warning(ShaderWarning::Code p_code, const StringName &p_subject = "") {
warnings.push_back(ShaderWarning(p_code, tk_line, p_subject));
}
void _add_warning(ShaderWarning::Code p_code, int p_line, const StringName &p_subject = "") {
warnings.push_back(ShaderWarning(p_code, p_line, p_subject));
}
void _check_warning_accums();
#endif // DEBUG_ENABLED
String code;
int char_idx;
int tk_line;
StringName current_function;
bool last_const = false;
StringName last_name;
VaryingFunctionNames varying_function_names;
@ -849,9 +883,15 @@ private:
IDENTIFIER_LOCAL_VAR,
IDENTIFIER_BUILTIN_VAR,
IDENTIFIER_CONSTANT,
IDENTIFIER_MAX,
};
IdentifierType last_type = IDENTIFIER_MAX;
bool _find_identifier(const BlockNode *p_block, bool p_allow_reassign, const FunctionInfo &p_function_info, const StringName &p_identifier, DataType *r_data_type = nullptr, IdentifierType *r_type = nullptr, bool *r_is_const = nullptr, int *r_array_size = nullptr, StringName *r_struct_name = nullptr, ConstantNode::Value *r_constant_value = nullptr);
#ifdef DEBUG_ENABLED
void _parse_used_identifier(const StringName &p_identifier, IdentifierType p_type);
#endif // DEBUG_ENABLED
bool _is_operator_assign(Operator p_op) const;
bool _validate_assign(Node *p_node, const FunctionInfo &p_function_info, String *r_message = nullptr);
bool _validate_operator(OperatorNode *p_op, DataType *r_ret_type = nullptr);
@ -910,6 +950,16 @@ private:
Error _find_last_flow_op_in_op(ControlFlowNode *p_flow, FlowOperation p_op);
public:
#ifdef DEBUG_ENABLED
List<ShaderWarning>::Element *get_warnings_ptr();
void enable_warning_checking(bool p_enabled);
bool is_warning_checking_enabled() const;
void set_warning_flags(uint32_t p_flags);
uint32_t get_warning_flags() const;
#endif // DEBUG_ENABLED
//static void get_keyword_list(ShaderType p_type,List<String> *p_keywords);
void clear();

View file

@ -0,0 +1,131 @@
/*************************************************************************/
/* shader_warnings.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "shader_warnings.h"
#include "core/variant/variant.h"
#ifdef DEBUG_ENABLED
ShaderWarning::Code ShaderWarning::get_code() const {
return code;
}
int ShaderWarning::get_line() const {
return line;
}
const StringName &ShaderWarning::get_subject() const {
return subject;
}
String ShaderWarning::get_message() const {
switch (code) {
case FLOAT_COMPARISON:
return vformat("Direct floating-point comparison (this may not evaluate to `true` as you expect). Instead, use `abs(a - b) < 0.0001` for an approximate but predictable comparison.");
case UNUSED_CONSTANT:
return vformat("The const '%s' is declared but never used.", subject);
case UNUSED_FUNCTION:
return vformat("The function '%s' is declared but never used.", subject);
case UNUSED_STRUCT:
return vformat("The struct '%s' is declared but never used.", subject);
case UNUSED_UNIFORM:
return vformat("The uniform '%s' is declared but never used.", subject);
case UNUSED_VARYING:
return vformat("The varying '%s' is declared but never used.", subject);
default:
break;
}
return String();
}
String ShaderWarning::get_name() const {
return get_name_from_code(code);
}
String ShaderWarning::get_name_from_code(Code p_code) {
ERR_FAIL_INDEX_V(p_code, WARNING_MAX, String());
static const char *names[] = {
"FLOAT_COMPARISON",
"UNUSED_CONSTANT",
"UNUSED_FUNCTION",
"UNUSED_STRUCT",
"UNUSED_UNIFORM",
"UNUSED_VARYING",
};
static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
return names[(int)p_code];
}
ShaderWarning::Code ShaderWarning::get_code_from_name(const String &p_name) {
for (int i = 0; i < WARNING_MAX; i++) {
if (get_name_from_code((Code)i) == p_name) {
return (Code)i;
}
}
ERR_FAIL_V_MSG(WARNING_MAX, "Invalid shader warning name: " + p_name);
}
static Map<int, uint32_t> *code_to_flags_map = nullptr;
static void init_code_to_flags_map() {
code_to_flags_map = memnew((Map<int, uint32_t>));
code_to_flags_map->insert(ShaderWarning::FLOAT_COMPARISON, ShaderWarning::FLOAT_COMPARISON_FLAG);
code_to_flags_map->insert(ShaderWarning::UNUSED_CONSTANT, ShaderWarning::UNUSED_CONSTANT_FLAG);
code_to_flags_map->insert(ShaderWarning::UNUSED_FUNCTION, ShaderWarning::UNUSED_FUNCTION_FLAG);
code_to_flags_map->insert(ShaderWarning::UNUSED_STRUCT, ShaderWarning::UNUSED_STRUCT_FLAG);
code_to_flags_map->insert(ShaderWarning::UNUSED_UNIFORM, ShaderWarning::UNUSED_UNIFORM_FLAG);
code_to_flags_map->insert(ShaderWarning::UNUSED_VARYING, ShaderWarning::UNUSED_VARYING_FLAG);
}
ShaderWarning::CodeFlags ShaderWarning::get_flags_from_codemap(const Map<Code, bool> &p_map) {
uint32_t result = 0U;
if (code_to_flags_map == nullptr) {
init_code_to_flags_map();
}
for (Map<Code, bool>::Element *E = p_map.front(); E; E = E->next()) {
if (E->get()) {
ERR_FAIL_COND_V(!code_to_flags_map->has((int)E->key()), ShaderWarning::NONE_FLAG);
result |= (*code_to_flags_map)[(int)E->key()];
}
}
return (CodeFlags)result;
}
ShaderWarning::ShaderWarning(Code p_code, int p_line, const StringName &p_subject) :
code(p_code), line(p_line), subject(p_subject) {
}
#endif // DEBUG_ENABLED

View file

@ -0,0 +1,83 @@
/*************************************************************************/
/* shader_warnings.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef SHADER_WARNINGS
#define SHADER_WARNINGS
#ifdef DEBUG_ENABLED
#include "core/string/string_name.h"
#include "core/templates/list.h"
#include "core/templates/map.h"
class ShaderWarning {
public:
enum Code {
FLOAT_COMPARISON,
UNUSED_CONSTANT,
UNUSED_FUNCTION,
UNUSED_STRUCT,
UNUSED_UNIFORM,
UNUSED_VARYING,
WARNING_MAX,
};
enum CodeFlags : uint32_t {
NONE_FLAG = 0U,
FLOAT_COMPARISON_FLAG = 1U,
UNUSED_CONSTANT_FLAG = 2U,
UNUSED_FUNCTION_FLAG = 4U,
UNUSED_STRUCT_FLAG = 8U,
UNUSED_UNIFORM_FLAG = 16U,
UNUSED_VARYING_FLAG = 32U,
};
private:
Code code;
int line;
StringName subject;
public:
Code get_code() const;
int get_line() const;
const StringName &get_subject() const;
String get_message() const;
String get_name() const;
static String get_name_from_code(Code p_code);
static Code get_code_from_name(const String &p_name);
static CodeFlags get_flags_from_codemap(const Map<Code, bool> &p_map);
ShaderWarning(Code p_code = WARNING_MAX, int p_line = -1, const StringName &p_subject = "");
};
#endif // DEBUG_ENABLED
#endif // SHADER_WARNINGS