ShaderNode: Add save/load

This commit is contained in:
Lynix 2020-06-04 18:31:35 +02:00
parent 5790b502f7
commit 0888589716
17 changed files with 358 additions and 12 deletions

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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>

View File

@ -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 {};
}

View File

@ -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;

View File

@ -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);

View File

@ -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());
}

View File

@ -18,6 +18,8 @@ class MainWindow : public QMainWindow
private:
void BuildMenu();
void OnCompileToGLSL();
void OnLoad();
void OnSave();
NazaraSlot(ShaderGraph, OnSelectedNodeUpdate, m_onSelectedNodeUpdate);