#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { template void RegisterShaderNode(ShaderGraph& graph, std::shared_ptr registry, QString category = QString()) { auto creator = [&] { return std::make_unique(graph); }; registry->registerModel(category, std::move(creator)); } } ShaderGraph::ShaderGraph() : m_flowScene(BuildRegistry()) { m_previewModel = std::make_unique(); QObject::connect(&m_flowScene, &QGraphicsScene::selectionChanged, [&] { auto selectedNodes = m_flowScene.selectedNodes(); if (selectedNodes.size() == 1) OnSelectedNodeUpdate(this, static_cast(selectedNodes.front()->nodeDataModel())); else OnSelectedNodeUpdate(this, nullptr); }); // Test AddInput("UV", PrimitiveType::Float2, InputRole::TexCoord, 0, 0); AddOutput("RenderTarget0", PrimitiveType::Float4, 0); AddTexture("Potato", TextureType::Sampler2D, 1); AddStruct("TestStruct", { { { "position", PrimitiveType::Float3 }, { "normal", PrimitiveType::Float3 }, { "uv", PrimitiveType::Float2 }, } }); AddStruct("TestStruct2", { { { "position", PrimitiveType::Float3 }, { "normal", PrimitiveType::Float3 }, { "uv", PrimitiveType::Float2 }, } }); UpdateTexturePreview(0, QImage(R"(C:\Users\Lynix\Pictures\potatavril.png)")); auto& node1 = m_flowScene.createNode(std::make_unique(*this)); node1.nodeGraphicsObject().setPos(0, 200); auto& node2 = m_flowScene.createNode(std::make_unique(*this)); node2.nodeGraphicsObject().setPos(50, 350); auto& node3 = m_flowScene.createNode(std::make_unique(*this)); node3.nodeGraphicsObject().setPos(200, 200); auto& node4 = m_flowScene.createNode(std::make_unique(*this)); node4.nodeGraphicsObject().setPos(400, 200); auto& node5 = m_flowScene.createNode(std::make_unique(*this)); node5.nodeGraphicsObject().setPos(600, 300); m_flowScene.createConnection(node3, 0, node1, 0); m_flowScene.createConnection(node3, 1, node2, 0); m_flowScene.createConnection(node4, 0, node3, 0); m_flowScene.createConnection(node4, 1, node3, 0); m_flowScene.createConnection(node5, 0, node4, 0); } ShaderGraph::~ShaderGraph() { m_flowScene.clearScene(); } std::size_t ShaderGraph::AddBuffer(std::string name, BufferType bufferType, std::size_t structIndex, std::size_t bindingIndex) { std::size_t index = m_buffers.size(); auto& bufferEntry = m_buffers.emplace_back(); bufferEntry.bindingIndex = bindingIndex; bufferEntry.name = std::move(name); bufferEntry.structIndex = structIndex; bufferEntry.type = bufferType; OnBufferListUpdate(this); return index; } std::size_t ShaderGraph::AddInput(std::string name, PrimitiveType type, InputRole role, std::size_t roleIndex, std::size_t locationIndex) { std::size_t index = m_inputs.size(); auto& inputEntry = m_inputs.emplace_back(); inputEntry.locationIndex = locationIndex; inputEntry.name = std::move(name); inputEntry.role = role; inputEntry.roleIndex = roleIndex; inputEntry.type = type; OnInputListUpdate(this); return index; } std::size_t ShaderGraph::AddOutput(std::string name, PrimitiveType type, std::size_t locationIndex) { std::size_t index = m_outputs.size(); auto& outputEntry = m_outputs.emplace_back(); outputEntry.locationIndex = locationIndex; outputEntry.name = std::move(name); outputEntry.type = type; OnOutputListUpdate(this); return index; } std::size_t ShaderGraph::AddStruct(std::string name, std::vector members) { std::size_t index = m_structs.size(); auto& structEntry = m_structs.emplace_back(); structEntry.name = std::move(name); structEntry.members = std::move(members); OnStructListUpdate(this); return index; } std::size_t ShaderGraph::AddTexture(std::string name, TextureType type, std::size_t bindingIndex) { std::size_t index = m_textures.size(); auto& textureEntry = m_textures.emplace_back(); textureEntry.bindingIndex = bindingIndex; textureEntry.name = std::move(name); textureEntry.type = type; OnTextureListUpdate(this); return index; } void ShaderGraph::Clear() { m_flowScene.clearScene(); m_flowScene.clear(); m_buffers.clear(); m_inputs.clear(); m_structs.clear(); m_outputs.clear(); m_textures.clear(); OnBufferListUpdate(this); OnInputListUpdate(this); OnStructListUpdate(this); OnOutputListUpdate(this); OnTextureListUpdate(this); } void ShaderGraph::Load(const QJsonObject& data) { Clear(); QJsonArray bufferArray = data["buffers"].toArray(); for (const auto& bufferDocRef : bufferArray) { QJsonObject bufferDoc = bufferDocRef.toObject(); BufferEntry& buffer = m_buffers.emplace_back(); buffer.bindingIndex = static_cast(bufferDoc["bindingIndex"].toInt(0)); buffer.name = bufferDoc["name"].toString().toStdString(); buffer.structIndex = bufferDoc["structIndex"].toInt(); buffer.type = DecodeEnum(bufferDoc["type"].toString().toStdString()).value(); } OnBufferListUpdate(this); QJsonArray inputArray = data["inputs"].toArray(); for (const auto& inputDocRef : inputArray) { QJsonObject inputDoc = inputDocRef.toObject(); InputEntry& input = m_inputs.emplace_back(); input.locationIndex = static_cast(inputDoc["locationIndex"].toInt(0)); input.name = inputDoc["name"].toString().toStdString(); input.role = DecodeEnum(inputDoc["role"].toString().toStdString()).value(); input.roleIndex = static_cast(inputDoc["roleIndex"].toInt(0)); input.type = DecodeEnum(inputDoc["type"].toString().toStdString()).value(); } OnInputListUpdate(this); QJsonArray outputArray = data["outputs"].toArray(); for (const auto& outputDocRef : outputArray) { QJsonObject outputDoc = outputDocRef.toObject(); OutputEntry& output = m_outputs.emplace_back(); output.locationIndex = static_cast(outputDoc["locationIndex"].toInt(0)); output.name = outputDoc["name"].toString().toStdString(); output.type = DecodeEnum(outputDoc["type"].toString().toStdString()).value(); } OnOutputListUpdate(this); QJsonArray structArray = data["structs"].toArray(); for (const auto& structDocRef : structArray) { QJsonObject structDoc = structDocRef.toObject(); StructEntry& structInfo = m_structs.emplace_back(); structInfo.name = structDoc["name"].toString().toStdString(); QJsonArray memberArray = structDoc["members"].toArray(); for (const auto& memberDocRef : memberArray) { QJsonObject memberDoc = memberDocRef.toObject(); auto& memberInfo = structInfo.members.emplace_back(); memberInfo.name = memberDoc["name"].toString().toStdString(); const auto& typeDocRef = memberDoc["type"]; if (typeDocRef.isString()) memberInfo.type = DecodeEnum(typeDocRef.toString().toStdString()).value(); else memberInfo.type = typeDocRef.toInt(); } } OnStructListUpdate(this); QJsonArray textureArray = data["textures"].toArray(); for (const auto& textureDocRef : textureArray) { QJsonObject textureDoc = textureDocRef.toObject(); TextureEntry& texture = m_textures.emplace_back(); texture.bindingIndex = static_cast(textureDoc["bindingIndex"].toInt(0)); texture.name = textureDoc["name"].toString().toStdString(); texture.type = DecodeEnum(textureDoc["type"].toString().toStdString()).value(); } OnTextureListUpdate(this); for (QJsonValueRef node : data["nodes"].toArray()) m_flowScene.restoreNode(node.toObject()); for (QJsonValueRef connection : data["connections"].toArray()) m_flowScene.restoreConnection(connection.toObject()); } QJsonObject ShaderGraph::Save() { QJsonObject sceneJson; QJsonArray bufferArray; { for (const auto& buffer : m_buffers) { QJsonObject bufferDoc; bufferDoc["bindingIndex"] = int(buffer.bindingIndex); bufferDoc["name"] = QString::fromStdString(buffer.name); bufferDoc["structIndex"] = int(buffer.structIndex); bufferDoc["type"] = QString(EnumToString(buffer.type)); bufferArray.append(bufferDoc); } } sceneJson["buffers"] = bufferArray; QJsonArray inputArray; { for (const auto& input : m_inputs) { QJsonObject inputDoc; inputDoc["locationIndex"] = int(input.locationIndex); inputDoc["name"] = QString::fromStdString(input.name); inputDoc["role"] = QString(EnumToString(input.role)); inputDoc["roleIndex"] = int(input.roleIndex); inputDoc["type"] = QString(EnumToString(input.type)); inputArray.append(inputDoc); } } sceneJson["inputs"] = inputArray; QJsonArray outputArray; { for (const auto& output : m_outputs) { QJsonObject outputDoc; outputDoc["locationIndex"] = int(output.locationIndex); outputDoc["name"] = QString::fromStdString(output.name); outputDoc["type"] = QString(EnumToString(output.type)); outputArray.append(outputDoc); } } sceneJson["outputs"] = outputArray; QJsonArray structArray; { for (const auto& s : m_structs) { QJsonObject structDoc; structDoc["name"] = QString::fromStdString(s.name); QJsonArray memberArray; for (const auto& member : s.members) { QJsonObject memberDoc; memberDoc["name"] = QString::fromStdString(member.name); std::visit([&](auto&& arg) { using T = std::decay_t; if constexpr (std::is_same_v) memberDoc["type"] = QString(EnumToString(arg)); else if constexpr (std::is_same_v) memberDoc["type"] = int(arg); else static_assert(AlwaysFalse::value, "non-exhaustive visitor"); }, member.type); memberDoc["type"] = QString::fromStdString(member.name); } structDoc["members"] = memberArray; structArray.append(structDoc); } } sceneJson["structs"] = structArray; QJsonArray textureArray; { for (const auto& texture : m_textures) { QJsonObject textureDoc; textureDoc["bindingIndex"] = int(texture.bindingIndex); textureDoc["name"] = QString::fromStdString(texture.name); textureDoc["type"] = QString(EnumToString(texture.type)); textureArray.append(textureDoc); } } sceneJson["textures"] = textureArray; QJsonArray nodesJsonArray; { for (auto&& [uuid, node] : m_flowScene.nodes()) nodesJsonArray.append(node->save()); } sceneJson["nodes"] = nodesJsonArray; QJsonArray connectionJsonArray; { for (auto&& [uuid, connection] : m_flowScene.connections()) { QJsonObject connectionJson = connection->save(); if (!connectionJson.isEmpty()) connectionJsonArray.append(connectionJson); } } sceneJson["connections"] = connectionJsonArray; return sceneJson; } Nz::ShaderNodes::StatementPtr ShaderGraph::ToAst() { std::vector statements; QHash usageCount; std::function DetectVariables; DetectVariables = [&](QtNodes::Node* node) { auto it = usageCount.find(node->id()); if (it == usageCount.end()) { for (const auto& connectionSet : node->nodeState().getEntries(QtNodes::PortType::In)) { for (const auto& [uuid, conn] : connectionSet) { DetectVariables(conn->getNode(QtNodes::PortType::Out)); } } it = usageCount.insert(node->id(), 0); } (*it)++; }; m_flowScene.iterateOverNodes([&](QtNodes::Node* node) { if (node->nodeDataModel()->nPorts(QtNodes::PortType::Out) == 0) DetectVariables(node); }); QHash variableExpressions; unsigned int varCount = 0; std::unordered_set usedVariableNames; std::function HandleNode; HandleNode = [&](QtNodes::Node* node) -> Nz::ShaderNodes::ExpressionPtr { ShaderNode* shaderNode = static_cast(node->nodeDataModel()); if (shaderNode->validationState() != QtNodes::NodeValidationState::Valid) throw std::runtime_error(shaderNode->validationMessage().toStdString()); qDebug() << shaderNode->name() << node->id(); if (auto it = variableExpressions.find(node->id()); it != variableExpressions.end()) return *it; auto it = usageCount.find(node->id()); assert(it != usageCount.end()); std::size_t inputCount = shaderNode->nPorts(QtNodes::PortType::In); Nz::StackArray expressions = NazaraStackArray(Nz::ShaderNodes::ExpressionPtr, inputCount); std::size_t i = 0; for (const auto& connectionSet : node->nodeState().getEntries(QtNodes::PortType::In)) { for (const auto& [uuid, conn] : connectionSet) { assert(i < expressions.size()); expressions[i] = HandleNode(conn->getNode(QtNodes::PortType::Out)); i++; } } auto expression = shaderNode->GetExpression(expressions.data(), expressions.size()); const std::string& variableName = shaderNode->GetVariableName(); if (*it > 1 || !variableName.empty()) { Nz::ShaderNodes::ExpressionPtr varExpression; if (expression->GetExpressionCategory() == Nz::ShaderNodes::ExpressionCategory::RValue) { std::string name; if (variableName.empty()) name = "var" + std::to_string(varCount++); else name = variableName; if (usedVariableNames.find(name) != usedVariableNames.end()) throw std::runtime_error("duplicate variable found: " + name); usedVariableNames.insert(name); auto variable = Nz::ShaderBuilder::Local(std::move(name), expression->GetExpressionType()); statements.emplace_back(Nz::ShaderBuilder::DeclareVariable(variable, expression)); varExpression = Nz::ShaderBuilder::Identifier(variable); } else varExpression = expression; variableExpressions.insert(node->id(), varExpression); return varExpression; } else return expression; }; m_flowScene.iterateOverNodes([&](QtNodes::Node* node) { if (node->nodeDataModel()->nPorts(QtNodes::PortType::Out) == 0) { statements.emplace_back(Nz::ShaderBuilder::ExprStatement(HandleNode(node))); } }); return Nz::ShaderNodes::StatementBlock::Build(std::move(statements)); } Nz::ShaderExpressionType ShaderGraph::ToShaderExpressionType(const std::variant& type) const { return std::visit([&](auto&& arg) -> Nz::ShaderExpressionType { using T = std::decay_t; if constexpr (std::is_same_v) return ToShaderExpressionType(arg); else if constexpr (std::is_same_v) { assert(arg < m_structs.size()); const auto& s = m_structs[arg]; return s.name; } else static_assert(AlwaysFalse::value, "non-exhaustive visitor"); }, type); }; void ShaderGraph::UpdateBuffer(std::size_t bufferIndex, std::string name, BufferType bufferType, std::size_t structIndex, std::size_t bindingIndex) { assert(bufferIndex < m_buffers.size()); auto& bufferEntry = m_buffers[bufferIndex]; bufferEntry.bindingIndex = bindingIndex; bufferEntry.name = std::move(name); bufferEntry.structIndex = structIndex; bufferEntry.type = bufferType; OnBufferUpdate(this, bufferIndex); } void ShaderGraph::UpdateInput(std::size_t inputIndex, std::string name, PrimitiveType type, InputRole role, std::size_t roleIndex, std::size_t locationIndex) { assert(inputIndex < m_inputs.size()); auto& inputEntry = m_inputs[inputIndex]; inputEntry.locationIndex = locationIndex; inputEntry.name = std::move(name); inputEntry.role = role; inputEntry.roleIndex = roleIndex; inputEntry.type = type; OnInputUpdate(this, inputIndex); } void ShaderGraph::UpdateOutput(std::size_t outputIndex, std::string name, PrimitiveType type, std::size_t locationIndex) { assert(outputIndex < m_outputs.size()); auto& outputEntry = m_outputs[outputIndex]; outputEntry.locationIndex = locationIndex; outputEntry.name = std::move(name); outputEntry.type = type; OnOutputUpdate(this, outputIndex); } void ShaderGraph::UpdateStruct(std::size_t structIndex, std::string name, std::vector members) { assert(structIndex < m_structs.size()); auto& structEntry = m_structs[structIndex]; structEntry.name = std::move(name); structEntry.members = std::move(members); OnStructUpdate(this, structIndex); } void ShaderGraph::UpdateTexture(std::size_t textureIndex, std::string name, TextureType type, std::size_t bindingIndex) { assert(textureIndex < m_textures.size()); auto& textureEntry = m_textures[textureIndex]; textureEntry.bindingIndex = bindingIndex; textureEntry.name = std::move(name); textureEntry.type = type; OnTextureUpdate(this, textureIndex); } void ShaderGraph::UpdateTexturePreview(std::size_t textureIndex, QImage preview) { assert(textureIndex < m_textures.size()); auto& textureEntry = m_textures[textureIndex]; textureEntry.preview = std::move(preview); textureEntry.preview.convertTo(QImage::Format_RGBA8888); OnTexturePreviewUpdate(this, textureIndex); } Nz::ShaderExpressionType ShaderGraph::ToShaderExpressionType(PrimitiveType type) { switch (type) { case PrimitiveType::Bool: return Nz::ShaderNodes::BasicType::Boolean; case PrimitiveType::Float1: return Nz::ShaderNodes::BasicType::Float1; case PrimitiveType::Float2: return Nz::ShaderNodes::BasicType::Float2; case PrimitiveType::Float3: return Nz::ShaderNodes::BasicType::Float3; case PrimitiveType::Float4: return Nz::ShaderNodes::BasicType::Float4; } assert(false); throw std::runtime_error("Unhandled primitive type"); } Nz::ShaderExpressionType ShaderGraph::ToShaderExpressionType(TextureType type) { switch (type) { case TextureType::Sampler2D: return Nz::ShaderNodes::BasicType::Sampler2D; } assert(false); throw std::runtime_error("Unhandled texture type"); } std::shared_ptr ShaderGraph::BuildRegistry() { auto registry = std::make_shared(); RegisterShaderNode(*this, registry, "Inputs"); RegisterShaderNode(*this, registry, "Casts"); RegisterShaderNode(*this, registry, "Casts"); RegisterShaderNode(*this, registry, "Casts"); RegisterShaderNode(*this, registry, "Constants"); RegisterShaderNode(*this, registry, "Inputs"); RegisterShaderNode(*this, registry, "Outputs"); RegisterShaderNode(*this, registry, "Texture"); RegisterShaderNode(*this, registry, "Texture"); RegisterShaderNode(*this, registry, "Vector operations"); RegisterShaderNode(*this, registry, "Vector operations"); RegisterShaderNode(*this, registry, "Vector operations"); RegisterShaderNode(*this, registry, "Vector operations"); RegisterShaderNode(*this, registry, "Vector operations"); RegisterShaderNode(*this, registry, "Vector operations"); RegisterShaderNode(*this, registry, "Constants"); RegisterShaderNode(*this, registry, "Constants"); RegisterShaderNode(*this, registry, "Constants"); return registry; }