1/**************************************************************************/
2/* shader_file_editor_plugin.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "shader_file_editor_plugin.h"
32
33#include "core/io/resource_loader.h"
34#include "core/io/resource_saver.h"
35#include "core/os/keyboard.h"
36#include "core/os/os.h"
37#include "editor/editor_node.h"
38#include "editor/editor_scale.h"
39#include "editor/editor_settings.h"
40#include "editor/editor_string_names.h"
41#include "scene/gui/item_list.h"
42#include "scene/gui/split_container.h"
43#include "servers/display_server.h"
44#include "servers/rendering/shader_types.h"
45
46/*** SHADER SCRIPT EDITOR ****/
47
48/*** SCRIPT EDITOR ******/
49
50void ShaderFileEditor::_update_version(const StringName &p_version_txt, const RD::ShaderStage p_stage) {
51}
52
53void ShaderFileEditor::_version_selected(int p_option) {
54 int c = versions->get_current();
55 StringName version_txt = versions->get_item_metadata(c);
56
57 RD::ShaderStage stage = RD::SHADER_STAGE_MAX;
58 int first_found = -1;
59
60 Ref<RDShaderSPIRV> bytecode = shader_file->get_spirv(version_txt);
61 ERR_FAIL_COND(bytecode.is_null());
62
63 for (int i = 0; i < RD::SHADER_STAGE_MAX; i++) {
64 if (bytecode->get_stage_bytecode(RD::ShaderStage(i)).is_empty() && bytecode->get_stage_compile_error(RD::ShaderStage(i)) == String()) {
65 stages[i]->set_icon(Ref<Texture2D>());
66 continue;
67 }
68
69 Ref<Texture2D> icon;
70 if (bytecode->get_stage_compile_error(RD::ShaderStage(i)) != String()) {
71 icon = get_editor_theme_icon(SNAME("ImportFail"));
72 } else {
73 icon = get_editor_theme_icon(SNAME("ImportCheck"));
74 }
75 stages[i]->set_icon(icon);
76
77 if (first_found == -1) {
78 first_found = i;
79 }
80
81 if (stages[i]->is_pressed()) {
82 stage = RD::ShaderStage(i);
83 break;
84 }
85 }
86
87 error_text->clear();
88
89 if (stage == RD::SHADER_STAGE_MAX) { //need to change stage, does not have it
90 if (first_found == -1) {
91 error_text->add_text(TTR("No valid shader stages found."));
92 return; //well you did not put any stage I guess?
93 }
94 stages[first_found]->set_pressed(true);
95 stage = RD::ShaderStage(first_found);
96 }
97
98 String error = bytecode->get_stage_compile_error(stage);
99
100 error_text->push_font(get_theme_font(SNAME("source"), EditorStringName(EditorFonts)));
101
102 if (error.is_empty()) {
103 error_text->add_text(TTR("Shader stage compiled without errors."));
104 } else {
105 error_text->add_text(error);
106 }
107}
108
109void ShaderFileEditor::_update_options() {
110 ERR_FAIL_COND(shader_file.is_null());
111
112 if (!shader_file->get_base_error().is_empty()) {
113 stage_hb->hide();
114 versions->hide();
115 error_text->clear();
116 error_text->push_font(get_theme_font(SNAME("source"), EditorStringName(EditorFonts)));
117 error_text->add_text(vformat(TTR("File structure for '%s' contains unrecoverable errors:\n\n"), shader_file->get_path().get_file()));
118 error_text->add_text(shader_file->get_base_error());
119 return;
120 }
121
122 stage_hb->show();
123 versions->show();
124
125 int c = versions->get_current();
126 //remember current
127 versions->clear();
128 TypedArray<StringName> version_list = shader_file->get_version_list();
129
130 if (c >= version_list.size()) {
131 c = version_list.size() - 1;
132 }
133 if (c < 0) {
134 c = 0;
135 }
136
137 StringName current_version;
138
139 for (int i = 0; i < version_list.size(); i++) {
140 String title = version_list[i];
141 if (title.is_empty()) {
142 title = "default";
143 }
144
145 Ref<Texture2D> icon;
146
147 Ref<RDShaderSPIRV> bytecode = shader_file->get_spirv(version_list[i]);
148 ERR_FAIL_COND(bytecode.is_null());
149
150 bool failed = false;
151 for (int j = 0; j < RD::SHADER_STAGE_MAX; j++) {
152 String error = bytecode->get_stage_compile_error(RD::ShaderStage(j));
153 if (!error.is_empty()) {
154 failed = true;
155 }
156 }
157
158 if (failed) {
159 icon = get_editor_theme_icon(SNAME("ImportFail"));
160 } else {
161 icon = get_editor_theme_icon(SNAME("ImportCheck"));
162 }
163
164 versions->add_item(title, icon);
165 versions->set_item_metadata(i, version_list[i]);
166
167 if (i == c) {
168 versions->select(i);
169 current_version = version_list[i];
170 }
171 }
172
173 if (version_list.size() == 0) {
174 for (int i = 0; i < RD::SHADER_STAGE_MAX; i++) {
175 stages[i]->set_disabled(true);
176 }
177 return;
178 }
179
180 Ref<RDShaderSPIRV> bytecode = shader_file->get_spirv(current_version);
181 ERR_FAIL_COND(bytecode.is_null());
182 int first_valid = -1;
183 int current = -1;
184 for (int i = 0; i < RD::SHADER_STAGE_MAX; i++) {
185 Vector<uint8_t> bc = bytecode->get_stage_bytecode(RD::ShaderStage(i));
186 String error = bytecode->get_stage_compile_error(RD::ShaderStage(i));
187 bool disable = error.is_empty() && bc.is_empty();
188 stages[i]->set_disabled(disable);
189 if (!disable) {
190 if (stages[i]->is_pressed()) {
191 current = i;
192 }
193 first_valid = i;
194 }
195 }
196
197 if (current == -1 && first_valid != -1) {
198 stages[first_valid]->set_pressed(true);
199 }
200
201 _version_selected(0);
202}
203
204void ShaderFileEditor::_notification(int p_what) {
205 switch (p_what) {
206 case NOTIFICATION_WM_WINDOW_FOCUS_IN: {
207 if (is_visible_in_tree() && shader_file.is_valid()) {
208 _update_options();
209 }
210 } break;
211 }
212}
213
214void ShaderFileEditor::_editor_settings_changed() {
215 if (is_visible_in_tree() && shader_file.is_valid()) {
216 _update_options();
217 }
218}
219
220void ShaderFileEditor::_bind_methods() {
221}
222
223void ShaderFileEditor::edit(const Ref<RDShaderFile> &p_shader) {
224 if (p_shader.is_null()) {
225 if (shader_file.is_valid()) {
226 shader_file->disconnect_changed(callable_mp(this, &ShaderFileEditor::_shader_changed));
227 }
228 return;
229 }
230
231 if (shader_file == p_shader) {
232 return;
233 }
234
235 shader_file = p_shader;
236
237 if (shader_file.is_valid()) {
238 shader_file->connect_changed(callable_mp(this, &ShaderFileEditor::_shader_changed));
239 }
240
241 _update_options();
242}
243
244void ShaderFileEditor::_shader_changed() {
245 if (is_visible_in_tree()) {
246 _update_options();
247 }
248}
249
250ShaderFileEditor *ShaderFileEditor::singleton = nullptr;
251
252ShaderFileEditor::ShaderFileEditor() {
253 singleton = this;
254 HSplitContainer *main_hs = memnew(HSplitContainer);
255
256 add_child(main_hs);
257
258 versions = memnew(ItemList);
259 versions->connect("item_selected", callable_mp(this, &ShaderFileEditor::_version_selected));
260 versions->set_custom_minimum_size(Size2i(200 * EDSCALE, 0));
261 main_hs->add_child(versions);
262
263 VBoxContainer *main_vb = memnew(VBoxContainer);
264 main_vb->set_h_size_flags(SIZE_EXPAND_FILL);
265 main_hs->add_child(main_vb);
266
267 static const char *stage_str[RD::SHADER_STAGE_MAX] = {
268 "Vertex",
269 "Fragment",
270 "TessControl",
271 "TessEval",
272 "Compute"
273 };
274
275 stage_hb = memnew(HBoxContainer);
276 main_vb->add_child(stage_hb);
277
278 Ref<ButtonGroup> bg;
279 bg.instantiate();
280 for (int i = 0; i < RD::SHADER_STAGE_MAX; i++) {
281 Button *button = memnew(Button(stage_str[i]));
282 button->set_toggle_mode(true);
283 button->set_focus_mode(FOCUS_NONE);
284 stage_hb->add_child(button);
285 stages[i] = button;
286 button->set_button_group(bg);
287 button->connect("pressed", callable_mp(this, &ShaderFileEditor::_version_selected).bind(i));
288 }
289
290 error_text = memnew(RichTextLabel);
291 error_text->set_v_size_flags(SIZE_EXPAND_FILL);
292 error_text->set_selection_enabled(true);
293 error_text->set_context_menu_enabled(true);
294 main_vb->add_child(error_text);
295}
296
297void ShaderFileEditorPlugin::edit(Object *p_object) {
298 RDShaderFile *s = Object::cast_to<RDShaderFile>(p_object);
299 shader_editor->edit(s);
300}
301
302bool ShaderFileEditorPlugin::handles(Object *p_object) const {
303 RDShaderFile *shader = Object::cast_to<RDShaderFile>(p_object);
304 return shader != nullptr;
305}
306
307void ShaderFileEditorPlugin::make_visible(bool p_visible) {
308 if (p_visible) {
309 button->show();
310 EditorNode::get_singleton()->make_bottom_panel_item_visible(shader_editor);
311
312 } else {
313 button->hide();
314 if (shader_editor->is_visible_in_tree()) {
315 EditorNode::get_singleton()->hide_bottom_panel();
316 }
317 }
318}
319
320ShaderFileEditorPlugin::ShaderFileEditorPlugin() {
321 shader_editor = memnew(ShaderFileEditor);
322
323 shader_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE);
324 button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("ShaderFile"), shader_editor);
325 button->hide();
326}
327
328ShaderFileEditorPlugin::~ShaderFileEditorPlugin() {
329}
330