Loading...
Searching...
No Matches
ShaderProgram.hpp
1#pragma once
2#include <Gfx/Graph/RenderState.hpp>
3#include <Gfx/Hashes.hpp>
4
5#include <score/tools/Debug.hpp>
6
7#include <ossia/detail/hash.hpp>
8#include <ossia/detail/hash_map.hpp>
9
10#include <QDebug>
11#include <QObject>
12#include <QDateTime>
13#include <QString>
14#include <QtGui/private/qshader_p.h>
15
16#include <isf.hpp>
17#include <score_plugin_gfx_export.h>
18
19#include <array>
20#include <optional>
21#include <verdigris>
22namespace isf
23{
24struct descriptor;
25}
26
27namespace Gfx
28{
29struct SCORE_PLUGIN_GFX_EXPORT ShaderSource
30{
31 using ProgramType = isf::parser::ShaderType;
32 ShaderSource() = default;
33 ~ShaderSource() = default;
34 ShaderSource(const ShaderSource&) = default;
35 ShaderSource(ShaderSource&&) = default;
36
37 ShaderSource(const QString& vert, const QString& frag)
38 : type{isf::parser::ShaderType::ISF}
39 , vertex{vert}
40 , fragment{frag}
41 {
42 }
43
44 ShaderSource(const std::vector<QString>& vec)
45 {
46 SCORE_ASSERT(vec.size() == 2);
47 type = isf::parser::ShaderType::ISF;
48 fragment = vec[0];
49 vertex = vec[1];
50 }
51 ShaderSource(ProgramType tp, const QString& vert, const QString& frag)
52 : type{tp}
53 , vertex{vert}
54 , fragment{frag}
55 {
56 }
57
58 ShaderSource(ProgramType tp, const std::vector<QString>& vec)
59 {
60 SCORE_ASSERT(vec.size() == 2);
61 type = tp;
62 fragment = vec[0];
63 vertex = vec[1];
64 }
65
66 ShaderSource(ProgramType tp, std::vector<QString>&& vec)
67 {
68 SCORE_ASSERT(vec.size() == 2);
69 type = tp;
70 fragment = std::move(vec[0]);
71 vertex = std::move(vec[1]);
72 }
73
74 ShaderSource& operator=(const ShaderSource&) = default;
75 ShaderSource& operator=(ShaderSource&&) = default;
76
77 ProgramType type{};
78 QString vertex;
79 QString fragment;
80
82 {
83 const QString name;
84 const QString ShaderSource::*pointer;
85 const std::string_view language{};
86 };
87
88 static const inline std::array<MemberSpec, 2> specification{
89 MemberSpec{QObject::tr("Fragment"), &ShaderSource::fragment, "GLSL"},
90 MemberSpec{QObject::tr("Vertex"), &ShaderSource::vertex, "GLSL"},
91 };
92
93 friend QDebug& operator<<(QDebug& d, const ShaderSource& sp)
94 {
95 return (d << sp.vertex << sp.fragment);
96 }
97 friend bool operator==(const ShaderSource& lhs, const ShaderSource& rhs) noexcept
98 {
99 // `type` MUST be part of equality: std::hash<ShaderSource> seeds with
100 // `type`, so two sources differing only by type hash differently. If ==
101 // ignored type they'd be "equal but unequal-hash", breaking the
102 // unordered-container invariant for ProgramCache / ProgramCacheKey.
103 return lhs.type == rhs.type && lhs.vertex == rhs.vertex
104 && lhs.fragment == rhs.fragment;
105 }
106 friend bool operator!=(const ShaderSource& lhs, const ShaderSource& rhs) noexcept
107 {
108 return !(lhs == rhs);
109 }
110
111 friend bool
112 operator==(const std::vector<QString>& lhs, const ShaderSource& rhs) noexcept
113 {
114 SCORE_ASSERT(lhs.size() == 2);
115 return lhs[0] == rhs.*(ShaderSource::specification[0].pointer)
116 && lhs[1] == rhs.*(ShaderSource::specification[1].pointer);
117 }
118 friend bool
119 operator!=(const std::vector<QString>& lhs, const ShaderSource& rhs) noexcept
120 {
121 return !(lhs == rhs);
122 }
123};
124
130enum class ShaderFamily
131{
132 Unknown,
133 RawRaster,
134 GeometryFilter,
135 Compute,
136 VertexShaderArt
137};
138
139SCORE_PLUGIN_GFX_EXPORT ShaderFamily shaderFileFamily(const QString& path) noexcept;
140
143SCORE_PLUGIN_GFX_EXPORT QString vertexShaderSibling(const QString& fsPath) noexcept;
144
147SCORE_PLUGIN_GFX_EXPORT QString fragmentShaderSibling(const QString& vsPath) noexcept;
148
152SCORE_PLUGIN_GFX_EXPORT
153QString locateShaderPath(const QString& path, const QObject& process) noexcept;
154
155SCORE_PLUGIN_GFX_EXPORT ShaderSource
156programFromISFFragmentShaderPath(
157 const QString& fsFilename, QByteArray fsData,
158 ShaderSource::ProgramType type = ShaderSource::ProgramType::ISF);
159SCORE_PLUGIN_GFX_EXPORT ShaderSource
160programFromVSAVertexShaderPath(const QString& vertexFilename, QByteArray vertexData);
161
162// Textual `#include` resolution for a single GLSL buffer. Used by
163// callers that want include support without going through the full
164// ProgramCache ISF pipeline — compute shaders are the current use case.
165// Returns the expanded source and a non-empty error string on failure
166// (missing header, include cycle, depth limit, …). The returned
167// QByteArray is empty iff the error is non-empty.
168SCORE_PLUGIN_GFX_EXPORT
169std::pair<QByteArray, QString>
170preprocessShaderIncludes(QByteArray source, const QString& originPath = {}) noexcept;
171
175SCORE_PLUGIN_GFX_EXPORT
176void logShaderError(const QObject& process, const QString& error) noexcept;
177}
178
179namespace std
180{
181template <>
182struct hash<Gfx::ShaderSource>
183{
184 std::size_t operator()(const Gfx::ShaderSource& program) const noexcept
185 {
186 // rapidhash via the gfx Qt-aware adapters; same primitive that
187 // produces content_hash values throughout the gfx pipeline.
188 std::size_t seed{(std::size_t)program.type};
189 ossia::hash_combine(seed, score::gfx::hash_qstring(program.vertex));
190 ossia::hash_combine(seed, score::gfx::hash_qstring(program.fragment));
191 return seed;
192 }
193};
194}
195
196namespace Gfx
197{
199{
200 isf::descriptor descriptor;
201};
202
203// Cache key. `originDir` is the *canonical directory* the shader was
204// loaded from (derived by the cache from the caller-supplied origin
205// path). Keying on both means two models loading the same source text
206// from different directories don't collide — include resolution against
207// each shader's own sibling dir stays correct.
209{
210 ShaderSource source;
211 QString originDir;
212
213 friend bool
214 operator==(const ProgramCacheKey& a, const ProgramCacheKey& b) noexcept
215 {
216 return a.source == b.source && a.originDir == b.originDir;
217 }
218};
219}
220
221namespace std
222{
223template <>
224struct hash<Gfx::ProgramCacheKey>
225{
226 std::size_t operator()(const Gfx::ProgramCacheKey& k) const noexcept
227 {
228 std::size_t seed = std::hash<Gfx::ShaderSource>{}(k.source);
229 ossia::hash_combine(seed, score::gfx::hash_qstring(k.originDir));
230 return seed;
231 }
232};
233}
234
235namespace Gfx
236{
237struct SCORE_PLUGIN_GFX_EXPORT ProgramCache
238{
239 static ProgramCache& instance() noexcept;
240
241 // `originPath` is the absolute path of the shader file the source was
242 // loaded from, used as the base for quoted `#include "..."` resolution
243 // and as part of the cache key. Empty when the source is in-memory
244 // with no associated file.
245 std::pair<std::optional<ProcessedProgram>, QString>
246 get(const ShaderSource& program, const QString& originPath = {}) noexcept;
247
248 ossia::hash_map<ProgramCacheKey, ProcessedProgram> programs;
253 {
254 QString path;
255 QDateTime modified;
256 qint64 size{};
257 };
258 ossia::hash_map<ProgramCacheKey, std::vector<IncludedFile>> includes;
259};
260
261}
262
263Q_DECLARE_METATYPE(Gfx::ShaderSource)
264W_REGISTER_ARGTYPE(Gfx::ShaderSource)
265Q_DECLARE_METATYPE(Gfx::ProcessedProgram)
266W_REGISTER_ARGTYPE(Gfx::ProcessedProgram)
Binds the rendering pipeline to ossia processes.
Definition AssetTable.cpp:7
void logShaderError(const QObject &process, const QString &error) noexcept
QString vertexShaderSibling(const QString &fsPath) noexcept
QString locateShaderPath(const QString &path, const QObject &process) noexcept
QString fragmentShaderSibling(const QString &vsPath) noexcept
ShaderFamily
Definition ShaderProgram.hpp:131
STL namespace.
Definition ShaderProgram.hpp:199
Definition ShaderProgram.hpp:253
Definition ShaderProgram.hpp:238
Definition ShaderProgram.hpp:209
Definition ShaderProgram.hpp:82
Definition ShaderProgram.hpp:30