├── screenshot.png ├── src ├── shader_editor.cpp └── editor │ └── shader_editor.cpp ├── genie.lua ├── .gitignore ├── README.md └── LICENSE /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nem0/lumixengine_shader_editor/HEAD/screenshot.png -------------------------------------------------------------------------------- /src/shader_editor.cpp: -------------------------------------------------------------------------------- 1 | #define LUMIX_NO_CUSTOM_CRT 2 | #include "engine/plugin.h" 3 | #include "imgui/imgui.h" 4 | 5 | 6 | namespace Lumix 7 | { 8 | 9 | 10 | LUMIX_PLUGIN_ENTRY(shader_editor) 11 | { 12 | return nullptr; 13 | } 14 | 15 | 16 | } -------------------------------------------------------------------------------- /genie.lua: -------------------------------------------------------------------------------- 1 | if plugin "shader_editor" then 2 | files { 3 | "src/**.c", 4 | "src/**.cpp", 5 | "src/**.h", 6 | "genie.lua" 7 | } 8 | defines { "BUILDING_SHADER_EDITOR" } 9 | dynamic_link_plugin { "editor", "engine", "renderer", "core" } 10 | if build_studio then 11 | dynamic_link_plugin { "editor" } 12 | end 13 | end -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | tmp/ 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lumix Engine Shader Editor 2 | 3 | Visual shader editor for [Lumix Engine](https://github.com/nem0/LumixEngine/) 4 | 5 | [How to install this plugin](https://github.com/nem0/LumixEngine/wiki/available-plugins) 6 | 7 | You can also download [Lumix Engine from itch.io](https://mikulasflorek.itch.io/lumix-engine) including this plugin. 8 | 9 | ![Screenshot](screenshot.png) 10 | 11 | ## See it in action on YouTube 12 | * [Shield effect tutorial](https://www.youtube.com/watch?v=B-flQTi-CeA) 13 | * [Dissolve effect tutorial](https://www.youtube.com/watch?v=7OcggtIH6sg) 14 | * [Portal effect tutorial](https://www.youtube.com/watch?v=xXMm2oHM_fo) 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Mikulas Florek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/editor/shader_editor.cpp: -------------------------------------------------------------------------------- 1 | #include "core/crt.h" 2 | #include "core/log.h" 3 | #include "core/math.h" 4 | #include "core/os.h" 5 | #include "core/path.h" 6 | #include "core/profiler.h" 7 | #include "core/stream.h" 8 | #include "core/string.h" 9 | #include "editor/action.h" 10 | #include "editor/asset_browser.h" 11 | #include "editor/asset_compiler.h" 12 | #include "editor/editor_asset.h" 13 | #include "editor/studio_app.h" 14 | #include "editor/utils.h" 15 | #include "engine/component_uid.h" 16 | #include "engine/engine.h" 17 | #include "engine/plugin.h" 18 | #include "engine/world.h" 19 | #include "renderer/editor/particle_editor.h" 20 | #include "renderer/model.h" 21 | #include "renderer/renderer.h" 22 | #include "renderer/shader.h" 23 | #include "imgui/IconsFontAwesome5.h" 24 | 25 | 26 | namespace Lumix { 27 | 28 | namespace { 29 | 30 | struct ShaderEditor; 31 | 32 | enum class Version { 33 | FIRST, 34 | LAST 35 | }; 36 | 37 | enum class ShaderResourceEditorType : u32 { 38 | SURFACE, 39 | PARTICLE, 40 | FUNCTION 41 | }; 42 | 43 | // serialized, do not change order 44 | enum class ShaderNodeType { 45 | PBR, 46 | 47 | NUMBER, 48 | VEC2, 49 | VEC3, 50 | VEC4, 51 | SAMPLE, 52 | SWIZZLE, 53 | TIME, 54 | VERTEX_ID, 55 | POSITION, 56 | NORMAL, 57 | UV0, 58 | IF, 59 | APPEND, 60 | STATIC_SWITCH, 61 | MIX, 62 | 63 | SCALAR_PARAM, 64 | VEC4_PARAM, 65 | COLOR_PARAM, 66 | 67 | MULTIPLY, 68 | ADD, 69 | SUBTRACT, 70 | DIVIDE, 71 | 72 | DOT, 73 | CROSS, 74 | MIN, 75 | MAX, 76 | POW, 77 | DISTANCE, 78 | 79 | ABS, 80 | ALL, 81 | ANY, 82 | CEIL, 83 | COS, 84 | EXP, 85 | EXP2, 86 | FLOOR, 87 | FRACT, 88 | LOG, 89 | LOG2, 90 | NORMALIZE, 91 | NOT, 92 | ROUND, 93 | SATURATE, 94 | SIN, 95 | SQRT, 96 | TAN, 97 | TRANSPOSE, 98 | TRUNC, 99 | 100 | FRESNEL, 101 | LENGTH, 102 | VIEW_DIR, 103 | PIXEL_DEPTH, 104 | SCREEN_POSITION, 105 | SCENE_DEPTH, 106 | ONEMINUS, 107 | CODE, 108 | PIN, 109 | BACKFACE_SWITCH, 110 | 111 | FUNCTION_INPUT, 112 | FUNCTION_OUTPUT, 113 | FUNCTION_CALL, 114 | 115 | PARTICLE_STREAM 116 | }; 117 | 118 | struct ShaderEditorResource { 119 | using Link = NodeEditorLink; 120 | 121 | enum class ValueType : i32 { 122 | BOOL, 123 | FLOAT, 124 | INT, 125 | VEC2, 126 | VEC3, 127 | VEC4, 128 | IVEC4, 129 | 130 | COUNT, 131 | NONE 132 | }; 133 | 134 | struct Node : NodeEditorNode { 135 | Node(ShaderEditorResource& resource); 136 | virtual ~Node() {} 137 | 138 | virtual void serialize(OutputMemoryStream&blob) {} 139 | virtual void deserialize(InputMemoryStream&blob) {} 140 | virtual void printReference(OutputMemoryStream& blob, int output_idx) const; 141 | virtual ValueType getOutputType(int index) const { return ValueType::FLOAT; } 142 | virtual ShaderNodeType getType() const = 0; 143 | 144 | bool nodeGUI() override; 145 | bool generateOnce(OutputMemoryStream& blob); 146 | 147 | void inputSlot(); 148 | void outputSlot(); 149 | 150 | bool m_selected = false; 151 | bool m_reachable = false; 152 | bool m_generated = false; 153 | u32 m_input_count = 0; 154 | u32 m_output_count = 0; 155 | 156 | ShaderEditorResource& m_resource; 157 | String m_error; 158 | 159 | protected: 160 | virtual bool generate(OutputMemoryStream& blob) { return true; } 161 | virtual bool onGUI() = 0; 162 | bool error(const char* msg) { m_error = msg; return false; } 163 | }; 164 | 165 | ShaderEditorResource(const Path& path, ShaderEditor& editor, IAllocator& allocator) 166 | : m_editor(editor) 167 | , m_allocator(allocator) 168 | , m_links(m_allocator) 169 | , m_nodes(m_allocator) 170 | , m_path(path) 171 | {} 172 | 173 | ~ShaderEditorResource() { 174 | for (auto* node : m_nodes) { 175 | LUMIX_DELETE(m_allocator, node); 176 | } 177 | } 178 | 179 | bool load(StudioApp& app) { 180 | OutputMemoryStream content(m_allocator); 181 | if (!app.getEngine().getFileSystem().getContentSync(m_path, content)) { 182 | logError("Failed to read ", m_path); 183 | return false; 184 | } 185 | 186 | InputMemoryStream blob(content); 187 | if (!deserialize(blob)) logError("Failed to deserialize ", m_path); 188 | return true; 189 | } 190 | 191 | template 192 | static void forEachInput(const ShaderEditorResource& resource, int node_id, const F& f) { 193 | for (const ShaderEditorResource::Link& link : resource.m_links) { 194 | if (link.getToNode() == node_id) { 195 | const int iter = resource.m_nodes.find([&](const ShaderEditorResource::Node* node) { return node->m_id == link.getFromNode(); }); 196 | ShaderEditorResource::Node* from = resource.m_nodes[iter]; 197 | const u16 from_attr = link.getFromPin(); 198 | const u16 to_attr = link.getToPin(); 199 | f(from, from_attr, to_attr, u32(&link - resource.m_links.begin())); 200 | } 201 | } 202 | } 203 | 204 | void colorLinks() { 205 | const ImU32 colors[] = { 206 | IM_COL32(0x20, 0x20, 0xA0, 255), 207 | IM_COL32(0x20, 0xA0, 0x20, 255), 208 | IM_COL32(0x20, 0xA0, 0xA0, 255), 209 | IM_COL32(0xA0, 0x20, 0x20, 255), 210 | IM_COL32(0xA0, 0x20, 0xA0, 255), 211 | IM_COL32(0xA0, 0xA0, 0x20, 255), 212 | IM_COL32(0xA0, 0xA0, 0xA0, 255), 213 | }; 214 | 215 | for (Link& l : m_links) { 216 | l.color = IM_COL32(0xA0, 0xA0, 0xA0, 0xFF); 217 | } 218 | 219 | forEachInput(*this, m_nodes[0]->m_id, [&](ShaderEditorResource::Node* from, u16 from_attr, u16 to_attr, u32 link_idx) { 220 | colorLinks(colors[to_attr % lengthOf(colors)], link_idx); 221 | }); 222 | } 223 | 224 | void markReachableNodes() const { 225 | for (Node* n : m_nodes) { 226 | n->m_reachable = false; 227 | } 228 | markReachable(m_nodes[0]); 229 | } 230 | 231 | void clearGeneratedFlags() { 232 | for (Node* n : m_nodes) n->m_generated = false; 233 | } 234 | 235 | void destroyNode(Node* node) { 236 | for (i32 i = m_links.size() - 1; i >= 0; --i) { 237 | if (m_links[i].getFromNode() == node->m_id || m_links[i].getToNode() == node->m_id) { 238 | m_links.swapAndPop(i); 239 | } 240 | } 241 | 242 | LUMIX_DELETE(m_allocator, node); 243 | m_nodes.eraseItem(node); 244 | } 245 | 246 | void clear() { 247 | m_last_node_id = 0; 248 | m_links.clear(); 249 | for (Node* n : m_nodes) { 250 | LUMIX_DELETE(m_allocator, n); 251 | } 252 | m_nodes.clear(); 253 | } 254 | 255 | void markReachable(Node* node) const { 256 | node->m_reachable = true; 257 | 258 | forEachInput(*this, node->m_id, [&](ShaderEditorResource::Node* from, u16 from_attr, u16 to_attr, u32 link_idx){ 259 | markReachable(from); 260 | }); 261 | } 262 | 263 | void colorLinks(ImU32 color, u32 link_idx) { 264 | m_links[link_idx].color = color; 265 | const u32 from_node_id = m_links[link_idx].getFromNode(); 266 | for (u32 i = 0, c = m_links.size(); i < c; ++i) { 267 | if (m_links[i].getToNode() == from_node_id) colorLinks(color, i); 268 | } 269 | } 270 | 271 | void deleteSelectedNodes() { 272 | for (i32 i = m_nodes.size() - 1; i > 0; --i) { // we really don't want to delete node 0 (output) 273 | Node* node = m_nodes[i]; 274 | if (node->m_selected) { 275 | for (i32 j = m_links.size() - 1; j >= 0; --j) { 276 | if (m_links[j].getFromNode() == node->m_id || m_links[j].getToNode() == node->m_id) { 277 | m_links.erase(j); 278 | } 279 | } 280 | 281 | LUMIX_DELETE(m_allocator, node); 282 | m_nodes.swapAndPop(i); 283 | } 284 | } 285 | } 286 | 287 | void deleteUnreachable() { 288 | markReachableNodes(); 289 | colorLinks(); 290 | for (i32 i = m_nodes.size() - 1; i >= 0; --i) { 291 | Node* node = m_nodes[i]; 292 | if (!node->m_reachable) { 293 | for (i32 j = m_links.size() - 1; j >= 0; --j) { 294 | if (m_links[j].getFromNode() == node->m_id || m_links[j].getToNode() == node->m_id) { 295 | m_links.erase(j); 296 | } 297 | } 298 | 299 | LUMIX_DELETE(m_allocator, node); 300 | m_nodes.swapAndPop(i); 301 | } 302 | } 303 | } 304 | 305 | static void serializeNode(OutputMemoryStream& blob, Node& node) { 306 | int type = (int)node.getType(); 307 | blob.write(node.m_id); 308 | blob.write(type); 309 | blob.write(node.m_pos); 310 | 311 | node.serialize(blob); 312 | } 313 | 314 | Node& deserializeNode(InputMemoryStream& blob) { 315 | int type; 316 | u16 id; 317 | blob.read(id); 318 | blob.read(type); 319 | Node* node = createNode(type); 320 | node->m_id = id; 321 | m_nodes.push(node); 322 | blob.read(node->m_pos); 323 | 324 | node->deserialize(blob); 325 | return *node; 326 | } 327 | 328 | bool generate(String* source) { 329 | markReachableNodes(); 330 | colorLinks(); 331 | 332 | OutputMemoryStream blob(m_allocator); 333 | blob.reserve(32 * 1024); 334 | 335 | for (Node* n : m_nodes) n->m_error = ""; 336 | if (!m_nodes[0]->generateOnce(blob)) return false; 337 | 338 | if (source) { 339 | source->resize((u32)blob.size()); 340 | memcpy(source->getMutableData(), blob.data(), source->length()); 341 | source->getMutableData()[source->length()] = '\0'; 342 | } 343 | return true; 344 | } 345 | 346 | void serialize(OutputMemoryStream& blob) { 347 | blob.reserve(4096); 348 | blob.write(u32('_LSE')); 349 | blob.write(Version::LAST); 350 | blob.write(m_last_node_id); 351 | 352 | const i32 nodes_count = m_nodes.size(); 353 | blob.write(nodes_count); 354 | for(auto* node : m_nodes) { 355 | serializeNode(blob, *node); 356 | } 357 | 358 | const i32 links_count = m_links.size(); 359 | blob.write(links_count); 360 | for (Link& l : m_links) { 361 | blob.write(l.from); 362 | blob.write(l.to); 363 | } 364 | 365 | generate(nullptr); 366 | } 367 | 368 | bool deserialize(InputMemoryStream& blob) { 369 | Version version; 370 | u32 magic; 371 | blob.read(magic); 372 | if (magic != '_LSE') return false; 373 | blob.read(version); 374 | if (version > Version::LAST) return false; 375 | blob.read(m_last_node_id); 376 | 377 | int size; 378 | blob.read(size); 379 | for(int i = 0; i < size; ++i) { 380 | deserializeNode(blob); 381 | } 382 | 383 | blob.read(size); 384 | m_links.resize(size); 385 | for (Link& l : m_links) { 386 | blob.read(l.from); 387 | blob.read(l.to); 388 | } 389 | markReachableNodes(); 390 | colorLinks(); 391 | 392 | return true; 393 | } 394 | 395 | Node* createNode(int type); 396 | void init(ShaderResourceEditorType type); 397 | ShaderResourceEditorType getShaderType() const; 398 | ValueType getFunctionOutputType() const; 399 | 400 | IAllocator& m_allocator; 401 | ShaderEditor& m_editor; 402 | Path m_path; 403 | Array m_links; 404 | Array m_nodes; 405 | int m_last_node_id = 0; 406 | 407 | static ResourceType TYPE; 408 | }; 409 | 410 | ResourceType ShaderEditorResource::TYPE("shader_graph"); 411 | 412 | struct ShaderEditor final : StudioApp::IPlugin { 413 | struct FunctionPlugin : EditorAssetPlugin { 414 | FunctionPlugin(ShaderEditor& editor) 415 | : EditorAssetPlugin("Shader graph function", "sfn", TYPE, editor.m_app, editor.m_allocator) 416 | , m_editor(editor) 417 | {} 418 | 419 | void addSubresources(AssetCompiler& compiler, const Path& path, AtomicI32&) override { 420 | compiler.addResource(TYPE, path); 421 | m_editor.addFunction(path); 422 | } 423 | 424 | void openEditor(const Path& path) override { m_editor.open(path); } 425 | 426 | void createResource(OutputMemoryStream& blob) override { 427 | ShaderEditorResource res(Path("new shader function"), m_editor, m_editor.m_allocator); 428 | res.init(ShaderResourceEditorType::FUNCTION); 429 | res.serialize(blob); 430 | } 431 | 432 | ShaderEditor& m_editor; 433 | static ResourceType TYPE; 434 | }; 435 | 436 | struct AssetPlugin : EditorAssetPlugin { 437 | AssetPlugin(ShaderEditor& editor) 438 | : EditorAssetPlugin("Shader graph", "sed", Shader::TYPE, editor.m_app, editor.m_allocator) 439 | , m_editor(editor) 440 | {} 441 | 442 | bool compile(const Path& src) override { 443 | ShaderEditorResource res(src, m_editor, m_editor.m_allocator); 444 | if (!res.load(m_app)) { 445 | logError("Failed to load ", src); 446 | return false; 447 | } 448 | 449 | String source(m_editor.m_allocator); 450 | if (!res.generate(&source)) return false; 451 | 452 | Span span((const u8*)source.c_str(), source.length()); 453 | 454 | m_editor.registerDependencies(res); 455 | 456 | return m_editor.m_app.getAssetCompiler().writeCompiledResource(src, span); 457 | } 458 | 459 | void createResource(OutputMemoryStream& blob) override { 460 | ShaderEditorResource res(Path("new surface shader"), m_editor, m_editor.m_allocator); 461 | res.init(ShaderResourceEditorType::SURFACE); 462 | res.serialize(blob); 463 | } 464 | 465 | void openEditor(const Path& path) override { m_editor.open(path); } 466 | 467 | void listLoaded() override { 468 | auto& resources = m_editor.m_app.getAssetCompiler().lockResources(); 469 | for (const AssetCompiler::ResourceItem& res : resources) { 470 | if (res.type != FunctionPlugin::TYPE) continue; 471 | m_editor.addFunction(res.path); 472 | } 473 | m_editor.m_app.getAssetCompiler().unlockResources(); 474 | } 475 | 476 | ShaderEditor& m_editor; 477 | }; 478 | 479 | ShaderEditor(StudioApp& app) 480 | : m_allocator(app.getAllocator(), "shader editor") 481 | , m_app(app) 482 | , m_functions(m_allocator) 483 | , m_function_plugin(*this) 484 | , m_asset_plugin(*this) 485 | {} 486 | 487 | void registerDependencies(const ShaderEditorResource& res); 488 | 489 | void init() override {} 490 | const char* getName() const override { return "shader editor"; } 491 | bool showGizmo(WorldView&, ComponentUID) override { return false; } 492 | 493 | void addFunction(const Path& path) { 494 | FileSystem& fs = m_app.getEngine().getFileSystem(); 495 | OutputMemoryStream data(m_allocator); 496 | UniquePtr shd = UniquePtr::create(m_allocator, path, *this, m_allocator); 497 | if (!fs.getContentSync(path, data)) { 498 | logError("Failed to load ", path); 499 | return; 500 | } 501 | 502 | m_functions.eraseItems([&](const UniquePtr& f){ return f->m_path == path; }); 503 | InputMemoryStream blob(data); 504 | shd->deserialize(blob); 505 | shd->m_path = path; 506 | ASSERT(shd->getShaderType() == ShaderResourceEditorType::FUNCTION); 507 | m_functions.emplace(shd.move()); 508 | } 509 | 510 | void open(const Path& path); 511 | 512 | TagAllocator m_allocator; 513 | StudioApp& m_app; 514 | Array> m_functions; 515 | FunctionPlugin m_function_plugin; 516 | AssetPlugin m_asset_plugin; 517 | }; 518 | 519 | ResourceType ShaderEditor::FunctionPlugin::TYPE("shader_graph_function"); 520 | 521 | struct VertexOutput { 522 | ShaderEditorResource::ValueType type; 523 | StaticString<32> name; 524 | }; 525 | 526 | static ShaderEditorResource::ValueType toType(const gpu::Attribute& attr) { 527 | switch (attr.type) { 528 | case gpu::AttributeType::FLOAT: 529 | break; 530 | case gpu::AttributeType::I16: 531 | case gpu::AttributeType::I8: 532 | if (attr.flags & gpu::Attribute::AS_INT) { 533 | switch (attr.components_count) { 534 | case 1: return ShaderEditorResource::ValueType::INT; 535 | case 2: ASSERT(false); break; 536 | case 3: ASSERT(false); break; 537 | case 4: return ShaderEditorResource::ValueType::IVEC4; 538 | } 539 | ASSERT(false); 540 | return ShaderEditorResource::ValueType::NONE; 541 | } 542 | break; 543 | case gpu::AttributeType::U32: 544 | case gpu::AttributeType::U16: 545 | case gpu::AttributeType::U8: 546 | if (attr.flags & gpu::Attribute::AS_INT) { 547 | switch (attr.components_count) { 548 | case 1: ASSERT(false); break; 549 | case 2: ASSERT(false); break; 550 | case 3: ASSERT(false); break; 551 | case 4: ASSERT(false); break; 552 | } 553 | ASSERT(false); 554 | return ShaderEditorResource::ValueType::NONE; 555 | } 556 | break; 557 | } 558 | switch (attr.components_count) { 559 | case 1: return ShaderEditorResource::ValueType::FLOAT; 560 | case 2: return ShaderEditorResource::ValueType::VEC2; 561 | case 3: return ShaderEditorResource::ValueType::VEC3; 562 | case 4: return ShaderEditorResource::ValueType::VEC4; 563 | } 564 | ASSERT(false); 565 | return ShaderEditorResource::ValueType::NONE; 566 | } 567 | 568 | static constexpr const char* toString(ShaderEditorResource::ValueType type) { 569 | switch (type) { 570 | case ShaderEditorResource::ValueType::COUNT: 571 | case ShaderEditorResource::ValueType::NONE: 572 | return "error"; 573 | case ShaderEditorResource::ValueType::BOOL: return "bool"; 574 | case ShaderEditorResource::ValueType::INT: return "int"; 575 | case ShaderEditorResource::ValueType::FLOAT: return "float"; 576 | case ShaderEditorResource::ValueType::VEC2: return "vec2"; 577 | case ShaderEditorResource::ValueType::VEC3: return "vec3"; 578 | case ShaderEditorResource::ValueType::VEC4: return "vec4"; 579 | case ShaderEditorResource::ValueType::IVEC4: return "ivec4"; 580 | } 581 | ASSERT(false); 582 | return "Unknown type"; 583 | } 584 | 585 | static bool edit(const char* label, ShaderEditorResource::ValueType* type) { 586 | bool changed = false; 587 | if (ImGui::BeginCombo(label, toString(*type))) { 588 | if (ImGui::Selectable("bool")) { *type = ShaderEditorResource::ValueType::BOOL; changed = true; } 589 | if (ImGui::Selectable("int")) { *type = ShaderEditorResource::ValueType::INT; changed = true; } 590 | if (ImGui::Selectable("float")) { *type = ShaderEditorResource::ValueType::FLOAT; changed = true; } 591 | if (ImGui::Selectable("vec2")) { *type = ShaderEditorResource::ValueType::VEC2; changed = true; } 592 | if (ImGui::Selectable("vec3")) { *type = ShaderEditorResource::ValueType::VEC3; changed = true; } 593 | if (ImGui::Selectable("vec4")) { *type = ShaderEditorResource::ValueType::VEC4; changed = true; } 594 | ImGui::EndCombo(); 595 | } 596 | return changed; 597 | } 598 | 599 | struct Input { 600 | ShaderEditorResource::Node* node = nullptr; 601 | u16 output_idx; 602 | 603 | void printReference(OutputMemoryStream& blob) const { node->printReference(blob, output_idx); } 604 | operator bool() const { return node != nullptr; } 605 | }; 606 | 607 | static Input getInput(const ShaderEditorResource& resource, u16 node_id, u16 input_idx) { 608 | Input res; 609 | ShaderEditorResource::forEachInput(resource, node_id, [&](ShaderEditorResource::Node* from, u16 from_attr, u16 to_attr, u32 link_idx){ 610 | if (to_attr == input_idx) { 611 | res.output_idx = from_attr; 612 | res.node = from; 613 | } 614 | }); 615 | return res; 616 | } 617 | 618 | static bool isOutputConnected(const ShaderEditorResource& resource, u16 node_id, u16 input_idx) { 619 | for (const ShaderEditorResource::Link& link : resource.m_links) { 620 | if (link.getFromNode() == node_id) { 621 | const u16 from_attr = link.getFromPin(); 622 | if (from_attr == input_idx) return true; 623 | } 624 | } 625 | return false; 626 | } 627 | 628 | static bool isInputConnected(const ShaderEditorResource& resource, u16 node_id, u16 input_idx) { 629 | return getInput(resource, node_id, input_idx).node; 630 | } 631 | 632 | void ShaderEditorResource::Node::printReference(OutputMemoryStream& blob, int output_idx) const 633 | { 634 | blob << "v" << m_id; 635 | } 636 | 637 | ShaderEditorResource::Node::Node(ShaderEditorResource& resource) 638 | : m_resource(resource) 639 | , m_error(resource.m_allocator) 640 | { 641 | m_id = 0xffFF; 642 | } 643 | 644 | void ShaderEditorResource::Node::inputSlot() { 645 | ImGuiEx::Pin(m_id | (m_input_count << 16), true); 646 | ++m_input_count; 647 | } 648 | 649 | void ShaderEditorResource::Node::outputSlot() { 650 | ImGuiEx::Pin(m_id | (m_output_count << 16) | NodeEditor::OUTPUT_FLAG, false); 651 | ++m_output_count; 652 | } 653 | 654 | bool ShaderEditorResource::Node::generateOnce(OutputMemoryStream& blob) { 655 | if (m_generated) return true; 656 | m_generated = true; 657 | return generate(blob); 658 | } 659 | 660 | bool ShaderEditorResource::Node::nodeGUI() { 661 | ImGuiEx::BeginNode(m_id, m_pos, &m_selected); 662 | m_input_count = 0; 663 | m_output_count = 0; 664 | bool res = onGUI(); 665 | 666 | if (m_error.length() > 0) { 667 | ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(0xff, 0, 0, 0xff)); 668 | } 669 | ImGuiEx::EndNode(); 670 | if (m_error.length() > 0) { 671 | ImGui::PopStyleColor(); 672 | if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", m_error.c_str()); 673 | } 674 | 675 | ASSERT((m_input_count > 0) == hasInputPins()); 676 | ASSERT((m_output_count > 0) == hasOutputPins()); 677 | 678 | return res; 679 | } 680 | 681 | struct MixNode : ShaderEditorResource::Node { 682 | explicit MixNode(ShaderEditorResource& resource) 683 | : Node(resource) 684 | {} 685 | 686 | ShaderNodeType getType() const override { return ShaderNodeType::MIX; } 687 | 688 | bool hasInputPins() const override { return true; } 689 | bool hasOutputPins() const override { return true; } 690 | 691 | bool onGUI() override { 692 | ImGuiEx::NodeTitle("Mix"); 693 | 694 | ImGui::BeginGroup(); 695 | inputSlot(); ImGui::TextUnformatted("A"); 696 | inputSlot(); ImGui::TextUnformatted("B"); 697 | inputSlot(); ImGui::TextUnformatted("Weight"); 698 | ImGui::EndGroup(); 699 | 700 | ImGui::SameLine(); 701 | outputSlot(); 702 | return false; 703 | } 704 | 705 | bool generate(OutputMemoryStream& blob) override { 706 | const Input input0 = getInput(m_resource, m_id, 0); 707 | const Input input1 = getInput(m_resource, m_id, 1); 708 | const Input input2 = getInput(m_resource, m_id, 2); 709 | if (!input0 || !input1 || !input2) { 710 | return error("Missing input"); 711 | } 712 | input0.node->generateOnce(blob); 713 | input1.node->generateOnce(blob); 714 | input2.node->generateOnce(blob); 715 | 716 | blob << "\t\t" << toString(getOutputType(0)) << " v" << m_id << " = mix("; 717 | input0.printReference(blob); 718 | blob << ", "; 719 | input1.printReference(blob); 720 | blob << ", "; 721 | input2.printReference(blob); 722 | blob << ");\n"; 723 | return true; 724 | } 725 | }; 726 | 727 | struct CodeNode : ShaderEditorResource::Node { 728 | explicit CodeNode(ShaderEditorResource& resource, IAllocator& allocator) 729 | : Node(resource) 730 | , m_allocator(allocator) 731 | , m_inputs(allocator) 732 | , m_outputs(allocator) 733 | , m_code(allocator) 734 | {} 735 | 736 | ShaderNodeType getType() const override { return ShaderNodeType::CODE; } 737 | 738 | ShaderEditorResource::ValueType getOutputType(int index) const override { return m_outputs[index].type; } 739 | 740 | void serialize(OutputMemoryStream& blob) override { 741 | blob.writeString(m_code.c_str()); 742 | blob.write(m_inputs.size()); 743 | for (const Variable& var : m_inputs) { 744 | blob.write(var.type); 745 | blob.writeString(var.name.c_str()); 746 | } 747 | blob.write(m_outputs.size()); 748 | for (const Variable& var : m_outputs) { 749 | blob.write(var.type); 750 | blob.writeString(var.name.c_str()); 751 | } 752 | } 753 | 754 | void deserialize(InputMemoryStream& blob) override { 755 | m_code = blob.readString(); 756 | i32 size; 757 | blob.read(size); 758 | for (i32 i = 0; i < size; ++i) { 759 | Variable& var = m_inputs.emplace(m_allocator); 760 | blob.read(var.type); 761 | var.name = blob.readString(); 762 | } 763 | 764 | blob.read(size); 765 | for (i32 i = 0; i < size; ++i) { 766 | Variable& var = m_outputs.emplace(m_allocator); 767 | blob.read(var.type); 768 | var.name = blob.readString(); 769 | } 770 | } 771 | 772 | bool hasInputPins() const override { return !m_inputs.empty(); } 773 | bool hasOutputPins() const override { return !m_outputs.empty(); } 774 | 775 | void fixLinks(u32 deleted_idx, bool is_input) { 776 | const ShaderEditorResource::Link* to_del = nullptr; 777 | if (is_input) { 778 | for (ShaderEditorResource::Link& link : m_resource.m_links) { 779 | if (link.getToNode() == m_id) { 780 | const u16 to_attr = link.getToPin(); 781 | if (to_attr == deleted_idx) to_del = &link; 782 | else if (to_attr > deleted_idx) { 783 | link.to = m_id | (u32(to_attr - 1) << 16); 784 | } 785 | } 786 | } 787 | } 788 | else { 789 | for (ShaderEditorResource::Link& link : m_resource.m_links) { 790 | if (link.getFromNode() == m_id) { 791 | const u16 from_attr = link.getFromPin(); 792 | if (from_attr == deleted_idx) to_del = &link; 793 | else if (from_attr > deleted_idx) { 794 | link.from = m_id | (u32(from_attr - 1) << 16); 795 | } 796 | } 797 | } 798 | } 799 | if (to_del) m_resource.m_links.erase(u32(to_del - m_resource.m_links.begin())); 800 | } 801 | 802 | bool onGUI() override { 803 | bool changed = false; 804 | ImGuiEx::NodeTitle("Code"); 805 | 806 | ImGui::BeginGroup(); 807 | for (const Variable& input : m_inputs) { 808 | inputSlot(); 809 | ImGui::TextUnformatted(input.name.c_str()); 810 | } 811 | ImGui::EndGroup(); 812 | 813 | ImGui::SameLine(); 814 | ImGui::BeginGroup(); 815 | 816 | if (ImGui::Button(ICON_FA_PENCIL_ALT "Edit")) ImGui::OpenPopup("edit"); 817 | 818 | if (ImGuiEx::BeginResizablePopup("edit", ImVec2(300, 300))) { 819 | auto edit_vars = [&](const char* label, Array& vars, bool is_input){ 820 | if (ImGui::CollapsingHeader(label, ImGuiTreeNodeFlags_DefaultOpen)) { 821 | ImGui::PushID(&vars); 822 | 823 | if (ImGui::BeginTable("tab", 3)) { 824 | ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize); 825 | ImGui::TableSetupColumn("Type"); 826 | ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); 827 | ImGui::TableHeadersRow(); 828 | 829 | for (Variable& var : vars) { 830 | ImGui::PushID(&var); 831 | ImGui::TableNextColumn(); 832 | const u32 idx = u32(&var - vars.begin()); 833 | bool del = ImGui::Button(ICON_FA_TRASH); 834 | ImGui::TableNextColumn(); 835 | ImGui::SetNextItemWidth(-1); 836 | changed = edit("##type", &var.type) || changed; 837 | ImGui::TableNextColumn(); 838 | ImGui::SetNextItemWidth(-1); 839 | changed = inputString("##name", &var.name) || changed; 840 | ImGui::PopID(); 841 | if (del) { 842 | changed = true; 843 | fixLinks(idx, is_input); 844 | vars.erase(idx); 845 | break; 846 | } 847 | } 848 | 849 | ImGui::EndTable(); 850 | } 851 | 852 | if (ImGui::Button("Add")) { 853 | vars.emplace(m_allocator); 854 | changed = true; 855 | } 856 | ImGui::PopID(); 857 | } 858 | }; 859 | 860 | edit_vars("Inputs", m_inputs, true); 861 | edit_vars("Outputs", m_outputs, false); 862 | 863 | if (ImGui::CollapsingHeader("Code", ImGuiTreeNodeFlags_DefaultOpen)) { 864 | if (inputStringMultiline("##code", &m_code, ImVec2(-1, ImGui::GetContentRegionAvail().y))) { 865 | changed = true; 866 | } 867 | } 868 | ImGui::EndPopup(); 869 | } 870 | ImGui::EndGroup(); 871 | 872 | ImGui::SameLine(); 873 | ImGui::BeginGroup(); 874 | for (const Variable& output : m_outputs) { 875 | outputSlot(); 876 | ImGui::TextUnformatted(output.name.c_str()); 877 | } 878 | ImGui::EndGroup(); 879 | 880 | return changed; 881 | } 882 | 883 | bool generate(OutputMemoryStream& blob) override { 884 | for (const Variable& input_var : m_inputs) { 885 | const u32 idx = u32(&input_var - m_inputs.begin()); 886 | const Input input = getInput(m_resource, m_id, idx); 887 | if (!input) continue; 888 | 889 | input.node->generateOnce(blob); 890 | blob << toString(input_var.type) << " " << input_var.name.c_str() << " = "; 891 | input.printReference(blob); 892 | blob << ";\n"; 893 | } 894 | 895 | for (const Variable& var : m_outputs) { 896 | blob << toString(var.type) << " " << var.name.c_str() << ";"; 897 | } 898 | 899 | blob << m_code.c_str(); 900 | return true; 901 | } 902 | 903 | void printReference(OutputMemoryStream& blob, int output_idx) const override { 904 | blob << m_outputs[output_idx].name.c_str(); 905 | } 906 | 907 | struct Variable { 908 | Variable(IAllocator& allocator) : name(allocator) {} 909 | String name; 910 | ShaderEditorResource::ValueType type = ShaderEditorResource::ValueType::FLOAT; 911 | }; 912 | 913 | IAllocator& m_allocator; 914 | Array m_inputs; 915 | Array m_outputs; 916 | String m_code; 917 | }; 918 | 919 | static u32 getChannelsCount(ShaderEditorResource::ValueType type) { 920 | switch (type) { 921 | case ShaderEditorResource::ValueType::BOOL: 922 | case ShaderEditorResource::ValueType::INT: 923 | case ShaderEditorResource::ValueType::FLOAT: 924 | return 1; 925 | case ShaderEditorResource::ValueType::VEC2: 926 | return 2; 927 | case ShaderEditorResource::ValueType::VEC3: 928 | return 3; 929 | case ShaderEditorResource::ValueType::IVEC4: 930 | case ShaderEditorResource::ValueType::VEC4: 931 | return 4; 932 | default: 933 | ASSERT(false); 934 | return 0; 935 | } 936 | } 937 | 938 | static ShaderEditorResource::ValueType pickBiggerType(ShaderEditorResource::ValueType t0, ShaderEditorResource::ValueType t1) { 939 | if (getChannelsCount(t0) > getChannelsCount(t1)) return t0; 940 | return t1; 941 | } 942 | 943 | template 944 | struct OperatorNode : ShaderEditorResource::Node { 945 | explicit OperatorNode(ShaderEditorResource& resource) 946 | : Node(resource) 947 | {} 948 | 949 | ShaderNodeType getType() const override { return Type; } 950 | 951 | bool hasInputPins() const override { return true; } 952 | bool hasOutputPins() const override { return true; } 953 | 954 | void serialize(OutputMemoryStream& blob) override { blob.write(b_val); } 955 | void deserialize(InputMemoryStream& blob) override { blob.read(b_val); } 956 | 957 | ShaderEditorResource::ValueType getOutputType(int) const override { 958 | const Input input0 = getInput(m_resource, m_id, 0); 959 | const Input input1 = getInput(m_resource, m_id, 1); 960 | if (input0) { 961 | const ShaderEditorResource::ValueType type0 = input0.node->getOutputType(input0.output_idx); 962 | if (input1) { 963 | const ShaderEditorResource::ValueType type1 = input1.node->getOutputType(input1.output_idx); 964 | return pickBiggerType(type0, type1); 965 | } 966 | return type0; 967 | } 968 | return ShaderEditorResource::ValueType::FLOAT; 969 | } 970 | 971 | bool generate(OutputMemoryStream& blob) override { 972 | const Input input0 = getInput(m_resource, m_id, 0); 973 | const Input input1 = getInput(m_resource, m_id, 1); 974 | if (input0) input0.node->generateOnce(blob); 975 | if (input1) input1.node->generateOnce(blob); 976 | return true; 977 | } 978 | 979 | void printReference(OutputMemoryStream& blob, int attr_idx) const override 980 | { 981 | const Input input0 = getInput(m_resource, m_id, 0); 982 | const Input input1 = getInput(m_resource, m_id, 1); 983 | if (!input0) { 984 | blob << "0"; 985 | return; 986 | } 987 | 988 | blob << "("; 989 | input0.printReference(blob); 990 | switch(Type) { 991 | case ShaderNodeType::MULTIPLY: blob << " * "; break; 992 | case ShaderNodeType::ADD: blob << " + "; break; 993 | case ShaderNodeType::DIVIDE: blob << " / "; break; 994 | case ShaderNodeType::SUBTRACT: blob << " - "; break; 995 | default: ASSERT(false); blob << " * "; break; 996 | } 997 | if (input1) { 998 | input1.printReference(blob); 999 | } 1000 | else { 1001 | blob << b_val; 1002 | } 1003 | blob << ")"; 1004 | } 1005 | 1006 | static const char* getName() { 1007 | switch (Type) { 1008 | case ShaderNodeType::ADD: return "Add"; 1009 | case ShaderNodeType::SUBTRACT: return "Subtract"; 1010 | case ShaderNodeType::MULTIPLY: return "Multiply"; 1011 | case ShaderNodeType::DIVIDE: return "Divide"; 1012 | default: 1013 | ASSERT(false); 1014 | return "Error"; 1015 | } 1016 | } 1017 | 1018 | bool onGUI() override { 1019 | ImGuiEx::NodeTitle(getName()); 1020 | 1021 | outputSlot(); 1022 | inputSlot(); ImGui::Text("A"); 1023 | 1024 | inputSlot(); 1025 | if (isInputConnected(m_resource, m_id, 1)) { 1026 | ImGui::Text("B"); 1027 | } 1028 | else { 1029 | ImGui::DragFloat("B", &b_val); 1030 | } 1031 | 1032 | return false; 1033 | } 1034 | 1035 | float b_val = 2; 1036 | }; 1037 | 1038 | struct OneMinusNode : ShaderEditorResource::Node { 1039 | explicit OneMinusNode(ShaderEditorResource& resource) 1040 | : Node(resource) 1041 | {} 1042 | 1043 | ShaderNodeType getType() const override { return ShaderNodeType::ONEMINUS; } 1044 | 1045 | bool hasInputPins() const override { return true; } 1046 | bool hasOutputPins() const override { return true; } 1047 | 1048 | ShaderEditorResource::ValueType getOutputType(int index) const override { 1049 | const Input input = getInput(m_resource, m_id, 0); 1050 | if (!input) return ShaderEditorResource::ValueType::FLOAT; 1051 | return input.node->getOutputType(input.output_idx); 1052 | } 1053 | 1054 | bool generate(OutputMemoryStream& blob) override { 1055 | const Input input = getInput(m_resource, m_id, 0); 1056 | if (!input) return error("Missing input"); 1057 | input.node->generateOnce(blob); 1058 | return true; 1059 | } 1060 | 1061 | void printReference(OutputMemoryStream& blob, int output_idx) const override { 1062 | const Input input = getInput(m_resource, m_id, 0); 1063 | if (!input) return; 1064 | 1065 | switch(input.node->getOutputType(input.output_idx)) { 1066 | default : blob << "(1 - "; break; 1067 | case ShaderEditorResource::ValueType::VEC4: 1068 | blob << "(vec4(1) - "; 1069 | break; 1070 | case ShaderEditorResource::ValueType::IVEC4: 1071 | blob << "(ivec4(1) - "; 1072 | break; 1073 | case ShaderEditorResource::ValueType::VEC2: 1074 | blob << "(vec2(1) - "; 1075 | break; 1076 | case ShaderEditorResource::ValueType::VEC3: 1077 | blob << "(vec3(1) - "; 1078 | break; 1079 | } 1080 | 1081 | input.printReference(blob); 1082 | blob << ")"; 1083 | } 1084 | 1085 | bool onGUI() override { 1086 | inputSlot(); 1087 | ImGui::TextUnformatted("1 - X"); 1088 | ImGui::SameLine(); 1089 | outputSlot(); 1090 | return false; 1091 | } 1092 | }; 1093 | 1094 | struct SwizzleNode : ShaderEditorResource::Node { 1095 | explicit SwizzleNode(ShaderEditorResource& resource) 1096 | : Node(resource) 1097 | { 1098 | m_swizzle = "xyzw"; 1099 | } 1100 | 1101 | ShaderNodeType getType() const override { return ShaderNodeType::SWIZZLE; } 1102 | 1103 | bool hasInputPins() const override { return true; } 1104 | bool hasOutputPins() const override { return true; } 1105 | 1106 | void serialize(OutputMemoryStream& blob) override { blob.write(m_swizzle); } 1107 | void deserialize(InputMemoryStream& blob) override { blob.read(m_swizzle); } 1108 | ShaderEditorResource::ValueType getOutputType(int idx) const override { 1109 | // TODO other types, e.g. ivec4... 1110 | switch(stringLength(m_swizzle)) { 1111 | case 0: return ShaderEditorResource::ValueType::NONE; 1112 | case 1: return ShaderEditorResource::ValueType::FLOAT; 1113 | case 2: return ShaderEditorResource::ValueType::VEC2; 1114 | case 3: return ShaderEditorResource::ValueType::VEC3; 1115 | case 4: return ShaderEditorResource::ValueType::VEC4; 1116 | default: ASSERT(false); return ShaderEditorResource::ValueType::NONE; 1117 | } 1118 | } 1119 | 1120 | bool generate(OutputMemoryStream& blob) override { 1121 | const Input input = getInput(m_resource, m_id, 0); 1122 | if (!input) return error("Missing input"); 1123 | input.node->generateOnce(blob); 1124 | return true; 1125 | } 1126 | 1127 | void printReference(OutputMemoryStream& blob, int output_idx) const override { 1128 | const Input input = getInput(m_resource, m_id, 0); 1129 | if (!input) return; 1130 | 1131 | input.printReference(blob); 1132 | blob << "." << m_swizzle; 1133 | } 1134 | 1135 | bool onGUI() override { 1136 | inputSlot(); 1137 | ImGui::SetNextItemWidth(50); 1138 | bool res = ImGui::InputTextWithHint("", "swizzle", m_swizzle.data, sizeof(m_swizzle.data)); 1139 | 1140 | ImGui::SameLine(); 1141 | outputSlot(); 1142 | return res; 1143 | } 1144 | 1145 | StaticString<5> m_swizzle; 1146 | }; 1147 | 1148 | struct FresnelNode : ShaderEditorResource::Node { 1149 | explicit FresnelNode(ShaderEditorResource& resource) 1150 | : Node(resource) 1151 | {} 1152 | 1153 | ShaderNodeType getType() const override { return ShaderNodeType::FRESNEL; } 1154 | 1155 | bool hasInputPins() const override { return false; } 1156 | bool hasOutputPins() const override { return true; } 1157 | 1158 | void serialize(OutputMemoryStream&blob) override { 1159 | blob.write(F0); 1160 | blob.write(power); 1161 | } 1162 | 1163 | void deserialize(InputMemoryStream&blob) override { 1164 | blob.read(F0); 1165 | blob.read(power); 1166 | } 1167 | 1168 | bool onGUI() override { 1169 | ImGuiEx::NodeTitle("Fresnel"); 1170 | 1171 | outputSlot(); 1172 | ImGui::DragFloat("F0", &F0); 1173 | ImGui::DragFloat("Power", &power); 1174 | return false; 1175 | } 1176 | 1177 | bool generate(OutputMemoryStream& blob) override { 1178 | // TODO use data.normal instead of v_normal 1179 | blob << "float v" << m_id << " = mix(" << F0 << ", 1.0, pow(1 - saturate(dot(-normalize(v_wpos.xyz), v_normal)), " << power << "));\n"; 1180 | return true; 1181 | } 1182 | 1183 | float F0 = 0.04f; 1184 | float power = 5.0f; 1185 | }; 1186 | 1187 | struct FunctionInputNode : ShaderEditorResource::Node { 1188 | explicit FunctionInputNode(ShaderEditorResource& resource, IAllocator& allocator) 1189 | : Node(resource) 1190 | , m_name(allocator) 1191 | {} 1192 | 1193 | ShaderNodeType getType() const override { return ShaderNodeType::FUNCTION_INPUT; } 1194 | bool hasInputPins() const override { return false; } 1195 | bool hasOutputPins() const override { return true; } 1196 | 1197 | bool onGUI() override { 1198 | ImColor color(ImGui::GetStyle().Colors[ImGuiCol_Tab]); 1199 | ImGuiEx::BeginNodeTitleBar(color); 1200 | ImGui::Text("Input %s (%s)", m_name.c_str(), toString(m_type)); 1201 | ImGuiEx::EndNodeTitleBar(); 1202 | 1203 | outputSlot(); 1204 | bool res = inputString("Name", &m_name); 1205 | res = edit("Type", &m_type) || res; 1206 | return res; 1207 | } 1208 | 1209 | void serialize(OutputMemoryStream& blob) override { 1210 | blob.writeString(m_name.c_str()); 1211 | blob.write(m_type); 1212 | } 1213 | void deserialize(InputMemoryStream& blob) override { 1214 | m_name = blob.readString(); 1215 | blob.read(m_type); 1216 | } 1217 | 1218 | bool generate(OutputMemoryStream& blob) override { return true; } 1219 | 1220 | void printReference(OutputMemoryStream& blob, int attr_idx) const override { 1221 | blob << m_name.c_str(); 1222 | } 1223 | 1224 | ShaderEditorResource::ValueType getOutputType(i32) const override { return m_type; } 1225 | 1226 | String m_name; 1227 | ShaderEditorResource::ValueType m_type = ShaderEditorResource::ValueType::FLOAT; 1228 | }; 1229 | 1230 | struct FunctionOutputNode : ShaderEditorResource::Node { 1231 | explicit FunctionOutputNode(ShaderEditorResource& resource) 1232 | : Node(resource) 1233 | {} 1234 | 1235 | ShaderNodeType getType() const override { return ShaderNodeType::FUNCTION_OUTPUT; } 1236 | bool hasInputPins() const override { return true; } 1237 | bool hasOutputPins() const override { return false; } 1238 | 1239 | bool onGUI() override { 1240 | ImGuiEx::NodeTitle("Function output"); 1241 | inputSlot(); 1242 | ImGui::TextUnformatted(" "); 1243 | return false; 1244 | } 1245 | 1246 | void serialize(OutputMemoryStream& blob) override {} 1247 | void deserialize(InputMemoryStream& blob) override {} 1248 | 1249 | bool generate(OutputMemoryStream& blob) override { 1250 | const Input input = getInput(m_resource, m_id, 0); 1251 | if (!input) return error("Missing input"); 1252 | 1253 | const ShaderEditorResource::ValueType output_type = input.node->getOutputType(input.output_idx); 1254 | StringView name = Path::getBasename(m_resource.m_path.c_str()); 1255 | blob << toString(output_type) << " " << name << "("; 1256 | 1257 | bool first_arg = true; 1258 | for (const Node* node : m_resource.m_nodes) { 1259 | if (node->getType() != ShaderNodeType::FUNCTION_INPUT) continue; 1260 | 1261 | FunctionInputNode* n = (FunctionInputNode*)node; 1262 | if (!first_arg) blob << ", "; 1263 | blob << toString(n->m_type) << " " << n->m_name.c_str(); 1264 | first_arg = false; 1265 | } 1266 | 1267 | blob << ") {\n"; 1268 | if (input) { 1269 | input.node->generateOnce(blob); 1270 | blob << "\treturn "; 1271 | input.printReference(blob); 1272 | blob << ";\n"; 1273 | } 1274 | blob << "}"; 1275 | return true; 1276 | } 1277 | }; 1278 | 1279 | struct FunctionCallNode : ShaderEditorResource::Node { 1280 | explicit FunctionCallNode(ShaderEditorResource& resource) 1281 | : Node(resource) 1282 | {} 1283 | 1284 | ShaderNodeType getType() const override { return ShaderNodeType::FUNCTION_CALL; } 1285 | bool hasInputPins() const override { return true; } 1286 | bool hasOutputPins() const override { return true; } 1287 | 1288 | void serialize(OutputMemoryStream& blob) override { blob.writeString(m_function_resource->m_path.c_str()); } 1289 | void deserialize(InputMemoryStream& blob) override { 1290 | const char* path = blob.readString(); 1291 | for (const auto& f : m_resource.m_editor.m_functions) { 1292 | if (f->m_path == path) { 1293 | m_function_resource = f.get(); 1294 | break; 1295 | } 1296 | } 1297 | } 1298 | 1299 | ShaderEditorResource::ValueType getOutputType(int) const override { return m_function_resource->getFunctionOutputType(); } 1300 | 1301 | bool generate(OutputMemoryStream& blob) override { 1302 | StringView fn_name = Path::getBasename(m_function_resource->m_path.c_str()); 1303 | ShaderEditorResource::ValueType type = m_function_resource->getFunctionOutputType(); 1304 | blob << "\t" << toString(type) << " v" << m_id << " = " << fn_name << "("; 1305 | u32 input_count = 0; 1306 | for (const Node* n : m_function_resource->m_nodes) { 1307 | if (n->getType() == ShaderNodeType::FUNCTION_INPUT) ++input_count; 1308 | } 1309 | 1310 | for (u32 i = 0; i < input_count; ++i) { 1311 | const Input input = getInput(m_resource, m_id, i); 1312 | if (!input) { 1313 | return error("Input not connected"); 1314 | } 1315 | if (i > 0) blob << ", "; 1316 | input.printReference(blob); 1317 | } 1318 | blob << ");\n"; 1319 | return true; 1320 | } 1321 | 1322 | bool onGUI() override { 1323 | StringView basename = Path::getBasename(m_function_resource->m_path.c_str()); 1324 | StaticString name(basename); 1325 | ImGuiEx::NodeTitle(name); 1326 | outputSlot(); 1327 | for (const Node* node : m_function_resource->m_nodes) { 1328 | if (node->getType() != ShaderNodeType::FUNCTION_INPUT) continue; 1329 | 1330 | FunctionInputNode* n = (FunctionInputNode*)node; 1331 | inputSlot(); 1332 | ImGui::TextUnformatted(n->m_name.c_str()); 1333 | } 1334 | return false; 1335 | } 1336 | 1337 | ShaderEditorResource* m_function_resource; 1338 | }; 1339 | 1340 | template 1341 | struct BuiltinFunctionCallNode : ShaderEditorResource::Node 1342 | { 1343 | explicit BuiltinFunctionCallNode(ShaderEditorResource& resource) 1344 | : Node(resource) 1345 | {} 1346 | 1347 | ShaderNodeType getType() const override { return Type; } 1348 | bool hasInputPins() const override { return true; } 1349 | bool hasOutputPins() const override { return true; } 1350 | 1351 | void serialize(OutputMemoryStream& blob) override {} 1352 | void deserialize(InputMemoryStream& blob) override {} 1353 | 1354 | ShaderEditorResource::ValueType getOutputType(int) const override { 1355 | if constexpr (Type == ShaderNodeType::LENGTH) return ShaderEditorResource::ValueType::FLOAT; 1356 | const Input input0 = getInput(m_resource, m_id, 0); 1357 | if (input0) return input0.node->getOutputType(input0.output_idx); 1358 | return ShaderEditorResource::ValueType::FLOAT; 1359 | } 1360 | 1361 | static const char* getName() { 1362 | switch (Type) { 1363 | case ShaderNodeType::ABS: return "abs"; 1364 | case ShaderNodeType::ALL: return "all"; 1365 | case ShaderNodeType::ANY: return "any"; 1366 | case ShaderNodeType::CEIL: return "ceil"; 1367 | case ShaderNodeType::COS: return "cos"; 1368 | case ShaderNodeType::EXP: return "exp"; 1369 | case ShaderNodeType::EXP2: return "exp2"; 1370 | case ShaderNodeType::FLOOR: return "floor"; 1371 | case ShaderNodeType::FRACT: return "fract"; 1372 | case ShaderNodeType::LENGTH: return "length"; 1373 | case ShaderNodeType::LOG: return "log"; 1374 | case ShaderNodeType::LOG2: return "log2"; 1375 | case ShaderNodeType::NORMALIZE: return "normalize"; 1376 | case ShaderNodeType::NOT: return "not"; 1377 | case ShaderNodeType::ROUND: return "round"; 1378 | case ShaderNodeType::SATURATE: return "saturate"; 1379 | case ShaderNodeType::SIN: return "sin"; 1380 | case ShaderNodeType::SQRT: return "sqrt"; 1381 | case ShaderNodeType::TAN: return "tan"; 1382 | case ShaderNodeType::TRANSPOSE: return "transpose"; 1383 | case ShaderNodeType::TRUNC: return "trunc"; 1384 | default: ASSERT(false); return "error"; 1385 | } 1386 | } 1387 | 1388 | bool generate(OutputMemoryStream& blob) override { 1389 | const Input input0 = getInput(m_resource, m_id, 0); 1390 | 1391 | if (input0) input0.node->generateOnce(blob); 1392 | 1393 | blob << "\t\t" << toString(getOutputType(0)) << " v" << m_id << " = " << getName() << "("; 1394 | if (input0) { 1395 | input0.printReference(blob); 1396 | } 1397 | else { 1398 | blob << "0"; 1399 | } 1400 | blob << ");\n"; 1401 | return true; 1402 | } 1403 | 1404 | bool onGUI() override { 1405 | inputSlot(); 1406 | ImGui::TextUnformatted(getName()); 1407 | ImGui::SameLine(); 1408 | outputSlot(); 1409 | return false; 1410 | } 1411 | }; 1412 | 1413 | static void makeSafeCast(OutputMemoryStream& blob, ShaderEditorResource::ValueType t0, ShaderEditorResource::ValueType t1) { 1414 | const u32 c0 = getChannelsCount(t0); 1415 | const u32 c1 = getChannelsCount(t1); 1416 | if (c0 == c1) return; 1417 | 1418 | if (c1 == 1) { 1419 | blob << "."; 1420 | for (u32 i = 0; i < c0; ++i) blob << "x"; 1421 | } 1422 | else if (c0 == 1){ 1423 | blob << ".x"; 1424 | } 1425 | } 1426 | 1427 | struct PowerNode : ShaderEditorResource::Node { 1428 | explicit PowerNode(ShaderEditorResource& resource) 1429 | : Node(resource) 1430 | {} 1431 | 1432 | ShaderNodeType getType() const override { return ShaderNodeType::POW; } 1433 | 1434 | bool hasInputPins() const override { return true; } 1435 | bool hasOutputPins() const override { return true; } 1436 | 1437 | void serialize(OutputMemoryStream& blob) override { 1438 | blob.write(m_exponent); 1439 | } 1440 | 1441 | void deserialize(InputMemoryStream& blob) override { 1442 | blob.read(m_exponent); 1443 | } 1444 | 1445 | ShaderEditorResource::ValueType getOutputType(int) const override { 1446 | const Input input0 = getInput(m_resource, m_id, 0); 1447 | if (input0) return input0.node->getOutputType(input0.output_idx); 1448 | return ShaderEditorResource::ValueType::FLOAT; 1449 | } 1450 | 1451 | bool generate(OutputMemoryStream& blob) override { 1452 | const Input input0 = getInput(m_resource, m_id, 0); 1453 | if (!input0) return error("Missing input"); 1454 | input0.node->generateOnce(blob); 1455 | 1456 | const Input input1 = getInput(m_resource, m_id, 1); 1457 | if (input1) input1.node->generateOnce(blob); 1458 | 1459 | const char* type_str = toString(getOutputType(0)); 1460 | blob << "\t\t" << type_str << " v" << m_id << " = pow("; 1461 | input0.printReference(blob); 1462 | blob << ", "; 1463 | if (input1) { 1464 | input1.printReference(blob); 1465 | makeSafeCast(blob 1466 | , input0.node->getOutputType(input0.output_idx) 1467 | , input1.node->getOutputType(input1.output_idx)); 1468 | } 1469 | else { 1470 | blob << type_str << "(" << m_exponent << ")"; 1471 | } 1472 | blob << ");\n"; 1473 | return true; 1474 | } 1475 | 1476 | bool onGUI() override { 1477 | ImGuiEx::NodeTitle("Power"); 1478 | ImGui::BeginGroup(); 1479 | inputSlot(); ImGui::Text("Base"); 1480 | inputSlot(); 1481 | if (getInput(m_resource, m_id, 1)) { 1482 | ImGui::Text("Exponent"); 1483 | } 1484 | else { 1485 | ImGui::DragFloat("Exponent", &m_exponent); 1486 | } 1487 | 1488 | ImGui::EndGroup(); 1489 | 1490 | ImGui::SameLine(); 1491 | outputSlot(); 1492 | return false; 1493 | } 1494 | 1495 | float m_exponent = 2.f; 1496 | }; 1497 | 1498 | template 1499 | struct BinaryBuiltinFunctionCallNode : ShaderEditorResource::Node 1500 | { 1501 | explicit BinaryBuiltinFunctionCallNode(ShaderEditorResource& resource) 1502 | : Node(resource) 1503 | { 1504 | } 1505 | 1506 | ShaderNodeType getType() const override { return Type; } 1507 | bool hasInputPins() const override { return true; } 1508 | bool hasOutputPins() const override { return true; } 1509 | 1510 | void serialize(OutputMemoryStream& blob) override {} 1511 | void deserialize(InputMemoryStream& blob) override {} 1512 | 1513 | ShaderEditorResource::ValueType getOutputType(int) const override { 1514 | switch (Type) { 1515 | case ShaderNodeType::DISTANCE: 1516 | case ShaderNodeType::DOT: return ShaderEditorResource::ValueType::FLOAT; 1517 | default: break; 1518 | } 1519 | const Input input0 = getInput(m_resource, m_id, 0); 1520 | if (input0) return input0.node->getOutputType(input0.output_idx); 1521 | return ShaderEditorResource::ValueType::FLOAT; 1522 | } 1523 | 1524 | static const char* getName() { 1525 | switch (Type) { 1526 | case ShaderNodeType::DOT: return "dot"; 1527 | case ShaderNodeType::CROSS: return "cross"; 1528 | case ShaderNodeType::MIN: return "min"; 1529 | case ShaderNodeType::MAX: return "max"; 1530 | case ShaderNodeType::DISTANCE: return "distance"; 1531 | default: ASSERT(false); return "error"; 1532 | } 1533 | } 1534 | 1535 | bool generate(OutputMemoryStream& blob) override { 1536 | const Input input0 = getInput(m_resource, m_id, 0); 1537 | const Input input1 = getInput(m_resource, m_id, 1); 1538 | if (input0) input0.node->generateOnce(blob); 1539 | if (input1) input1.node->generateOnce(blob); 1540 | 1541 | blob << "\t\t" << toString(getOutputType(0)) << " v" << m_id << " = " << getName() << "("; 1542 | if (input0) { 1543 | input0.printReference(blob); 1544 | } 1545 | else { 1546 | blob << "1"; 1547 | } 1548 | blob << ", "; 1549 | if (input1) { 1550 | input1.printReference(blob); 1551 | if (input0) { 1552 | makeSafeCast(blob 1553 | , input0.node->getOutputType(input0.output_idx) 1554 | , input1.node->getOutputType(input1.output_idx)); 1555 | } 1556 | } 1557 | else { 1558 | blob << "1"; 1559 | } 1560 | blob << ");\n"; 1561 | return true; 1562 | } 1563 | 1564 | bool onGUI() override { 1565 | ImGuiEx::NodeTitle(getName()); 1566 | ImGui::BeginGroup(); 1567 | inputSlot(); ImGui::Text("A"); 1568 | inputSlot(); ImGui::Text("B"); 1569 | ImGui::EndGroup(); 1570 | 1571 | ImGui::SameLine(); 1572 | outputSlot(); 1573 | return false; 1574 | } 1575 | }; 1576 | 1577 | struct PositionNode : ShaderEditorResource::Node { 1578 | explicit PositionNode(ShaderEditorResource& resource) 1579 | : Node(resource) 1580 | {} 1581 | 1582 | ShaderNodeType getType() const override { return ShaderNodeType::POSITION; } 1583 | 1584 | bool hasInputPins() const override { return false; } 1585 | bool hasOutputPins() const override { return true; } 1586 | 1587 | void serialize(OutputMemoryStream& blob) override { blob.write(m_space); } 1588 | void deserialize(InputMemoryStream& blob) override { blob.read(m_space); } 1589 | 1590 | ShaderEditorResource::ValueType getOutputType(int) const override { return ShaderEditorResource::ValueType::VEC3; } 1591 | 1592 | void printReference(OutputMemoryStream& blob, int output_idx) const override { 1593 | switch (m_space) { 1594 | case CAMERA: blob << "v_wpos"; break; 1595 | case LOCAL: blob << "v_local_position"; break; 1596 | } 1597 | } 1598 | 1599 | bool onGUI() override { 1600 | ImGuiEx::NodeTitle("Position"); 1601 | outputSlot(); 1602 | return ImGui::Combo("Space", (i32*)&m_space, "Camera\0Local\0"); 1603 | } 1604 | 1605 | enum Space : u32 { 1606 | CAMERA, 1607 | LOCAL 1608 | }; 1609 | 1610 | Space m_space = CAMERA; 1611 | }; 1612 | 1613 | template 1614 | struct VaryingNode : ShaderEditorResource::Node { 1615 | explicit VaryingNode(ShaderEditorResource& resource) 1616 | : Node(resource) 1617 | {} 1618 | 1619 | ShaderNodeType getType() const override { return Type; } 1620 | bool hasInputPins() const override { return false; } 1621 | bool hasOutputPins() const override { return true; } 1622 | 1623 | void serialize(OutputMemoryStream&) override {} 1624 | void deserialize(InputMemoryStream&) override {} 1625 | 1626 | ShaderEditorResource::ValueType getOutputType(int) const override { 1627 | switch(Type) { 1628 | case ShaderNodeType::NORMAL: return ShaderEditorResource::ValueType::VEC3; 1629 | case ShaderNodeType::UV0: return ShaderEditorResource::ValueType::VEC2; 1630 | default: ASSERT(false); return ShaderEditorResource::ValueType::VEC3; 1631 | } 1632 | } 1633 | 1634 | void printReference(OutputMemoryStream& blob, int output_idx) const override { 1635 | switch(Type) { 1636 | case ShaderNodeType::NORMAL: blob << "v_normal"; break; 1637 | case ShaderNodeType::UV0: blob << "v_uv"; break; 1638 | default: ASSERT(false); break; 1639 | } 1640 | 1641 | } 1642 | 1643 | bool onGUI() override { 1644 | outputSlot(); 1645 | switch(Type) { 1646 | case ShaderNodeType::NORMAL: ImGui::Text("Normal"); break; 1647 | case ShaderNodeType::UV0: ImGui::Text("UV0"); break; 1648 | default: ASSERT(false); break; 1649 | } 1650 | return false; 1651 | } 1652 | }; 1653 | 1654 | template 1655 | struct ConstNode : ShaderEditorResource::Node 1656 | { 1657 | explicit ConstNode(ShaderEditorResource& resource) 1658 | : Node(resource) 1659 | { 1660 | m_value[0] = m_value[1] = m_value[2] = m_value[3] = 0; 1661 | m_int_value = 0; 1662 | } 1663 | 1664 | ShaderNodeType getType() const override { 1665 | switch(TYPE) { 1666 | case ShaderEditorResource::ValueType::VEC4: return ShaderNodeType::VEC4; 1667 | case ShaderEditorResource::ValueType::VEC3: return ShaderNodeType::VEC3; 1668 | case ShaderEditorResource::ValueType::VEC2: return ShaderNodeType::VEC2; 1669 | case ShaderEditorResource::ValueType::FLOAT: return ShaderNodeType::NUMBER; 1670 | default: ASSERT(false); return ShaderNodeType::NUMBER; 1671 | } 1672 | } 1673 | 1674 | void serialize(OutputMemoryStream& blob) override 1675 | { 1676 | blob.write(m_value); 1677 | blob.write(m_int_value); 1678 | } 1679 | 1680 | void deserialize(InputMemoryStream& blob) override 1681 | { 1682 | blob.read(m_value); 1683 | blob.read(m_int_value); 1684 | } 1685 | 1686 | ShaderEditorResource::ValueType getOutputType(int) const override { return TYPE; } 1687 | 1688 | void printInputValue(u32 idx, OutputMemoryStream& blob) const { 1689 | const Input input = getInput(m_resource, m_id, idx); 1690 | if (input) { 1691 | input.printReference(blob); 1692 | return; 1693 | } 1694 | blob << m_value[idx]; 1695 | } 1696 | 1697 | bool generate(OutputMemoryStream& blob) override { 1698 | for (u32 i = 0; i < 4; ++i) { 1699 | const Input input = getInput(m_resource, m_id, i); 1700 | if (input) input.node->generateOnce(blob); 1701 | } 1702 | return true; 1703 | } 1704 | 1705 | void printReference(OutputMemoryStream& blob, int output_idx) const override { 1706 | switch(TYPE) { 1707 | case ShaderEditorResource::ValueType::VEC4: 1708 | blob << "vec4("; 1709 | printInputValue(0, blob); 1710 | blob << ", "; 1711 | printInputValue(1, blob); 1712 | blob << ", "; 1713 | printInputValue(2, blob); 1714 | blob << ", "; 1715 | printInputValue(3, blob); 1716 | blob << ")"; 1717 | break; 1718 | case ShaderEditorResource::ValueType::VEC3: 1719 | blob << "vec3("; 1720 | printInputValue(0, blob); 1721 | blob << ", "; 1722 | printInputValue(1, blob); 1723 | blob << ", "; 1724 | printInputValue(2, blob); 1725 | blob << ")"; 1726 | break; 1727 | case ShaderEditorResource::ValueType::VEC2: 1728 | blob << "vec2("; 1729 | printInputValue(0, blob); 1730 | blob << ", "; 1731 | printInputValue(1, blob); 1732 | blob << ")"; 1733 | break; 1734 | case ShaderEditorResource::ValueType::INT: 1735 | blob << m_int_value; 1736 | break; 1737 | case ShaderEditorResource::ValueType::FLOAT: 1738 | blob << m_value[0]; 1739 | break; 1740 | default: ASSERT(false); break; 1741 | } 1742 | } 1743 | 1744 | bool onGUI() override { 1745 | bool res = false; 1746 | 1747 | const char* labels[] = { "X", "Y", "Z", "W" }; 1748 | 1749 | ImGui::BeginGroup(); 1750 | u32 channels_count = 0; 1751 | switch (TYPE) { 1752 | case ShaderEditorResource::ValueType::VEC4: channels_count = 4; break; 1753 | case ShaderEditorResource::ValueType::VEC3: channels_count = 3; break; 1754 | case ShaderEditorResource::ValueType::VEC2: channels_count = 2; break; 1755 | default: channels_count = 1; break; 1756 | } 1757 | 1758 | switch(TYPE) { 1759 | case ShaderEditorResource::ValueType::VEC4: 1760 | case ShaderEditorResource::ValueType::VEC3: 1761 | case ShaderEditorResource::ValueType::VEC2: 1762 | for (u16 i = 0; i < channels_count; ++i) { 1763 | inputSlot(); 1764 | if (isInputConnected(m_resource, m_id, i)) { 1765 | ImGui::TextUnformatted(labels[i]); 1766 | } 1767 | else { 1768 | res = ImGui::DragFloat(labels[i], &m_value[i]); 1769 | } 1770 | } 1771 | switch (TYPE) { 1772 | case ShaderEditorResource::ValueType::VEC4: 1773 | res = ImGui::ColorEdit4("##col", m_value, ImGuiColorEditFlags_NoInputs) || res; 1774 | break; 1775 | case ShaderEditorResource::ValueType::VEC3: 1776 | res = ImGui::ColorEdit3("##col", m_value, ImGuiColorEditFlags_NoInputs) || res; 1777 | break; 1778 | default: break; 1779 | } 1780 | break; 1781 | case ShaderEditorResource::ValueType::FLOAT: 1782 | ImGui::SetNextItemWidth(60); 1783 | res = ImGui::DragFloat("##val", m_value) || res; 1784 | break; 1785 | case ShaderEditorResource::ValueType::INT: 1786 | ImGui::SetNextItemWidth(60); 1787 | res = ImGui::InputInt("##val", &m_int_value) || res; 1788 | break; 1789 | default: ASSERT(false); break; 1790 | } 1791 | ImGui::EndGroup(); 1792 | 1793 | ImGui::SameLine(); 1794 | outputSlot(); 1795 | 1796 | return res; 1797 | } 1798 | 1799 | bool hasInputPins() const override { 1800 | switch (TYPE) { 1801 | case ShaderEditorResource::ValueType::VEC4: 1802 | case ShaderEditorResource::ValueType::VEC3: 1803 | case ShaderEditorResource::ValueType::VEC2: 1804 | return true; 1805 | default: return false; 1806 | } 1807 | } 1808 | 1809 | bool hasOutputPins() const override { return true; } 1810 | 1811 | float m_value[4]; 1812 | int m_int_value; 1813 | }; 1814 | 1815 | struct SampleNode : ShaderEditorResource::Node 1816 | { 1817 | explicit SampleNode(ShaderEditorResource& resource, IAllocator& allocator) 1818 | : Node(resource) 1819 | , m_texture(allocator) 1820 | {} 1821 | 1822 | ShaderNodeType getType() const override { return ShaderNodeType::SAMPLE; } 1823 | 1824 | 1825 | bool hasInputPins() const override { return true; } 1826 | bool hasOutputPins() const override { return true; } 1827 | 1828 | void serialize(OutputMemoryStream& blob) override { blob.writeString(m_texture.c_str()); } 1829 | void deserialize(InputMemoryStream& blob) override { m_texture = blob.readString(); } 1830 | ShaderEditorResource::ValueType getOutputType(int) const override { return ShaderEditorResource::ValueType::VEC4; } 1831 | 1832 | bool generate(OutputMemoryStream& blob) override { 1833 | const Input input0 = getInput(m_resource, m_id, 0); 1834 | if (input0) input0.node->generateOnce(blob); 1835 | blob << "\t\tvec4 v" << m_id << " = "; 1836 | char var_name[64]; 1837 | Shader::toTextureVarName(Span(var_name), m_texture.c_str()); 1838 | blob << "texture(" << var_name << ", "; 1839 | if (input0) input0.printReference(blob); 1840 | else blob << "v_uv"; 1841 | blob << ");\n"; 1842 | return true; 1843 | } 1844 | 1845 | bool onGUI() override { 1846 | inputSlot(); 1847 | ImGui::Text("UV"); 1848 | 1849 | ImGui::SameLine(); 1850 | outputSlot(); 1851 | return inputString("Texture", &m_texture); 1852 | } 1853 | 1854 | String m_texture; 1855 | }; 1856 | 1857 | struct AppendNode : ShaderEditorResource::Node { 1858 | explicit AppendNode(ShaderEditorResource& resource) 1859 | : Node(resource) 1860 | {} 1861 | 1862 | ShaderNodeType getType() const override { return ShaderNodeType::APPEND; } 1863 | 1864 | bool hasInputPins() const override { return true; } 1865 | bool hasOutputPins() const override { return true; } 1866 | 1867 | bool onGUI() override { 1868 | ImGuiEx::NodeTitle("Append"); 1869 | 1870 | ImGui::BeginGroup(); 1871 | inputSlot(); 1872 | ImGui::TextUnformatted("A"); 1873 | inputSlot(); 1874 | ImGui::TextUnformatted("B"); 1875 | ImGui::EndGroup(); 1876 | 1877 | ImGui::SameLine(); 1878 | outputSlot(); 1879 | return false; 1880 | } 1881 | 1882 | static u32 getChannelCount(ShaderEditorResource::ValueType type) { 1883 | switch (type) { 1884 | case ShaderEditorResource::ValueType::FLOAT: 1885 | case ShaderEditorResource::ValueType::BOOL: 1886 | case ShaderEditorResource::ValueType::INT: 1887 | return 1; 1888 | case ShaderEditorResource::ValueType::VEC2: 1889 | return 2; 1890 | case ShaderEditorResource::ValueType::VEC3: 1891 | return 3; 1892 | case ShaderEditorResource::ValueType::IVEC4: 1893 | case ShaderEditorResource::ValueType::VEC4: 1894 | return 4; 1895 | default: 1896 | // TODO handle mat3 & co. 1897 | ASSERT(false); 1898 | return 1; 1899 | } 1900 | } 1901 | 1902 | ShaderEditorResource::ValueType getOutputType(int index) const override { 1903 | const Input input0 = getInput(m_resource, m_id, 0); 1904 | const Input input1 = getInput(m_resource, m_id, 1); 1905 | u32 count = 0; 1906 | if (input0) count += getChannelCount(input0.node->getOutputType(input0.output_idx)); 1907 | if (input1) count += getChannelCount(input1.node->getOutputType(input1.output_idx)); 1908 | // TODO other types likes ivec4 1909 | switch (count) { 1910 | case 1: return ShaderEditorResource::ValueType::FLOAT; 1911 | case 2: return ShaderEditorResource::ValueType::VEC2; 1912 | case 3: return ShaderEditorResource::ValueType::VEC3; 1913 | case 4: return ShaderEditorResource::ValueType::VEC4; 1914 | default: ASSERT(false); return ShaderEditorResource::ValueType::FLOAT; 1915 | } 1916 | } 1917 | 1918 | bool generate(OutputMemoryStream& blob) override { 1919 | const Input input0 = getInput(m_resource, m_id, 0); 1920 | if (input0) input0.node->generateOnce(blob); 1921 | const Input input1 = getInput(m_resource, m_id, 1); 1922 | if (input1) input1.node->generateOnce(blob); 1923 | return true; 1924 | } 1925 | 1926 | void printReference(OutputMemoryStream& blob, int output_idx) const override { 1927 | const Input input0 = getInput(m_resource, m_id, 0); 1928 | const Input input1 = getInput(m_resource, m_id, 1); 1929 | if (!input0 && !input1) blob << "0"; 1930 | blob << toString(getOutputType(0)) << "("; 1931 | if (input0) { 1932 | input0.printReference(blob); 1933 | if (input1) blob << ", "; 1934 | } 1935 | if (input1) { 1936 | input1.printReference(blob); 1937 | } 1938 | blob << ")"; 1939 | } 1940 | }; 1941 | 1942 | struct StaticSwitchNode : ShaderEditorResource::Node { 1943 | explicit StaticSwitchNode(ShaderEditorResource& resource, IAllocator& allocator) 1944 | : Node(resource) 1945 | , m_define(allocator) 1946 | {} 1947 | 1948 | ShaderNodeType getType() const override { return ShaderNodeType::STATIC_SWITCH; } 1949 | bool hasInputPins() const override { return true; } 1950 | bool hasOutputPins() const override { return true; } 1951 | 1952 | bool onGUI() override { 1953 | ImGuiEx::NodeTitle("Static switch"); 1954 | 1955 | ImGui::BeginGroup(); 1956 | inputSlot(); 1957 | ImGui::TextUnformatted("True"); 1958 | inputSlot(); 1959 | ImGui::TextUnformatted("False"); 1960 | ImGui::EndGroup(); 1961 | 1962 | ImGui::SameLine(); 1963 | outputSlot(); 1964 | ImGui::SetNextItemWidth(80); 1965 | return inputString("##param", &m_define); 1966 | } 1967 | 1968 | void serialize(OutputMemoryStream& blob) override { blob.writeString(m_define.c_str()); } 1969 | void deserialize(InputMemoryStream& blob) override { m_define = blob.readString(); } 1970 | 1971 | const char* getOutputTypeName() const { 1972 | const Input input = getInput(m_resource, m_id, 0); 1973 | if (!input) return "float"; 1974 | return toString(input.node->getOutputType(input.output_idx)); 1975 | } 1976 | 1977 | bool generate(OutputMemoryStream& blob) override { 1978 | blob << "#ifdef " << m_define.c_str() << "\n"; 1979 | const Input input0 = getInput(m_resource, m_id, 0); 1980 | if (input0) { 1981 | input0.node->generateOnce(blob); 1982 | blob << getOutputTypeName() << " v" << m_id << " = "; 1983 | input0.printReference(blob); 1984 | blob << ";\n"; 1985 | } 1986 | blob << "#else\n"; 1987 | const Input input1 = getInput(m_resource, m_id, 1); 1988 | if (input1) { 1989 | input1.node->generateOnce(blob); 1990 | blob << getOutputTypeName() << " v" << m_id << " = "; \ 1991 | input1.printReference(blob); 1992 | blob << ";\n"; 1993 | } 1994 | blob << "#endif\n"; 1995 | return true; 1996 | } 1997 | 1998 | ShaderEditorResource::ValueType getOutputType(int) const override { 1999 | const Input input = getInput(m_resource, m_id, 0); 2000 | if (input) return input.node->getOutputType(input.output_idx); 2001 | return ShaderEditorResource::ValueType::FLOAT; 2002 | } 2003 | 2004 | String m_define; 2005 | }; 2006 | 2007 | template 2008 | struct ParameterNode : ShaderEditorResource::Node { 2009 | explicit ParameterNode(ShaderEditorResource& resource, IAllocator& allocator) 2010 | : Node(resource) 2011 | , m_name(allocator) 2012 | {} 2013 | 2014 | ShaderNodeType getType() const override { return Type; } 2015 | bool hasInputPins() const override { return false; } 2016 | bool hasOutputPins() const override { return true; } 2017 | 2018 | void serialize(OutputMemoryStream& blob) override { blob.writeString(m_name.c_str()); } 2019 | void deserialize(InputMemoryStream& blob) override { m_name = blob.readString(); } 2020 | 2021 | bool onGUI() override { 2022 | const ImU32 color = ImGui::GetColorU32(ImGuiCol_PlotLinesHovered); 2023 | switch(Type) { 2024 | case ShaderNodeType::SCALAR_PARAM: ImGuiEx::NodeTitle("Scalar param", color); break; 2025 | case ShaderNodeType::VEC4_PARAM: ImGuiEx::NodeTitle("Vec4 param", color); break; 2026 | case ShaderNodeType::COLOR_PARAM: ImGuiEx::NodeTitle("Color param", color); break; 2027 | default: ASSERT(false); ImGuiEx::NodeTitle("Error"); break; 2028 | } 2029 | 2030 | outputSlot(); 2031 | return inputString("##name", &m_name); 2032 | } 2033 | 2034 | bool generate(OutputMemoryStream& blob) override { 2035 | switch(Type) { 2036 | case ShaderNodeType::SCALAR_PARAM: blob << "\tfloat v"; break; 2037 | case ShaderNodeType::VEC4_PARAM: blob << "\tvec4 v"; break; 2038 | case ShaderNodeType::COLOR_PARAM: blob << "\tvec4 v"; break; 2039 | default: ASSERT(false); blob << "\tfloat v"; break; 2040 | } 2041 | char var_name[64]; 2042 | Shader::toUniformVarName(Span(var_name), m_name.c_str()); 2043 | 2044 | blob << m_id << " = " << var_name << ";"; 2045 | return true; 2046 | } 2047 | 2048 | String m_name; 2049 | }; 2050 | 2051 | struct PinNode : ShaderEditorResource::Node { 2052 | explicit PinNode(ShaderEditorResource& resource) 2053 | : Node(resource) 2054 | {} 2055 | 2056 | ShaderNodeType getType() const override { return ShaderNodeType::PIN; } 2057 | bool hasInputPins() const override { return true; } 2058 | bool hasOutputPins() const override { return true; } 2059 | 2060 | bool generate(OutputMemoryStream& blob) override { 2061 | const Input input = getInput(m_resource, m_id, 0); 2062 | if (!input) return error("Missing input"); 2063 | input.node->generateOnce(blob); 2064 | return true; 2065 | } 2066 | 2067 | void printReference(OutputMemoryStream& blob, int output_idx) const override { 2068 | const Input input = getInput(m_resource, m_id, 0); 2069 | if (input) input.printReference(blob); 2070 | } 2071 | 2072 | bool onGUI() override { 2073 | inputSlot(); 2074 | ImGui::TextUnformatted(" "); 2075 | ImGui::SameLine(); 2076 | outputSlot(); 2077 | return false; 2078 | } 2079 | }; 2080 | 2081 | struct PBRNode : ShaderEditorResource::Node 2082 | { 2083 | explicit PBRNode(ShaderEditorResource& resource) 2084 | : Node(resource) 2085 | , m_vertex_decl(gpu::PrimitiveType::TRIANGLE_STRIP) 2086 | , m_attributes_names(resource.m_allocator) 2087 | {} 2088 | 2089 | ShaderNodeType getType() const override { return ShaderNodeType::PBR; } 2090 | 2091 | bool hasInputPins() const override { return true; } 2092 | bool hasOutputPins() const override { return false; } 2093 | 2094 | void serialize(OutputMemoryStream& blob) override { 2095 | blob.write(m_type); 2096 | blob.write(m_vertex_decl); 2097 | blob.write(m_is_masked); 2098 | blob.write(m_attributes_names.size()); 2099 | for (const String& a : m_attributes_names) { 2100 | blob.writeString(a.c_str()); 2101 | } 2102 | } 2103 | 2104 | void deserialize(InputMemoryStream& blob) override { 2105 | blob.read(m_type); 2106 | blob.read(m_vertex_decl); 2107 | blob.read(m_is_masked); 2108 | u32 c; 2109 | blob.read(c); 2110 | m_attributes_names.reserve(c); 2111 | for (u32 i = 0; i < c; ++i) { 2112 | m_attributes_names.emplace(blob.readString(), m_resource.m_allocator); 2113 | } 2114 | } 2115 | 2116 | static const char* typeToString(const gpu::Attribute& attr) { 2117 | switch (attr.type) { 2118 | case gpu::AttributeType::FLOAT: 2119 | break; 2120 | case gpu::AttributeType::I16: 2121 | case gpu::AttributeType::I8: 2122 | if (attr.flags & gpu::Attribute::AS_INT) { 2123 | switch (attr.components_count) { 2124 | case 1: return "int"; 2125 | case 2: return "ivec2"; 2126 | case 3: return "ivec3"; 2127 | case 4: return "ivec4"; 2128 | } 2129 | ASSERT(false); 2130 | return "int"; 2131 | } 2132 | break; 2133 | case gpu::AttributeType::U32: 2134 | case gpu::AttributeType::U16: 2135 | case gpu::AttributeType::U8: 2136 | if (attr.flags & gpu::Attribute::AS_INT) { 2137 | switch (attr.components_count) { 2138 | case 1: return "uint"; 2139 | case 2: return "uvec2"; 2140 | case 3: return "uvec3"; 2141 | case 4: return "uvec4"; 2142 | } 2143 | ASSERT(false); 2144 | return "int"; 2145 | } 2146 | break; 2147 | } 2148 | switch (attr.components_count) { 2149 | case 1: return "float"; 2150 | case 2: return "vec2"; 2151 | case 3: return "vec3"; 2152 | case 4: return "vec4"; 2153 | } 2154 | ASSERT(false); 2155 | return "float"; 2156 | } 2157 | 2158 | bool generate(OutputMemoryStream& blob) override; 2159 | 2160 | bool onGUI() override { 2161 | ImGuiEx::NodeTitle(m_type == Type::SURFACE ? "PBR Surface" : "PBR Particles"); 2162 | 2163 | bool changed = ImGui::Combo("Type", (i32*)&m_type, "SURFACE\0PARTICLES\0"); 2164 | 2165 | inputSlot(); ImGui::TextUnformatted("Albedo"); 2166 | inputSlot(); ImGui::TextUnformatted("Normal"); 2167 | inputSlot(); ImGui::TextUnformatted("Opacity"); 2168 | inputSlot(); ImGui::TextUnformatted("Roughness"); 2169 | inputSlot(); ImGui::TextUnformatted("Metallic"); 2170 | inputSlot(); ImGui::TextUnformatted("Emission"); 2171 | inputSlot(); ImGui::TextUnformatted("AO"); 2172 | inputSlot(); ImGui::TextUnformatted("Translucency"); 2173 | inputSlot(); ImGui::TextUnformatted("Shadow"); 2174 | inputSlot(); ImGui::TextUnformatted("Position offset"); 2175 | 2176 | ImGui::Checkbox("Masked", &m_is_masked); 2177 | 2178 | if (m_type == Type::PARTICLES && ImGui::Button("Copy vertex declaration")) { 2179 | m_show_fs = true; 2180 | } 2181 | 2182 | FileSelector& fs = m_resource.m_editor.m_app.getFileSelector(); 2183 | if (fs.gui("Select particle", &m_show_fs, "par", false)) { 2184 | // TODO emitter index 2185 | m_vertex_decl = ParticleEditor::getVertexDecl(fs.getPath(), 0, m_attributes_names, m_resource.m_editor.m_app); 2186 | if (m_vertex_decl.attributes_count < 1 || m_vertex_decl.attributes[0].components_count != 3) { 2187 | logError("First particle shader input must be position (have 3 components)"); 2188 | } 2189 | changed = true; 2190 | } 2191 | 2192 | return changed; 2193 | } 2194 | 2195 | enum class Type : u32 { 2196 | SURFACE, 2197 | PARTICLES 2198 | }; 2199 | 2200 | Array m_attributes_names; 2201 | gpu::VertexDecl m_vertex_decl; 2202 | Type m_type = Type::SURFACE; 2203 | bool m_show_fs = false; 2204 | bool m_is_masked = false; 2205 | }; 2206 | 2207 | struct ParticleStreamNode : ShaderEditorResource::Node { 2208 | explicit ParticleStreamNode(ShaderEditorResource& resource) 2209 | : Node(resource) 2210 | {} 2211 | 2212 | ShaderNodeType getType() const override { return ShaderNodeType::PARTICLE_STREAM; } 2213 | 2214 | bool hasInputPins() const override { return false; } 2215 | bool hasOutputPins() const override { return true; } 2216 | 2217 | void serialize(OutputMemoryStream& blob) override { blob.write(m_stream); } 2218 | void deserialize(InputMemoryStream& blob) override { blob.read(m_stream); } 2219 | 2220 | ShaderEditorResource::ValueType getOutputType(int idx) const override { 2221 | const Node* n = m_resource.m_nodes[0]; 2222 | ASSERT(n->getType() == ShaderNodeType::PBR); 2223 | const PBRNode* pbr = (const PBRNode*)n; 2224 | if (m_stream >= pbr->m_vertex_decl.attributes_count) return ShaderEditorResource::ValueType::FLOAT; 2225 | return toType(pbr->m_vertex_decl.attributes[m_stream]); 2226 | } 2227 | 2228 | bool generate(OutputMemoryStream& blob) override { return true; } 2229 | 2230 | void printReference(OutputMemoryStream& blob, int output_idx) const override { 2231 | const Node* n = m_resource.m_nodes[0]; 2232 | ASSERT(n->getType() == ShaderNodeType::PBR); 2233 | const PBRNode* pbr = (const PBRNode*)n; 2234 | if (m_stream >= pbr->m_vertex_decl.attributes_count) return; 2235 | 2236 | blob << "v_" << pbr->m_attributes_names[m_stream].c_str(); 2237 | } 2238 | 2239 | bool onGUI() override { 2240 | ImGuiEx::NodeTitle("Particle stream"); 2241 | outputSlot(); 2242 | const Node* n = m_resource.m_nodes[0]; 2243 | ASSERT(n->getType() == ShaderNodeType::PBR); 2244 | const PBRNode* pbr = (const PBRNode*)n; 2245 | const char* preview = m_stream < (u32)pbr->m_attributes_names.size() ? pbr->m_attributes_names[m_stream].c_str() : "N/A"; 2246 | ImGui::TextUnformatted(preview); 2247 | return false; 2248 | } 2249 | 2250 | u32 m_stream = 0; 2251 | }; 2252 | 2253 | bool PBRNode::generate(OutputMemoryStream& blob) { 2254 | blob << "import \"pipelines/surface_base.inc\"\n\n"; 2255 | 2256 | IAllocator& allocator = m_resource.m_allocator; 2257 | Array uniforms(allocator); 2258 | Array defines(allocator); 2259 | Array textures(allocator); 2260 | Array particle_streams(allocator); 2261 | Array functions(allocator); 2262 | 2263 | auto add_function = [&](FunctionCallNode* n){ 2264 | const i32 idx = functions.indexOf(n->m_function_resource); 2265 | if (idx < 0) functions.push(n->m_function_resource); 2266 | }; 2267 | 2268 | auto add_particle_stream = [&](ParticleStreamNode* n){ 2269 | const i32 idx = particle_streams.indexOf(n->m_stream); 2270 | if (idx < 0) particle_streams.push(n->m_stream); 2271 | }; 2272 | 2273 | auto add_uniform = [&](auto* n, const char* type) { 2274 | const i32 idx = uniforms.find([&](const String& u) { return u == n->m_name; }); 2275 | if (idx < 0) { 2276 | uniforms.emplace(n->m_name.c_str(), allocator); 2277 | blob << "uniform(\"" << n->m_name.c_str() << "\", \"" << type << "\")\n"; 2278 | } 2279 | }; 2280 | 2281 | auto add_define = [&](StaticSwitchNode* n){ 2282 | const i32 idx = defines.find([&](const String& u) { return u == n->m_define; }); 2283 | if (idx < 0) { 2284 | defines.emplace(n->m_define.c_str(), allocator); 2285 | blob << "define(\"" << n->m_define.c_str() << "\")\n"; 2286 | } 2287 | }; 2288 | 2289 | auto add_texture = [&](SampleNode* n){ 2290 | const i32 idx = textures.find([&](const String& u) { return u == n->m_texture; }); 2291 | if (idx < 0) { 2292 | textures.emplace(n->m_texture.c_str(), allocator); 2293 | blob << "{\n" 2294 | << "\tname = \"" << n->m_texture.c_str() << "\",\n" 2295 | << "\tdefault_texture = \"textures/common/white.tga\"\n" 2296 | << "}\n"; 2297 | } 2298 | }; 2299 | 2300 | for (Node* n : m_resource.m_nodes) { 2301 | if (!n->m_reachable) continue; 2302 | switch(n->getType()) { 2303 | case ShaderNodeType::PARTICLE_STREAM: 2304 | add_particle_stream((ParticleStreamNode*)n); 2305 | break; 2306 | case ShaderNodeType::SCALAR_PARAM: 2307 | add_uniform((ParameterNode*)n, "float"); 2308 | break; 2309 | case ShaderNodeType::VEC4_PARAM: 2310 | add_uniform((ParameterNode*)n, "vec4"); 2311 | break; 2312 | case ShaderNodeType::COLOR_PARAM: 2313 | add_uniform((ParameterNode*)n, "color"); 2314 | break; 2315 | case ShaderNodeType::FUNCTION_CALL: 2316 | add_function((FunctionCallNode*)n); 2317 | break; 2318 | case ShaderNodeType::STATIC_SWITCH: 2319 | add_define((StaticSwitchNode*)n); 2320 | break; 2321 | default: break; 2322 | } 2323 | } 2324 | 2325 | if (m_type == Type::PARTICLES) { 2326 | blob << "common(\"#define PARTICLES\\n\")\n"; 2327 | } 2328 | 2329 | blob << "surface_shader_ex({\n"; 2330 | blob << "texture_slots = {\n"; 2331 | for (Node* n : m_resource.m_nodes) { 2332 | if (!n->m_reachable) continue; 2333 | if (n->getType() == ShaderNodeType::SAMPLE) add_texture((SampleNode*)n); 2334 | } 2335 | blob << "},\n"; 2336 | 2337 | if (m_type == Type::PARTICLES && !m_attributes_names.empty()) { 2338 | blob << "vertex_preface = [[\n"; 2339 | for (u32 i : particle_streams) { 2340 | blob << "\tlayout(location = " << i << ") in " << typeToString(m_vertex_decl.attributes[i]) << " i_" << m_attributes_names[i].c_str() << ";\n"; 2341 | blob << "\tlayout(location = " << i + 1 << ") out " << typeToString(m_vertex_decl.attributes[i]) << " v_" << m_attributes_names[i].c_str() << ";\n"; 2342 | } 2343 | blob << R"#( 2344 | layout (location = 0) out vec2 v_uv; 2345 | ]], 2346 | vertex = [[ 2347 | vec2 pos = vec2(gl_VertexID & 1, (gl_VertexID & 2) * 0.5); 2348 | v_uv = pos; 2349 | )#"; 2350 | for (u32 i : particle_streams) { 2351 | const String& a = m_attributes_names[i]; 2352 | blob << "\t\tv_" << a.c_str() << " = i_" << a.c_str() << ";\n"; 2353 | } 2354 | blob << R"#( 2355 | pos = pos * 2 - 1; 2356 | gl_Position = Pass.projection * ((Pass.view * u_model * vec4(i_)#" << m_attributes_names[0].c_str() << R"#(.xyz, 1)) + vec4(pos.xy, 0, 0)); 2357 | ]], 2358 | 2359 | fragment_preface = [[ 2360 | )#"; 2361 | // TODO vertex shader functions 2362 | for (ShaderEditorResource* f : functions) { 2363 | f->clearGeneratedFlags(); 2364 | String s(m_resource.m_allocator); 2365 | if (!f->generate(&s)) return false; 2366 | blob << s.c_str() << "\n\n"; 2367 | } 2368 | for (u32 i : particle_streams) { 2369 | blob << "\tlayout(location = " << i + 1 << ") in " << typeToString(m_vertex_decl.attributes[i]) << " v_" << m_attributes_names[i].c_str() << ";\n"; 2370 | } 2371 | blob << R"#( 2372 | layout (location = 0) in vec2 v_uv; 2373 | ]], 2374 | )#"; 2375 | } 2376 | else { 2377 | blob << "fragment_preface = [[\n"; 2378 | for (ShaderEditorResource* f : functions) { 2379 | f->clearGeneratedFlags(); 2380 | String s(m_resource.m_allocator); 2381 | if (!f->generate(&s)) return false; 2382 | blob << s.c_str() << "\n\n"; 2383 | } 2384 | blob << "]],\n\n"; 2385 | } 2386 | 2387 | 2388 | blob << "fragment = [[\n"; 2389 | 2390 | bool need_local_position = false; 2391 | for (Node* n : m_resource.m_nodes) { 2392 | if (n->getType() == ShaderNodeType::POSITION) { 2393 | need_local_position = need_local_position || ((PositionNode*)n)->m_space == PositionNode::LOCAL; 2394 | } 2395 | n->m_generated = false; 2396 | } 2397 | 2398 | const struct { 2399 | const char* name; 2400 | const char* default_value; 2401 | const char* particle_default = nullptr; 2402 | } 2403 | fields[] = { 2404 | { "albedo", "vec3(1, 0, 1)" }, 2405 | { "N", "v_normal", "vec3(0, 1, 0)" }, 2406 | { "alpha", "1" }, 2407 | { "roughness", "1" }, 2408 | { "metallic", "0" }, 2409 | { "emission", "0" }, 2410 | { "ao", "1" }, 2411 | { "translucency", "0" }, 2412 | { "shadow", "1" } 2413 | }; 2414 | 2415 | for (const auto& field : fields) { 2416 | const int i = int(&field - fields); 2417 | Input input = getInput(m_resource, m_id, i); 2418 | if (input) { 2419 | input.node->generateOnce(blob); 2420 | blob << "\tdata." << field.name << " = "; 2421 | if (i < 2) blob << "vec3("; 2422 | input.printReference(blob); 2423 | const ShaderEditorResource::ValueType type = input.node->getOutputType(input.output_idx); 2424 | if (i == 0) { 2425 | switch(type) { 2426 | case ShaderEditorResource::ValueType::IVEC4: 2427 | case ShaderEditorResource::ValueType::VEC4: 2428 | blob << ".rgb"; break; 2429 | case ShaderEditorResource::ValueType::VEC3: 2430 | break; 2431 | case ShaderEditorResource::ValueType::VEC2: 2432 | blob << ".rgr"; break; 2433 | case ShaderEditorResource::ValueType::BOOL: 2434 | case ShaderEditorResource::ValueType::INT: 2435 | case ShaderEditorResource::ValueType::FLOAT: 2436 | break; 2437 | case ShaderEditorResource::ValueType::COUNT: 2438 | case ShaderEditorResource::ValueType::NONE: 2439 | // invalid data 2440 | break; 2441 | } 2442 | } 2443 | else if (type != ShaderEditorResource::ValueType::VEC3 && i < 2) blob << ".rgb"; 2444 | else if (type != ShaderEditorResource::ValueType::FLOAT && i >= 2) blob << ".x"; 2445 | if (i < 2) blob << ")"; 2446 | blob << ";\n"; 2447 | } 2448 | else { 2449 | if (m_type == Type::PARTICLES && field.particle_default) { 2450 | blob << "\tdata." << field.name << " = " << field.particle_default << ";\n"; 2451 | } 2452 | else { 2453 | blob << "\tdata." << field.name << " = " << field.default_value << ";\n"; 2454 | } 2455 | } 2456 | } 2457 | 2458 | blob << "\tdata.V = vec3(0);\n"; 2459 | blob << "\tdata.wpos = vec3(0);\n"; 2460 | if (m_is_masked) { 2461 | blob << "\tif (data.alpha < 0.5) discard;\n"; 2462 | } 2463 | blob << "]]\n"; 2464 | Input po_input = getInput(m_resource, m_id, lengthOf(fields)); 2465 | if (po_input) { 2466 | blob << ", vertex [["; 2467 | po_input.node->generateOnce(blob); 2468 | blob << "v_wpos += "; 2469 | po_input.printReference(blob); 2470 | blob << ";\n"; 2471 | blob << "]]\n"; 2472 | } 2473 | 2474 | if (need_local_position) blob << ",\nneed_local_position = true\n"; 2475 | blob << "})\n"; 2476 | return true; 2477 | } 2478 | 2479 | struct BackfaceSwitchNode : ShaderEditorResource::Node { 2480 | explicit BackfaceSwitchNode(ShaderEditorResource& resource) 2481 | : Node(resource) 2482 | {} 2483 | 2484 | ShaderNodeType getType() const override { return ShaderNodeType::BACKFACE_SWITCH; } 2485 | bool hasInputPins() const override { return true; } 2486 | bool hasOutputPins() const override { return true; } 2487 | 2488 | void serialize(OutputMemoryStream& blob) override {} 2489 | void deserialize(InputMemoryStream& blob) override {} 2490 | 2491 | ShaderEditorResource::ValueType getOutputType(int) const override { 2492 | const Input inputA = getInput(m_resource, m_id, 0); 2493 | if (inputA) { 2494 | return inputA.node->getOutputType(inputA.output_idx); 2495 | } 2496 | const Input inputB = getInput(m_resource, m_id, 1); 2497 | if (inputB) { 2498 | return inputB.node->getOutputType(inputB.output_idx); 2499 | } 2500 | return ShaderEditorResource::ValueType::FLOAT; 2501 | } 2502 | 2503 | bool generate(OutputMemoryStream& blob) override { 2504 | const Input inputA = getInput(m_resource, m_id, 0); 2505 | const Input inputB = getInput(m_resource, m_id, 1); 2506 | if (!inputA && !inputB) return error("Missing inputs"); 2507 | 2508 | blob << "\t\t" << toString(getOutputType(0)) << " v" << m_id << ";\n"; 2509 | if (inputA) { 2510 | blob << "\tif (gl_FrontFacing) {\n"; 2511 | inputA.node->generateOnce(blob); 2512 | blob << "\t\tv" << m_id << " = "; 2513 | inputA.printReference(blob); 2514 | blob << ";\n\t}\n"; 2515 | } 2516 | if (inputB) { 2517 | blob << "\tif (!gl_FrontFacing) {\n"; 2518 | inputB.node->generateOnce(blob); 2519 | blob << "\t\tv" << m_id << " = "; 2520 | inputB.printReference(blob); 2521 | blob << ";\n\t}\n"; 2522 | } 2523 | return true; 2524 | } 2525 | 2526 | bool onGUI() override { 2527 | ImGuiEx::NodeTitle("Backface switch"); 2528 | outputSlot(); 2529 | inputSlot(); ImGui::TextUnformatted("Front"); 2530 | inputSlot(); ImGui::TextUnformatted("Back"); 2531 | return false; 2532 | } 2533 | }; 2534 | 2535 | struct IfNode : ShaderEditorResource::Node { 2536 | explicit IfNode(ShaderEditorResource& resource) 2537 | : Node(resource) 2538 | {} 2539 | 2540 | ShaderNodeType getType() const override { return ShaderNodeType::IF; } 2541 | bool hasInputPins() const override { return true; } 2542 | bool hasOutputPins() const override { return true; } 2543 | 2544 | void serialize(OutputMemoryStream& blob) override {} 2545 | void deserialize(InputMemoryStream& blob) override {} 2546 | 2547 | bool generate(OutputMemoryStream& blob) override { 2548 | const Input inputA = getInput(m_resource, m_id, 0); 2549 | const Input inputB = getInput(m_resource, m_id, 1); 2550 | const Input inputGT = getInput(m_resource, m_id, 2); 2551 | const Input inputEQ = getInput(m_resource, m_id, 3); 2552 | const Input inputLT = getInput(m_resource, m_id, 4); 2553 | if (!inputA || !inputB) return error("Missing input"); 2554 | if (!inputGT && !inputEQ && !inputLT) return error("Missing input"); 2555 | 2556 | inputA.node->generateOnce(blob); 2557 | inputB.node->generateOnce(blob); 2558 | 2559 | blob << "\t\t" << toString(getOutputType(0)) << " v" << m_id << ";\n"; 2560 | if (inputGT) { 2561 | inputGT.node->generateOnce(blob); 2562 | 2563 | blob << "\t\tif("; 2564 | inputA.printReference(blob); 2565 | blob << " > "; 2566 | inputB.printReference(blob); 2567 | blob << ") {\n"; 2568 | blob << "\t\t\tv" << m_id << " = "; 2569 | inputGT.printReference(blob); 2570 | blob << ";\n"; 2571 | blob << "\t\t}\n"; 2572 | } 2573 | if (inputEQ) { 2574 | inputEQ.node->generateOnce(blob); 2575 | 2576 | blob << "\t\tif("; 2577 | inputA.printReference(blob); 2578 | blob << " == "; 2579 | inputB.printReference(blob); 2580 | blob << ") {\n"; 2581 | blob << "\t\t\tv" << m_id << " = "; 2582 | inputEQ.printReference(blob); 2583 | blob << ";\n"; 2584 | blob << "\t\t}\n"; 2585 | } 2586 | if (inputLT) { 2587 | inputLT.node->generateOnce(blob); 2588 | 2589 | blob << "\t\tif("; 2590 | inputA.printReference(blob); 2591 | blob << " < "; 2592 | inputB.printReference(blob); 2593 | blob << ") {\n"; 2594 | blob << "\t\t\tv" << m_id << " = "; 2595 | inputLT.printReference(blob); 2596 | blob << ";\n"; 2597 | blob << "\t\t}\n"; 2598 | } 2599 | return true; 2600 | } 2601 | 2602 | bool onGUI() override { 2603 | ImGui::BeginGroup(); 2604 | inputSlot(); 2605 | ImGui::Text("A"); 2606 | 2607 | inputSlot(); 2608 | ImGui::Text("B"); 2609 | 2610 | inputSlot(); 2611 | ImGui::Text("A > B"); 2612 | 2613 | inputSlot(); 2614 | ImGui::Text("A == B"); 2615 | 2616 | inputSlot(); 2617 | ImGui::Text("A < B"); 2618 | ImGui::EndGroup(); 2619 | 2620 | ImGui::SameLine(); 2621 | 2622 | outputSlot(); 2623 | ImGui::TextUnformatted("Output"); 2624 | 2625 | return false; 2626 | } 2627 | }; 2628 | 2629 | struct VertexIDNode : ShaderEditorResource::Node 2630 | { 2631 | explicit VertexIDNode(ShaderEditorResource& resource) 2632 | : Node(resource) 2633 | {} 2634 | 2635 | ShaderNodeType getType() const override { return ShaderNodeType::VERTEX_ID; } 2636 | bool hasInputPins() const override { return false; } 2637 | bool hasOutputPins() const override { return true; } 2638 | 2639 | void serialize(OutputMemoryStream& blob) override {} 2640 | void deserialize(InputMemoryStream& blob) override {} 2641 | 2642 | void printReference(OutputMemoryStream& blob, int output_idx) const override { 2643 | blob << "gl_VertexID"; 2644 | } 2645 | 2646 | ShaderEditorResource::ValueType getOutputType(int) const override 2647 | { 2648 | return ShaderEditorResource::ValueType::INT; 2649 | } 2650 | 2651 | bool onGUI() override { 2652 | outputSlot(); 2653 | ImGui::Text("Vertex ID"); 2654 | return false; 2655 | } 2656 | }; 2657 | 2658 | template 2659 | struct UniformNode : ShaderEditorResource::Node 2660 | { 2661 | explicit UniformNode(ShaderEditorResource& resource) 2662 | : Node(resource) 2663 | {} 2664 | 2665 | ShaderNodeType getType() const override { return Type; } 2666 | bool hasInputPins() const override { return false; } 2667 | bool hasOutputPins() const override { return true; } 2668 | 2669 | void serialize(OutputMemoryStream& blob) override {} 2670 | void deserialize(InputMemoryStream& blob) override {} 2671 | 2672 | void printReference(OutputMemoryStream& blob, int output_idx) const override 2673 | { 2674 | blob << getVarName(); 2675 | } 2676 | 2677 | ShaderEditorResource::ValueType getOutputType(int) const override 2678 | { 2679 | switch (Type) { 2680 | case ShaderNodeType::SCREEN_POSITION: return ShaderEditorResource::ValueType::VEC2; 2681 | case ShaderNodeType::VIEW_DIR: return ShaderEditorResource::ValueType::VEC3; 2682 | case ShaderNodeType::SCENE_DEPTH: 2683 | case ShaderNodeType::PIXEL_DEPTH: 2684 | case ShaderNodeType::TIME: 2685 | return ShaderEditorResource::ValueType::FLOAT; 2686 | default: 2687 | ASSERT(false); 2688 | return ShaderEditorResource::ValueType::FLOAT; 2689 | } 2690 | } 2691 | 2692 | static const char* getVarName() { 2693 | switch (Type) { 2694 | case ShaderNodeType::TIME: return "Global.time"; 2695 | case ShaderNodeType::VIEW_DIR: return "Pass.view_dir.xyz"; 2696 | case ShaderNodeType::PIXEL_DEPTH: return "toLinearDepth(Pass.inv_projection, gl_FragCoord.z)"; 2697 | case ShaderNodeType::SCENE_DEPTH: return "toLinearDepth(Pass.inv_projection, texture(u_depthbuffer, gl_FragCoord.xy / Global.framebuffer_size).x)"; 2698 | case ShaderNodeType::SCREEN_POSITION: return "(gl_FragCoord.xy / Global.framebuffer_size)"; 2699 | default: ASSERT(false); return "Error"; 2700 | } 2701 | } 2702 | 2703 | static const char* getName() { 2704 | switch (Type) { 2705 | case ShaderNodeType::TIME: return "Time"; 2706 | case ShaderNodeType::VIEW_DIR: return "View direction"; 2707 | case ShaderNodeType::PIXEL_DEPTH: return "Pixel depth"; 2708 | case ShaderNodeType::SCENE_DEPTH: return "Scene depth"; 2709 | case ShaderNodeType::SCREEN_POSITION: return "Screen position"; 2710 | default: ASSERT(false); return "Error"; 2711 | } 2712 | } 2713 | 2714 | bool onGUI() override 2715 | { 2716 | outputSlot(); 2717 | ImGui::TextUnformatted(getName()); 2718 | return false; 2719 | } 2720 | }; 2721 | 2722 | ShaderEditorResource::ValueType ShaderEditorResource::getFunctionOutputType() const { 2723 | for (const Node* n : m_nodes) { 2724 | if (n->getType() == ShaderNodeType::FUNCTION_OUTPUT) { 2725 | const Input input = getInput(*this, n->m_id, 0); 2726 | if (!input) return ShaderEditorResource::ValueType::NONE; 2727 | 2728 | return input.node->getOutputType(input.output_idx); 2729 | } 2730 | } 2731 | ASSERT(false); 2732 | return ShaderEditorResource::ValueType::NONE; 2733 | } 2734 | 2735 | ShaderResourceEditorType ShaderEditorResource::getShaderType() const { 2736 | 2737 | switch (m_nodes[0]->getType()) { 2738 | case ShaderNodeType::PBR: return ((PBRNode*)m_nodes[0])->m_type == PBRNode::Type::PARTICLES 2739 | ? ShaderResourceEditorType::PARTICLE 2740 | : ShaderResourceEditorType::SURFACE; 2741 | case ShaderNodeType::FUNCTION_OUTPUT: return ShaderResourceEditorType::FUNCTION; 2742 | default: ASSERT(false); return ShaderResourceEditorType::SURFACE; 2743 | } 2744 | } 2745 | 2746 | void ShaderEditorResource::init(ShaderResourceEditorType type) { 2747 | Node* node = nullptr; 2748 | switch(type) { 2749 | case ShaderResourceEditorType::PARTICLE: 2750 | case ShaderResourceEditorType::SURFACE: { 2751 | PBRNode* output = LUMIX_NEW(m_allocator, PBRNode)(*this); 2752 | output->m_type = (type == ShaderResourceEditorType::PARTICLE) ? PBRNode::Type::PARTICLES : PBRNode::Type::SURFACE; 2753 | node = output; 2754 | break; 2755 | } 2756 | case ShaderResourceEditorType::FUNCTION: { 2757 | node = LUMIX_NEW(m_allocator, FunctionOutputNode)(*this); 2758 | break; 2759 | } 2760 | } 2761 | ASSERT(node); 2762 | m_nodes.push(node); 2763 | node->m_pos.x = 50; 2764 | node->m_pos.y = 50; 2765 | node->m_id = ++m_last_node_id; 2766 | } 2767 | 2768 | ShaderEditorResource::Node* ShaderEditorResource::createNode(int type) { 2769 | switch ((ShaderNodeType)type) { 2770 | case ShaderNodeType::PBR: return LUMIX_NEW(m_allocator, PBRNode)(*this); 2771 | case ShaderNodeType::PIN: return LUMIX_NEW(m_allocator, PinNode)(*this); 2772 | case ShaderNodeType::VEC4: return LUMIX_NEW(m_allocator, ConstNode)(*this); 2773 | case ShaderNodeType::VEC3: return LUMIX_NEW(m_allocator, ConstNode)(*this); 2774 | case ShaderNodeType::VEC2: return LUMIX_NEW(m_allocator, ConstNode)(*this); 2775 | case ShaderNodeType::NUMBER: return LUMIX_NEW(m_allocator, ConstNode)(*this); 2776 | case ShaderNodeType::SAMPLE: return LUMIX_NEW(m_allocator, SampleNode)(*this, m_allocator); 2777 | case ShaderNodeType::MULTIPLY: return LUMIX_NEW(m_allocator, OperatorNode)(*this); 2778 | case ShaderNodeType::ADD: return LUMIX_NEW(m_allocator, OperatorNode)(*this); 2779 | case ShaderNodeType::DIVIDE: return LUMIX_NEW(m_allocator, OperatorNode)(*this); 2780 | case ShaderNodeType::SUBTRACT: return LUMIX_NEW(m_allocator, OperatorNode)(*this); 2781 | case ShaderNodeType::PARTICLE_STREAM: return LUMIX_NEW(m_allocator, ParticleStreamNode)(*this); 2782 | case ShaderNodeType::SWIZZLE: return LUMIX_NEW(m_allocator, SwizzleNode)(*this); 2783 | case ShaderNodeType::TIME: return LUMIX_NEW(m_allocator, UniformNode)(*this); 2784 | case ShaderNodeType::VIEW_DIR: return LUMIX_NEW(m_allocator, UniformNode)(*this); 2785 | case ShaderNodeType::PIXEL_DEPTH: return LUMIX_NEW(m_allocator, UniformNode)(*this); 2786 | case ShaderNodeType::SCENE_DEPTH: return LUMIX_NEW(m_allocator, UniformNode)(*this); 2787 | case ShaderNodeType::SCREEN_POSITION: return LUMIX_NEW(m_allocator, UniformNode)(*this); 2788 | case ShaderNodeType::VERTEX_ID: return LUMIX_NEW(m_allocator, VertexIDNode)(*this); 2789 | case ShaderNodeType::BACKFACE_SWITCH: return LUMIX_NEW(m_allocator, BackfaceSwitchNode)(*this); 2790 | case ShaderNodeType::IF: return LUMIX_NEW(m_allocator, IfNode)(*this); 2791 | case ShaderNodeType::STATIC_SWITCH: return LUMIX_NEW(m_allocator, StaticSwitchNode)(*this, m_allocator); 2792 | case ShaderNodeType::FUNCTION_INPUT: return LUMIX_NEW(m_allocator, FunctionInputNode)(*this, m_allocator); 2793 | case ShaderNodeType::FUNCTION_OUTPUT: return LUMIX_NEW(m_allocator, FunctionOutputNode)(*this); 2794 | case ShaderNodeType::FUNCTION_CALL: return LUMIX_NEW(m_allocator, FunctionCallNode)(*this); 2795 | case ShaderNodeType::ONEMINUS: return LUMIX_NEW(m_allocator, OneMinusNode)(*this); 2796 | case ShaderNodeType::CODE: return LUMIX_NEW(m_allocator, CodeNode)(*this, m_allocator); 2797 | case ShaderNodeType::APPEND: return LUMIX_NEW(m_allocator, AppendNode)(*this); 2798 | case ShaderNodeType::FRESNEL: return LUMIX_NEW(m_allocator, FresnelNode)(*this); 2799 | case ShaderNodeType::POSITION: return LUMIX_NEW(m_allocator, PositionNode)(*this); 2800 | case ShaderNodeType::NORMAL: return LUMIX_NEW(m_allocator, VaryingNode)(*this); 2801 | case ShaderNodeType::UV0: return LUMIX_NEW(m_allocator, VaryingNode)(*this); 2802 | case ShaderNodeType::SCALAR_PARAM: return LUMIX_NEW(m_allocator, ParameterNode)(*this, m_allocator); 2803 | case ShaderNodeType::COLOR_PARAM: return LUMIX_NEW(m_allocator, ParameterNode)(*this, m_allocator); 2804 | case ShaderNodeType::VEC4_PARAM: return LUMIX_NEW(m_allocator, ParameterNode)(*this, m_allocator); 2805 | case ShaderNodeType::MIX: return LUMIX_NEW(m_allocator, MixNode)(*this); 2806 | 2807 | case ShaderNodeType::ABS: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2808 | case ShaderNodeType::ALL: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2809 | case ShaderNodeType::ANY: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2810 | case ShaderNodeType::CEIL: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2811 | case ShaderNodeType::COS: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2812 | case ShaderNodeType::EXP: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2813 | case ShaderNodeType::EXP2: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2814 | case ShaderNodeType::FLOOR: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2815 | case ShaderNodeType::FRACT: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2816 | case ShaderNodeType::LOG: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2817 | case ShaderNodeType::LOG2: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2818 | case ShaderNodeType::NORMALIZE: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2819 | case ShaderNodeType::NOT: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2820 | case ShaderNodeType::ROUND: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2821 | case ShaderNodeType::SATURATE: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2822 | case ShaderNodeType::SIN: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2823 | case ShaderNodeType::SQRT: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2824 | case ShaderNodeType::TAN: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2825 | case ShaderNodeType::TRANSPOSE: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2826 | case ShaderNodeType::TRUNC: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2827 | case ShaderNodeType::LENGTH: return LUMIX_NEW(m_allocator, BuiltinFunctionCallNode)(*this); 2828 | 2829 | case ShaderNodeType::DOT: return LUMIX_NEW(m_allocator, BinaryBuiltinFunctionCallNode)(*this); 2830 | case ShaderNodeType::CROSS: return LUMIX_NEW(m_allocator, BinaryBuiltinFunctionCallNode)(*this); 2831 | case ShaderNodeType::MIN: return LUMIX_NEW(m_allocator, BinaryBuiltinFunctionCallNode)(*this); 2832 | case ShaderNodeType::MAX: return LUMIX_NEW(m_allocator, BinaryBuiltinFunctionCallNode)(*this); 2833 | case ShaderNodeType::POW: return LUMIX_NEW(m_allocator, PowerNode)(*this); 2834 | case ShaderNodeType::DISTANCE: return LUMIX_NEW(m_allocator, BinaryBuiltinFunctionCallNode)(*this); 2835 | } 2836 | 2837 | ASSERT(false); 2838 | return nullptr; 2839 | } 2840 | 2841 | struct ShaderEditorWindow : public AssetEditorWindow, NodeEditor { 2842 | using Node = ShaderEditorResource::Node; 2843 | using Link = NodeEditorLink; 2844 | 2845 | struct INodeTypeVisitor { 2846 | struct ICreator { 2847 | virtual void create(ShaderEditorWindow& editor, ImVec2 pos) = 0; 2848 | }; 2849 | 2850 | virtual bool beginCategory(const char* name) { return true; } 2851 | virtual void endCategory() {} 2852 | virtual INodeTypeVisitor& visitType(const char* label, ICreator& creator, char shortcut = 0) = 0; 2853 | 2854 | INodeTypeVisitor& visitType(const char* label, ShaderNodeType type, char shortcut = 0); 2855 | }; 2856 | 2857 | explicit ShaderEditorWindow(const Path& path, ShaderEditor& editor, StudioApp& app, IAllocator& allocator) 2858 | : NodeEditor(app.getAllocator()) 2859 | , AssetEditorWindow(app) 2860 | , m_editor(editor) 2861 | , m_allocator(allocator) 2862 | , m_app(app) 2863 | , m_source(allocator) 2864 | , m_resource(path, editor, allocator) 2865 | { 2866 | m_resource.load(app); 2867 | pushUndo(NO_MERGE_UNDO); 2868 | m_dirty = false; 2869 | } 2870 | 2871 | void windowGUI() override { 2872 | if (m_source_open) { 2873 | ImGui::SetNextWindowSize(ImVec2(300, 300), ImGuiCond_FirstUseEver); 2874 | if (ImGui::Begin("Shader source", &m_source_open)) { 2875 | if (m_source.length() == 0) { 2876 | ImGui::Text("Empty"); 2877 | } else { 2878 | ImGui::SetNextItemWidth(-1); 2879 | ImGui::InputTextMultiline("##src", m_source.getMutableData(), m_source.length(), ImVec2(0, ImGui::GetContentRegionAvail().y), ImGuiInputTextFlags_ReadOnly); 2880 | } 2881 | } 2882 | ImGui::End(); 2883 | } 2884 | 2885 | onGUIMenu(); 2886 | 2887 | FileSelector& fs = m_app.getFileSelector(); 2888 | if (fs.gui("Save As", &m_show_save_as, "sed", true)) saveAs(fs.getPath()); 2889 | 2890 | ImGui::BeginChild("canvas"); 2891 | nodeEditorGUI(m_resource.m_nodes, m_resource.m_links); 2892 | ImGui::EndChild(); 2893 | } 2894 | 2895 | const Path& getPath() override { return m_resource.m_path; } 2896 | 2897 | void pushUndo(u32 tag) override { 2898 | m_dirty = true; 2899 | SimpleUndoRedo::pushUndo(tag); 2900 | m_resource.generate(&m_source); 2901 | } 2902 | 2903 | const char* getName() const override { return "shader_editor"; } 2904 | 2905 | void saveAs(const char* path) { 2906 | // TODO update shaders using a function when the function is changed 2907 | os::OutputFile file; 2908 | FileSystem& fs = m_app.getEngine().getFileSystem(); 2909 | 2910 | OutputMemoryStream blob(m_allocator); 2911 | m_resource.serialize(blob); 2912 | if (!fs.saveContentSync(Path(path), blob)) { 2913 | logError("Could not save ", path); 2914 | return; 2915 | } 2916 | 2917 | m_resource.m_path = path; 2918 | m_dirty = false; 2919 | } 2920 | 2921 | void load(const char* path) { 2922 | FileSystem& fs = m_app.getEngine().getFileSystem(); 2923 | OutputMemoryStream data(m_allocator); 2924 | if (!fs.getContentSync(Path(path), data)) { 2925 | logError("Failed to load ", path); 2926 | return; 2927 | } 2928 | 2929 | InputMemoryStream blob(data); 2930 | m_resource.deserialize(blob); 2931 | m_resource.m_path = path; 2932 | 2933 | clearUndoStack(); 2934 | pushUndo(NO_MERGE_UNDO); 2935 | } 2936 | 2937 | void serialize(OutputMemoryStream& blob) override { 2938 | m_resource.serialize(blob); 2939 | } 2940 | 2941 | void deserialize(InputMemoryStream& blob) override { 2942 | m_resource.clear(); 2943 | m_resource.deserialize(blob); 2944 | } 2945 | 2946 | void onCanvasClicked(ImVec2 pos, i32 hovered_link) override { 2947 | struct : INodeTypeVisitor { 2948 | INodeTypeVisitor& visitType(const char* label, ICreator& creator, char shortcut) override { 2949 | if (shortcut && os::isKeyDown((os::Keycode)shortcut)) { 2950 | creator.create(*editor, pos); 2951 | if (hovered_link >= 0) editor->splitLink(editor->m_resource.m_nodes.back(), editor->m_resource.m_links, hovered_link); 2952 | editor->pushUndo(NO_MERGE_UNDO); 2953 | } 2954 | return *this; 2955 | } 2956 | ShaderEditorWindow* editor; 2957 | ImVec2 pos; 2958 | i32 hovered_link; 2959 | } visitor; 2960 | visitor.editor = this; 2961 | visitor.pos = pos; 2962 | visitor.hovered_link = hovered_link; 2963 | visitNodeTypes(visitor); 2964 | } 2965 | 2966 | void onLinkDoubleClicked(ShaderEditorWindow::Link& link, ImVec2 pos) override { 2967 | ShaderEditorResource::Node* n = addNode(ShaderNodeType::PIN, pos); 2968 | ShaderEditorResource::Link new_link; 2969 | new_link.color = link.color; 2970 | new_link.from = n->m_id | OUTPUT_FLAG; 2971 | new_link.to = link.to; 2972 | link.to = n->m_id; 2973 | m_resource.m_links.push(new_link); 2974 | pushUndo(SimpleUndoRedo::NO_MERGE_UNDO); 2975 | } 2976 | 2977 | void onContextMenu(ImVec2 pos) override { 2978 | static char filter[64] = ""; 2979 | ImGui::SetNextItemWidth(150); 2980 | if (ImGui::IsWindowAppearing()) ImGui::SetKeyboardFocusHere(); 2981 | ImGui::InputTextWithHint("##filter", "Filter", filter, sizeof(filter)); 2982 | if (filter[0]) { 2983 | struct : INodeTypeVisitor { 2984 | INodeTypeVisitor& visitType(const char* label, ICreator& creator, char shortcut) override { 2985 | if (!created && findInsensitive(label, filter)) { 2986 | if (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::MenuItem(label)) { 2987 | creator.create(*editor, pos); 2988 | editor->pushUndo(NO_MERGE_UNDO); 2989 | filter[0] = '\0'; 2990 | ImGui::CloseCurrentPopup(); 2991 | created = true; 2992 | } 2993 | } 2994 | return *this; 2995 | } 2996 | bool created = false; 2997 | ImVec2 pos; 2998 | ShaderEditorWindow* editor; 2999 | } visitor; 3000 | visitor.editor = this; 3001 | visitor.pos = pos; 3002 | visitNodeTypes(visitor); 3003 | } 3004 | else { 3005 | struct : INodeTypeVisitor { 3006 | bool beginCategory(const char* name) override { return ImGui::BeginMenu(name); } 3007 | void endCategory() override { ImGui::EndMenu(); } 3008 | 3009 | INodeTypeVisitor& visitType(const char* label, ICreator& creator, char shortcut) override { 3010 | if (ImGui::MenuItem(label)) { 3011 | creator.create(*editor, pos); 3012 | editor->pushUndo(NO_MERGE_UNDO); 3013 | } 3014 | return *this; 3015 | } 3016 | 3017 | ImVec2 pos; 3018 | ShaderEditorWindow* editor; 3019 | } visitor; 3020 | visitor.editor = this; 3021 | visitor.pos = pos; 3022 | visitNodeTypes(visitor); 3023 | } 3024 | } 3025 | 3026 | void onGUIMenu() { 3027 | CommonActions& actions = m_app.getCommonActions(); 3028 | if (m_app.checkShortcut(actions.del)) deleteSelectedNodes(); 3029 | else if (m_app.checkShortcut(actions.save)) saveAs(m_resource.m_path.c_str()); 3030 | else if (m_app.checkShortcut(actions.undo)) undo(); 3031 | else if (m_app.checkShortcut(actions.redo)) redo(); 3032 | 3033 | if(ImGui::BeginMenuBar()) { 3034 | if(ImGui::BeginMenu("File")) { 3035 | ImGui::MenuItem("View source", nullptr, &m_source_open); 3036 | if (menuItem(actions.save, true)) saveAs(m_resource.m_path.c_str()); 3037 | if (ImGui::MenuItem("Save as")) m_show_save_as = true; 3038 | ImGui::EndMenu(); 3039 | } 3040 | if (ImGui::BeginMenu("Edit")) { 3041 | if (menuItem(actions.undo, canUndo())) undo(); 3042 | if (menuItem(actions.redo, canRedo())) redo(); 3043 | if (ImGui::MenuItem(ICON_FA_BRUSH "Clear")) deleteUnreachable(); 3044 | ImGui::EndMenu(); 3045 | } 3046 | if (ImGuiEx::IconButton(ICON_FA_SAVE, "Save")) saveAs(m_resource.m_path.c_str()); 3047 | if (ImGuiEx::IconButton(ICON_FA_UNDO, "Undo", canUndo())) undo(); 3048 | if (ImGuiEx::IconButton(ICON_FA_REDO, "Redo", canRedo())) redo(); 3049 | if (ImGuiEx::IconButton(ICON_FA_BRUSH, "Clear")) deleteUnreachable(); 3050 | ImGui::EndMenuBar(); 3051 | } 3052 | } 3053 | 3054 | void deleteSelectedNodes() { 3055 | if (m_is_any_item_active) return; 3056 | m_resource.deleteSelectedNodes(); 3057 | pushUndo(NO_MERGE_UNDO); 3058 | } 3059 | 3060 | void deleteUnreachable() { 3061 | m_resource.deleteUnreachable(); 3062 | pushUndo(NO_MERGE_UNDO); 3063 | } 3064 | 3065 | ShaderEditorResource::Node* addNode(ShaderNodeType node_type, ImVec2 pos) { 3066 | Node* n = m_resource.createNode((int)node_type); 3067 | n->m_id = ++m_resource.m_last_node_id; 3068 | n->m_pos = pos; 3069 | m_resource.m_nodes.push(n); 3070 | if (m_half_link_start) { 3071 | if (m_half_link_start & OUTPUT_FLAG) { 3072 | if (n->hasInputPins()) m_resource.m_links.push({u32(m_half_link_start) & ~OUTPUT_FLAG, u32(n->m_id)}); 3073 | } 3074 | else { 3075 | if (n->hasOutputPins()) m_resource.m_links.push({u32(n->m_id), u32(m_half_link_start)}); 3076 | } 3077 | m_half_link_start = 0; 3078 | } 3079 | return n; 3080 | } 3081 | 3082 | void visitNodeTypes(INodeTypeVisitor& visitor) { 3083 | if (visitor.beginCategory("Constants")) { 3084 | visitor.visitType("Time", ShaderNodeType::TIME) 3085 | .visitType("Vertex ID", ShaderNodeType::VERTEX_ID) 3086 | .visitType("View direction", ShaderNodeType::VIEW_DIR) 3087 | .endCategory(); 3088 | } 3089 | 3090 | if (visitor.beginCategory("Functions")) { 3091 | for (const auto& fn : m_editor.m_functions) { 3092 | const StaticString name(Path::getBasename(fn->m_path.c_str())); 3093 | struct : INodeTypeVisitor::ICreator { 3094 | void create(ShaderEditorWindow& editor, ImVec2 pos) override { 3095 | FunctionCallNode* node = (FunctionCallNode*)editor.addNode(ShaderNodeType::FUNCTION_CALL, pos); 3096 | node->m_function_resource = res; 3097 | }; 3098 | ShaderEditorResource* res; 3099 | } creator; 3100 | creator.res = fn.get(); 3101 | visitor.visitType(name, creator); 3102 | } 3103 | visitor.endCategory(); 3104 | } 3105 | 3106 | if (visitor.beginCategory("Math")) { 3107 | visitor.visitType("Abs", ShaderNodeType::ABS) 3108 | .visitType("All", ShaderNodeType::ALL) 3109 | .visitType("Any", ShaderNodeType::ANY) 3110 | .visitType("Ceil", ShaderNodeType::CEIL) 3111 | .visitType("Cos", ShaderNodeType::COS) 3112 | .visitType("Exp", ShaderNodeType::EXP, 'E') 3113 | .visitType("Exp2", ShaderNodeType::EXP2) 3114 | .visitType("Floor", ShaderNodeType::FLOOR) 3115 | .visitType("Fract", ShaderNodeType::FRACT) 3116 | .visitType("Log", ShaderNodeType::LOG) 3117 | .visitType("Log2", ShaderNodeType::LOG2) 3118 | .visitType("Normalize", ShaderNodeType::NORMALIZE, 'N') 3119 | .visitType("Not", ShaderNodeType::NOT) 3120 | .visitType("Round", ShaderNodeType::ROUND) 3121 | .visitType("Saturate", ShaderNodeType::SATURATE) 3122 | .visitType("Sin", ShaderNodeType::SIN) 3123 | .visitType("Sqrt", ShaderNodeType::SQRT) 3124 | .visitType("Tan", ShaderNodeType::TAN) 3125 | .visitType("Transpose", ShaderNodeType::TRANSPOSE) 3126 | .visitType("Trunc", ShaderNodeType::TRUNC) 3127 | .visitType("Cross", ShaderNodeType::CROSS) 3128 | .visitType("Distance", ShaderNodeType::DISTANCE) 3129 | .visitType("Dot", ShaderNodeType::DOT, 'D') 3130 | .visitType("Length", ShaderNodeType::LENGTH, 'L') 3131 | .visitType("Max", ShaderNodeType::MAX) 3132 | .visitType("Min", ShaderNodeType::MIN) 3133 | .visitType("Power", ShaderNodeType::POW, 'P') 3134 | .visitType("Add", ShaderNodeType::ADD, 'A') 3135 | .visitType("Append", ShaderNodeType::APPEND) 3136 | .visitType("Divide", ShaderNodeType::DIVIDE) 3137 | .visitType("Mix", ShaderNodeType::MIX, 'X') 3138 | .visitType("Multiply", ShaderNodeType::MULTIPLY, 'M') 3139 | .visitType("One minus", ShaderNodeType::ONEMINUS, 'O') 3140 | .visitType("Subtract", ShaderNodeType::SUBTRACT) 3141 | .endCategory(); 3142 | } 3143 | 3144 | if (visitor.beginCategory("Parameters")) { 3145 | visitor.visitType("Color", ShaderNodeType::COLOR_PARAM, 'C') 3146 | .visitType("Scalar", ShaderNodeType::SCALAR_PARAM, 'P') 3147 | .visitType("Vec4", ShaderNodeType::VEC4_PARAM, 'V') 3148 | .endCategory(); 3149 | } 3150 | 3151 | if (visitor.beginCategory("Utility")) { 3152 | visitor.visitType("Fresnel", ShaderNodeType::FRESNEL) 3153 | .visitType("Custom code", ShaderNodeType::CODE) 3154 | .visitType("Backface switch", ShaderNodeType::BACKFACE_SWITCH) 3155 | .visitType("If", ShaderNodeType::IF, 'I') 3156 | .visitType("Pixel depth", ShaderNodeType::PIXEL_DEPTH) 3157 | .visitType("Scene depth", ShaderNodeType::SCENE_DEPTH) 3158 | .visitType("Screen position", ShaderNodeType::SCREEN_POSITION) 3159 | .visitType("Static switch", ShaderNodeType::STATIC_SWITCH) 3160 | .visitType("Swizzle", ShaderNodeType::SWIZZLE, 'S') 3161 | .endCategory(); 3162 | } 3163 | 3164 | if (visitor.beginCategory("Vertex")) { 3165 | visitor.visitType("Normal", ShaderNodeType::NORMAL) 3166 | .visitType("Position", ShaderNodeType::POSITION) 3167 | .visitType("UV0", ShaderNodeType::UV0) 3168 | .endCategory(); 3169 | } 3170 | 3171 | switch (m_resource.getShaderType()) { 3172 | case ShaderResourceEditorType::SURFACE: break; 3173 | case ShaderResourceEditorType::FUNCTION: 3174 | visitor.visitType("Function input", ShaderNodeType::FUNCTION_INPUT); 3175 | break; 3176 | case ShaderResourceEditorType::PARTICLE: { 3177 | if (visitor.beginCategory("Particles")) { 3178 | PBRNode* o = (PBRNode*)m_resource.m_nodes[0]; 3179 | for (const String& a : o->m_attributes_names) { 3180 | struct : INodeTypeVisitor::ICreator { 3181 | void create(ShaderEditorWindow& editor, ImVec2 pos) override { 3182 | ParticleStreamNode* node = (ParticleStreamNode*)editor.addNode(ShaderNodeType::PARTICLE_STREAM, pos); 3183 | node->m_stream = stream; 3184 | } 3185 | u32 stream; 3186 | } creator; 3187 | creator.stream = u32(&a - o->m_attributes_names.begin()); 3188 | visitor.visitType(a.c_str(), creator); 3189 | } 3190 | visitor.endCategory(); 3191 | } 3192 | break; 3193 | } 3194 | } 3195 | 3196 | visitor 3197 | .visitType("Sample", ShaderNodeType::SAMPLE, 'T') 3198 | .visitType("Vector 4", ShaderNodeType::VEC4, '4') 3199 | .visitType("Vector 3", ShaderNodeType::VEC3, '3') 3200 | .visitType("Vector 2", ShaderNodeType::VEC2, '2') 3201 | .visitType("Number", ShaderNodeType::NUMBER, '1'); 3202 | } 3203 | 3204 | StudioApp& m_app; 3205 | IAllocator& m_allocator; 3206 | ShaderEditor& m_editor; 3207 | ShaderEditorResource m_resource; 3208 | String m_source; 3209 | ImGuiEx::Canvas m_canvas; 3210 | bool m_source_open = false; 3211 | bool m_show_save_as = false; 3212 | }; 3213 | 3214 | void ShaderEditor::registerDependencies(const ShaderEditorResource& res) { 3215 | for (ShaderEditorResource::Node* n : res.m_nodes) { 3216 | if (n->getType() == ShaderNodeType::FUNCTION_CALL) { 3217 | FunctionCallNode* fn = (FunctionCallNode*)n; 3218 | m_app.getAssetCompiler().registerDependency(res.m_path, fn->m_resource.m_path); 3219 | } 3220 | } 3221 | } 3222 | 3223 | void ShaderEditor::open(const Path& path) { 3224 | IAllocator& allocator = m_app.getAllocator(); 3225 | UniquePtr win = UniquePtr::create(allocator, path, *this, m_app, m_app.getAllocator()); 3226 | m_app.getAssetBrowser().addWindow(win.move()); 3227 | } 3228 | 3229 | ShaderEditorWindow::INodeTypeVisitor& ShaderEditorWindow::INodeTypeVisitor::visitType(const char* label, ShaderNodeType type, char shortcut ) { 3230 | struct : ICreator { 3231 | void create(ShaderEditorWindow& editor, ImVec2 pos) override { 3232 | editor.addNode(type, pos); 3233 | }; 3234 | ShaderNodeType type; 3235 | } creator; 3236 | creator.type = type; 3237 | return visitType(label, creator, shortcut); 3238 | } 3239 | 3240 | LUMIX_STUDIO_ENTRY(shader_editor) { 3241 | PROFILE_FUNCTION(); 3242 | return LUMIX_NEW(app.getAllocator(), ShaderEditor)(app); 3243 | } 3244 | 3245 | } 3246 | 3247 | } // namespace Lumix --------------------------------------------------------------------------------