ShaderNode: Add save/load
This commit is contained in:
parent
5790b502f7
commit
0888589716
|
|
@ -1,5 +1,6 @@
|
|||
#include <ShaderNode/ShaderGraph.hpp>
|
||||
#include <ShaderNode/DataModels/InputValue.hpp>
|
||||
#include <ShaderNode/DataTypes/FloatData.hpp>
|
||||
#include <ShaderNode/DataTypes/VecData.hpp>
|
||||
#include <Nazara/Renderer/ShaderBuilder.hpp>
|
||||
#include <QtWidgets/QFormLayout>
|
||||
|
|
@ -11,7 +12,10 @@ ShaderNode(graph)
|
|||
m_onInputUpdateSlot.Connect(GetGraph().OnInputUpdate, [&](ShaderGraph*, std::size_t inputIndex)
|
||||
{
|
||||
if (m_currentInputIndex == inputIndex)
|
||||
{
|
||||
UpdatePreview();
|
||||
Q_EMIT dataUpdated(0);
|
||||
}
|
||||
});
|
||||
|
||||
if (graph.GetInputCount() > 0)
|
||||
|
|
@ -172,3 +176,19 @@ QString InputValue::validationMessage() const
|
|||
|
||||
return QString();
|
||||
}
|
||||
|
||||
void InputValue::restore(const QJsonObject& data)
|
||||
{
|
||||
m_currentInputText = data["input"].toString().toStdString();
|
||||
OnInputListUpdate();
|
||||
|
||||
ShaderNode::restore(data);
|
||||
}
|
||||
|
||||
QJsonObject InputValue::save() const
|
||||
{
|
||||
QJsonObject data = ShaderNode::save();
|
||||
data["input"] = QString::fromStdString(m_currentInputText);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ class InputValue : public ShaderNode
|
|||
bool ComputePreview(QPixmap& pixmap) override;
|
||||
void OnInputListUpdate();
|
||||
|
||||
void restore(const QJsonObject& data) override;
|
||||
QJsonObject save() const override;
|
||||
|
||||
NazaraSlot(ShaderGraph, OnInputListUpdate, m_onInputListUpdateSlot);
|
||||
NazaraSlot(ShaderGraph, OnInputUpdate, m_onInputUpdateSlot);
|
||||
|
||||
|
|
|
|||
|
|
@ -188,3 +188,19 @@ void OutputValue::OnOutputListUpdate()
|
|||
inputIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
void OutputValue::restore(const QJsonObject& data)
|
||||
{
|
||||
m_currentOutputText = data["input"].toString().toStdString();
|
||||
OnOutputListUpdate();
|
||||
|
||||
ShaderNode::restore(data);
|
||||
}
|
||||
|
||||
QJsonObject OutputValue::save() const
|
||||
{
|
||||
QJsonObject data = ShaderNode::save();
|
||||
data["input"] = QString::fromStdString(m_currentOutputText);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ class OutputValue : public ShaderNode
|
|||
bool ComputePreview(QPixmap& pixmap) override;
|
||||
void OnOutputListUpdate();
|
||||
|
||||
void restore(const QJsonObject& data) override;
|
||||
QJsonObject save() const override;
|
||||
|
||||
NazaraSlot(ShaderGraph, OnOutputListUpdate, m_onOutputListUpdateSlot);
|
||||
NazaraSlot(ShaderGraph, OnOutputUpdate, m_onOutputUpdateSlot);
|
||||
|
||||
|
|
|
|||
|
|
@ -54,14 +54,25 @@ void SampleTexture::UpdateOutput()
|
|||
float u = float(uvPtr[0]) / 255;
|
||||
float v = float(uvPtr[1]) / 255;
|
||||
|
||||
int texX = std::clamp(int(u * textureWidth), 0, textureWidth - 1);
|
||||
int texY = std::clamp(int(v * textureHeight), 0, textureHeight - 1);
|
||||
int texPixel = (texY * textureWidth + texX) * 4;
|
||||
if (textureWidth > 0 && textureHeight > 0)
|
||||
{
|
||||
int texX = std::clamp(int(u * textureWidth), 0, textureWidth - 1);
|
||||
int texY = std::clamp(int(v * textureHeight), 0, textureHeight - 1);
|
||||
int texPixel = (texY * textureWidth + texX) * 4;
|
||||
|
||||
*outputPtr++ = texturePtr[texPixel + 0];
|
||||
*outputPtr++ = texturePtr[texPixel + 1];
|
||||
*outputPtr++ = texturePtr[texPixel + 2];
|
||||
*outputPtr++ = texturePtr[texPixel + 3];
|
||||
}
|
||||
else
|
||||
{
|
||||
*outputPtr++ = 0;
|
||||
*outputPtr++ = 0;
|
||||
*outputPtr++ = 0;
|
||||
*outputPtr++ = 0xFF;
|
||||
}
|
||||
|
||||
*outputPtr++ = texturePtr[texPixel + 0];
|
||||
*outputPtr++ = texturePtr[texPixel + 1];
|
||||
*outputPtr++ = texturePtr[texPixel + 2];
|
||||
*outputPtr++ = texturePtr[texPixel + 3];
|
||||
uvPtr += 4;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,8 +81,6 @@ void ShaderNode::EnablePreview(bool enable)
|
|||
m_pixmapLabel->clear();
|
||||
m_pixmap.reset();
|
||||
}
|
||||
|
||||
embeddedWidgetSizeUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,6 +93,29 @@ void ShaderNode::setInData(std::shared_ptr<QtNodes::NodeData>, int)
|
|||
{
|
||||
}
|
||||
|
||||
void ShaderNode::restore(const QJsonObject& data)
|
||||
{
|
||||
NodeDataModel::restore(data);
|
||||
|
||||
bool isPreviewEnabled = data["preview_enabled"].toBool(m_isPreviewEnabled);
|
||||
m_previewSize.x = data["preview_width"].toInt(m_previewSize.x);
|
||||
m_previewSize.y = data["preview_height"].toInt(m_previewSize.y);
|
||||
m_variableName = data["variable_name"].toString().toStdString();
|
||||
|
||||
EnablePreview(isPreviewEnabled);
|
||||
}
|
||||
|
||||
QJsonObject ShaderNode::save() const
|
||||
{
|
||||
QJsonObject data = NodeDataModel::save();
|
||||
data["preview_enabled"] = m_isPreviewEnabled;
|
||||
data["preview_width"] = m_previewSize.x;
|
||||
data["preview_height"] = m_previewSize.y;
|
||||
data["variable_name"] = QString::fromStdString(m_variableName);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
bool ShaderNode::ComputePreview(QPixmap& /*pixmap*/)
|
||||
{
|
||||
return false;
|
||||
|
|
@ -103,7 +124,10 @@ bool ShaderNode::ComputePreview(QPixmap& /*pixmap*/)
|
|||
void ShaderNode::UpdatePreview()
|
||||
{
|
||||
if (!m_pixmap)
|
||||
{
|
||||
embeddedWidgetSizeUpdated();
|
||||
return;
|
||||
}
|
||||
|
||||
QPixmap& pixmap = *m_pixmap;
|
||||
|
||||
|
|
@ -116,4 +140,6 @@ void ShaderNode::UpdatePreview()
|
|||
pixmap = pixmap.scaled(m_previewSize.x, m_previewSize.y);
|
||||
|
||||
m_pixmapLabel->setPixmap(pixmap);
|
||||
|
||||
embeddedWidgetSizeUpdated();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ class ShaderNode : public QtNodes::NodeDataModel
|
|||
inline void EnableCustomVariableName(bool enable = true);
|
||||
void UpdatePreview();
|
||||
|
||||
void restore(const QJsonObject& data) override;
|
||||
QJsonObject save() const override;
|
||||
|
||||
private:
|
||||
virtual bool ComputePreview(QPixmap& pixmap);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ ShaderNode(graph)
|
|||
m_onTexturePreviewUpdateSlot.Connect(GetGraph().OnTexturePreviewUpdate, [&](ShaderGraph*, std::size_t textureIndex)
|
||||
{
|
||||
if (m_currentTextureIndex == textureIndex)
|
||||
{
|
||||
UpdatePreview();
|
||||
Q_EMIT dataUpdated(0);
|
||||
}
|
||||
});
|
||||
|
||||
if (graph.GetTextureCount() > 0)
|
||||
|
|
@ -161,3 +164,19 @@ QString TextureValue::validationMessage() const
|
|||
|
||||
return QString();
|
||||
}
|
||||
|
||||
void TextureValue::restore(const QJsonObject& data)
|
||||
{
|
||||
m_currentTextureText = data["texture"].toString().toStdString();
|
||||
OnTextureListUpdate();
|
||||
|
||||
ShaderNode::restore(data);
|
||||
}
|
||||
|
||||
QJsonObject TextureValue::save() const
|
||||
{
|
||||
QJsonObject data = ShaderNode::save();
|
||||
data["texture"] = QString::fromStdString(m_currentTextureText);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ class TextureValue : public ShaderNode
|
|||
bool ComputePreview(QPixmap& pixmap) override;
|
||||
void OnTextureListUpdate();
|
||||
|
||||
void restore(const QJsonObject& data) override;
|
||||
QJsonObject save() const override;
|
||||
|
||||
NazaraSlot(ShaderGraph, OnTextureListUpdate, m_onTextureListUpdateSlot);
|
||||
NazaraSlot(ShaderGraph, OnTexturePreviewUpdate, m_onTexturePreviewUpdateSlot);
|
||||
|
||||
|
|
|
|||
|
|
@ -33,9 +33,11 @@ class VecValue : public ShaderNode
|
|||
|
||||
private:
|
||||
bool ComputePreview(QPixmap& pixmap) override;
|
||||
|
||||
QColor ToColor() const;
|
||||
|
||||
void restore(const QJsonObject& data) override;
|
||||
QJsonObject save() const override;
|
||||
|
||||
VecType<ComponentCount> m_value;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include <Nazara/Core/Algorithm.hpp>
|
||||
#include <Nazara/Renderer/ShaderBuilder.hpp>
|
||||
#include <ShaderNode/DataTypes/VecData.hpp>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <array>
|
||||
#include <tuple>
|
||||
|
||||
|
|
@ -116,3 +117,27 @@ QColor VecValue<ComponentCount>::ToColor() const
|
|||
|
||||
return QColor::fromRgbF(values[0], values[1], values[2], values[3]);
|
||||
}
|
||||
|
||||
template<std::size_t ComponentCount>
|
||||
void VecValue<ComponentCount>::restore(const QJsonObject& data)
|
||||
{
|
||||
QJsonArray vecValues = data["value"].toArray();
|
||||
for (std::size_t i = 0; i < ComponentCount; ++i)
|
||||
m_value[i] = vecValues[int(i)].toInt(m_value[i]);
|
||||
|
||||
ShaderNode::restore(data);
|
||||
}
|
||||
|
||||
template<std::size_t ComponentCount>
|
||||
QJsonObject VecValue<ComponentCount>::save() const
|
||||
{
|
||||
QJsonObject data = ShaderNode::save();
|
||||
|
||||
QJsonArray vecValues;
|
||||
for (std::size_t i = 0; i < ComponentCount; ++i)
|
||||
vecValues.push_back(m_value[i]);
|
||||
|
||||
data["value"] = vecValues;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
#define NAZARA_SHADERNODES_ENUMS_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
enum class InputRole
|
||||
{
|
||||
|
|
@ -40,10 +42,11 @@ enum class TextureType
|
|||
constexpr std::size_t TextureTypeCount = static_cast<std::size_t>(TextureType::Max) + 1;
|
||||
|
||||
|
||||
std::size_t GetComponentCount(InOutType type);
|
||||
template<typename T> std::optional<T> DecodeEnum(const std::string_view& str);
|
||||
const char* EnumToString(InputRole role);
|
||||
const char* EnumToString(InOutType input);
|
||||
const char* EnumToString(TextureType textureType);
|
||||
std::size_t GetComponentCount(InOutType type);
|
||||
|
||||
#include <ShaderNode/Enums.inl>
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1,15 @@
|
|||
#include <ShaderNode/Enums.hpp>
|
||||
|
||||
template<typename T>
|
||||
std::optional<T> DecodeEnum(const std::string_view& str)
|
||||
{
|
||||
constexpr std::size_t ValueCount = static_cast<std::size_t>(T::Max) + 1;
|
||||
for (std::size_t i = 0; i < ValueCount; ++i)
|
||||
{
|
||||
T value = static_cast<T>(i);
|
||||
if (str == EnumToString(value))
|
||||
return value;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include <ShaderNode/ShaderGraph.hpp>
|
||||
#include <Nazara/Core/StackArray.hpp>
|
||||
#include <ShaderNode/DataModels/Cast.hpp>
|
||||
#include <ShaderNode/DataModels/FloatValue.hpp>
|
||||
#include <ShaderNode/DataModels/InputValue.hpp>
|
||||
#include <ShaderNode/DataModels/OutputValue.hpp>
|
||||
#include <ShaderNode/DataModels/SampleTexture.hpp>
|
||||
|
|
@ -114,6 +115,136 @@ std::size_t ShaderGraph::AddTexture(std::string name, TextureType type)
|
|||
return index;
|
||||
}
|
||||
|
||||
void ShaderGraph::Clear()
|
||||
{
|
||||
m_flowScene.clearScene();
|
||||
m_flowScene.clear();
|
||||
|
||||
m_inputs.clear();
|
||||
m_outputs.clear();
|
||||
m_textures.clear();
|
||||
|
||||
OnInputListUpdate(this);
|
||||
OnOutputListUpdate(this);
|
||||
OnTextureListUpdate(this);
|
||||
}
|
||||
|
||||
void ShaderGraph::Load(const QJsonObject& data)
|
||||
{
|
||||
Clear();
|
||||
|
||||
QJsonArray inputArray = data["inputs"].toArray();
|
||||
for (const auto& inputDocRef : inputArray)
|
||||
{
|
||||
QJsonObject inputDoc = inputDocRef.toObject();
|
||||
|
||||
InputEntry& input = m_inputs.emplace_back();
|
||||
input.name = inputDoc["name"].toString().toStdString();
|
||||
input.role = DecodeEnum<InputRole>(inputDoc["role"].toString().toStdString()).value();
|
||||
input.roleIndex = static_cast<std::size_t>(inputDoc["roleIndex"].toInt(0));
|
||||
input.type = DecodeEnum<InOutType>(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.name = outputDoc["name"].toString().toStdString();
|
||||
output.type = DecodeEnum<InOutType>(outputDoc["type"].toString().toStdString()).value();
|
||||
}
|
||||
|
||||
OnOutputListUpdate(this);
|
||||
|
||||
QJsonArray textureArray = data["textures"].toArray();
|
||||
for (const auto& textureDocRef : textureArray)
|
||||
{
|
||||
QJsonObject textureDoc = textureDocRef.toObject();
|
||||
|
||||
TextureEntry& texture = m_textures.emplace_back();
|
||||
texture.name = textureDoc["name"].toString().toStdString();
|
||||
texture.type = DecodeEnum<TextureType>(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 inputArray;
|
||||
{
|
||||
for (const auto& input : m_inputs)
|
||||
{
|
||||
QJsonObject inputDoc;
|
||||
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["name"] = QString::fromStdString(output.name);
|
||||
outputDoc["type"] = QString(EnumToString(output.type));
|
||||
|
||||
outputArray.append(outputDoc);
|
||||
}
|
||||
}
|
||||
sceneJson["outputs"] = outputArray;
|
||||
|
||||
QJsonArray textureArray;
|
||||
{
|
||||
for (const auto& texture : m_textures)
|
||||
{
|
||||
QJsonObject textureDoc;
|
||||
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::ShaderAst::StatementPtr ShaderGraph::ToAst()
|
||||
{
|
||||
std::vector<Nz::ShaderAst::StatementPtr> statements;
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ class ShaderGraph
|
|||
std::size_t AddOutput(std::string name, InOutType type);
|
||||
std::size_t AddTexture(std::string name, TextureType type);
|
||||
|
||||
void Clear();
|
||||
|
||||
inline const InputEntry& GetInput(std::size_t inputIndex) const;
|
||||
inline std::size_t GetInputCount() const;
|
||||
inline const std::vector<InputEntry>& GetInputs() const;
|
||||
|
|
@ -39,6 +41,9 @@ class ShaderGraph
|
|||
inline std::size_t GetTextureCount() const;
|
||||
inline const std::vector<TextureEntry>& GetTextures() const;
|
||||
|
||||
void Load(const QJsonObject& data);
|
||||
QJsonObject Save();
|
||||
|
||||
Nz::ShaderAst::StatementPtr ToAst();
|
||||
|
||||
void UpdateInput(std::size_t inputIndex, std::string name, InOutType type, InputRole role, std::size_t roleIndex);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@
|
|||
#include <ShaderNode/Widgets/NodeEditor.hpp>
|
||||
#include <ShaderNode/Widgets/TextureEditor.hpp>
|
||||
#include <nodes/FlowView>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtWidgets/QDockWidget>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <QtWidgets/QMenuBar>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QTextEdit>
|
||||
|
|
@ -77,7 +80,19 @@ m_shaderGraph(graph)
|
|||
|
||||
void MainWindow::BuildMenu()
|
||||
{
|
||||
QMenu* compileMenu = menuBar()->addMenu(tr("&Compilation"));
|
||||
QMenuBar* menu = menuBar();
|
||||
|
||||
QMenu* shader = menu->addMenu(tr("&Shader"));
|
||||
{
|
||||
QtNodes::FlowScene* scene = &m_shaderGraph.GetScene();
|
||||
|
||||
QAction* loadShader = shader->addAction(tr("Load..."));
|
||||
QObject::connect(loadShader, &QAction::triggered, this, &MainWindow::OnLoad);
|
||||
QAction* saveShader = shader->addAction(tr("Save..."));
|
||||
QObject::connect(saveShader, &QAction::triggered, this, &MainWindow::OnSave);
|
||||
}
|
||||
|
||||
QMenu* compileMenu = menu->addMenu(tr("&Compilation"));
|
||||
QAction* compileToGlsl = compileMenu->addAction(tr("GLSL"));
|
||||
connect(compileToGlsl, &QAction::triggered, [&](bool) { OnCompileToGLSL(); });
|
||||
}
|
||||
|
|
@ -103,3 +118,48 @@ void MainWindow::OnCompileToGLSL()
|
|||
QMessageBox::critical(this, tr("Compilation failed"), QString("Compilation failed: ") + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::OnLoad()
|
||||
{
|
||||
QString fileName = QFileDialog::getOpenFileName(this, tr("Open shader flow"), QDir::homePath(), tr("Shader Flow Files (*.shaderflow)"));
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to open file"), QString("Failed to open shader flow file: ") + file.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject jsonDocument = QJsonDocument::fromJson(file.readAll()).object();
|
||||
if (jsonDocument.isEmpty())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Invalid file"), tr("Invalid shader flow file"));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
m_shaderGraph.Load(jsonDocument);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Invalid file"), tr("Invalid shader flow file: ") + e.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::OnSave()
|
||||
{
|
||||
QString fileName = QFileDialog::getSaveFileName(nullptr, tr("Open shader flow"), QDir::homePath(), tr("Shader Flow Files (*.shaderflow)"));
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
if (!fileName.endsWith("flow", Qt::CaseInsensitive))
|
||||
fileName += ".shaderflow";
|
||||
|
||||
QFile file(fileName);
|
||||
if (file.open(QIODevice::WriteOnly))
|
||||
file.write(QJsonDocument(m_shaderGraph.Save()).toJson());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ class MainWindow : public QMainWindow
|
|||
private:
|
||||
void BuildMenu();
|
||||
void OnCompileToGLSL();
|
||||
void OnLoad();
|
||||
void OnSave();
|
||||
|
||||
NazaraSlot(ShaderGraph, OnSelectedNodeUpdate, m_onSelectedNodeUpdate);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue