Gamp v0.0.7-36-g24b1eb6
Gamp: Graphics, Audio, Multimedia and Processing
Loading...
Searching...
No Matches
ShaderCode.hpp
Go to the documentation of this file.
1/*
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright Gothel Software e.K.
4 *
5 * SPDX-License-Identifier: MIT
6 *
7 * This Source Code Form is subject to the terms of the MIT License
8 * If a copy of the MIT was not distributed with this file,
9 * you can obtain one at https://opensource.org/license/mit/.
10 */
11
12#ifndef GAMP_GLSLSHADERCODE_HPP_
13#define GAMP_GLSLSHADERCODE_HPP_
14
15#include <fstream>
16
17#include <jau/basic_types.hpp>
18#include <jau/debug.hpp>
19#include <jau/file_util.hpp>
20#include <jau/io_util.hpp>
21#include <jau/string_util.hpp>
22
23#include <gamp/Gamp.hpp>
25
26namespace gamp::render::gl::glsl {
27 using namespace gamp::render::gl;
28
29 /** @defgroup Gamp_GLSL Gamp GL Shader Support
30 * OpenGL Shading Language types and functionality.
31 *
32 * @{
33 */
34
35 class ShaderCode;
36 typedef std::shared_ptr<ShaderCode> ShaderCodeRef;
37
38 /**
39 * Convenient shader code class to use and instantiate vertex or fragment programs.
40 * <p>
41 * A documented example of how to use this code is available
42 * {@link #create(GL2ES2, int, Class, String, String, String, boolean) here} and
43 * {@link #create(GL2ES2, int, int, Class, String, String[], String, String) here}.
44 * </p>
45 * <p>
46 * Support for {@link GL4#GL_TESS_CONTROL_SHADER} and {@link GL4#GL_TESS_EVALUATION_SHADER}
47 * was added since 2.2.1.
48 * </p>
49 */
50 class ShaderCode {
51 public:
52 static bool DEBUG_CODE;
53
54 /** Unique resource suffix for {@link GL2ES2#GL_VERTEX_SHADER} in source code: <code>{@value}</code> */
55 static constexpr std::string_view SUFFIX_VERTEX_SOURCE = "vp" ;
56
57 /** Unique resource suffix for {@link GL2ES2#GL_VERTEX_SHADER} in binary: <code>{@value}</code> */
58 static constexpr std::string_view SUFFIX_VERTEX_BINARY = "bvp" ;
59
60 /** Unique resource suffix for {@link GL3#GL_GEOMETRY_SHADER} in source code: <code>{@value}</code> */
61 static constexpr std::string_view SUFFIX_GEOMETRY_SOURCE = "gp" ;
62
63 /** Unique resource suffix for {@link GL3#GL_GEOMETRY_SHADER} in binary: <code>{@value}</code> */
64 static constexpr std::string_view SUFFIX_GEOMETRY_BINARY = "bgp" ;
65
66 /**
67 * Unique resource suffix for {@link GL3ES3#GL_COMPUTE_SHADER} in source code: <code>{@value}</code>
68 * @since 2.3.2
69 */
70 static constexpr std::string_view SUFFIX_COMPUTE_SOURCE = "cp" ;
71
72 /**
73 * Unique resource suffix for {@link GL3ES3#GL_COMPUTE_SHADER} in binary: <code>{@value}</code>
74 * @since 2.3.2
75 */
76 static constexpr std::string_view SUFFIX_COMPUTE_BINARY = "bcp" ;
77
78 /**
79 * Unique resource suffix for {@link GL4#GL_TESS_CONTROL_SHADER} in source code: <code>{@value}</code>
80 * @since 2.2.1
81 */
82 static constexpr std::string_view SUFFIX_TESS_CONTROL_SOURCE = "tcp" ;
83
84 /**
85 * Unique resource suffix for {@link GL4#GL_TESS_CONTROL_SHADER} in binary: <code>{@value}</code>
86 * @since 2.2.1
87 */
88 static constexpr std::string_view SUFFIX_TESS_CONTROL_BINARY = "btcp" ;
89
90 /**
91 * Unique resource suffix for {@link GL4#GL_TESS_EVALUATION_SHADER} in source code: <code>{@value}</code>
92 * @since 2.2.1
93 */
94 static constexpr std::string_view SUFFIX_TESS_EVALUATION_SOURCE = "tep" ;
95
96 /**
97 * Unique resource suffix for {@link GL4#GL_TESS_EVALUATION_SHADER} in binary: <code>{@value}</code>
98 * @since 2.2.1
99 */
100 static constexpr std::string_view SUFFIX_TESS_EVALUATION_BINARY = "btep" ;
101
102 /** Unique resource suffix for {@link GL2ES2#GL_FRAGMENT_SHADER} in source code: <code>{@value}</code> */
103 static constexpr std::string_view SUFFIX_FRAGMENT_SOURCE = "fp" ;
104
105 /** Unique resource suffix for {@link GL2ES2#GL_FRAGMENT_SHADER} in binary: <code>{@value}</code> */
106 static constexpr std::string_view SUFFIX_FRAGMENT_BINARY = "bfp" ;
107
108 /** Unique relative path for binary shader resources for {@link GLES2#GL_NVIDIA_PLATFORM_BINARY_NV NVIDIA}: <code>{@value}</code> */
109 static constexpr std::string_view SUB_PATH_NVIDIA = "nvidia" ;
110
111 private:
112 struct Private{ explicit Private() = default; };
113
114 public:
115
116 /** Private ctor for `ShaderCodeRef create(...). */
117 ShaderCode(Private, GLenum type, size_t count, const source_list_t& sources) noexcept
118 : m_shaderBinaryFormat(0), m_shaderType(0), m_id(0), m_compiled(false)
119 {
120 if(sources.size() != count) {
121 ERR_PRINT("shader number (%zu) and sourceFiles array (%zu) of different length.", count, sources.size());
122 return;
123 }
124 if( !isValidShaderType(type) ) {
125 ERR_PRINT("Invalid shader type: %u", type);
126 return;
127 }
128 m_shaderSource = sources;
129 // m_shaderBinary = null;
130 m_shaderType = type;
131 m_shader.reserve(count);
132 m_shader.resize(count, 0);
133 m_id = nextID();
134 }
135 /**
136 * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER},
137 * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}.
138 * @param count number of shaders
139 * @param sources shader sources, organized as <code>source[count][strings-per-shader]</code>.
140 * @return successfully created valid ShaderCodeRef or nullptr on failure
141 */
142 static ShaderCodeRef create(GLenum type, size_t count, const source_list_t& sources) noexcept {
143 ShaderCodeRef res = std::make_shared<ShaderCode>(Private(), type, count, sources);
144 if( res->isValid() ) {
145 return res;
146 }
147 return nullptr;
148 }
149
150 /** Private ctor for `ShaderCodeRef create(...). */
151 ShaderCode(Private, GLenum type, size_t count, GLenum binFormat, const bytes_t& binary) noexcept
152 : m_shaderBinaryFormat(0), m_shaderType(0), m_id(0), m_compiled(false)
153 {
154 if( !isValidShaderType(type) ) {
155 ERR_PRINT("Invalid shader type: %u", type);
156 return;
157 }
158 // shaderSource = null;
159 m_shaderBinaryFormat = binFormat;
160 m_shaderBinary = binary;
161 m_shaderType = type;
162 m_shader.reserve(count);
163 m_shader.resize(count, 0);
164 m_id = nextID();
165 }
166 /**
167 * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER},
168 * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}.
169 * @param count number of shaders
170 * @param binary binary buffer containing the shader binaries,
171 * @return successfully created valid ShaderCodeRef or nullptr on failure
172 */
173 static ShaderCodeRef create(GLenum type, size_t count, GLenum binFormat, const bytes_t& binary) noexcept {
174 ShaderCodeRef res = std::make_shared<ShaderCode>(Private(), type, count, binFormat, binary);
175 if( res->isValid() ) {
176 return res;
177 }
178 return nullptr;
179 }
180
181 /**
182 * Creates a complete {@link ShaderCode} object while reading all shader source of <code>sourceFiles</code>,
183 * which location is resolved using the <code>context</code> class, see readShaderSource().
184 *
185 * @param gl current GL object to determine whether a shader compiler is available. If null, no validation is performed.
186 * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER},
187 * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}.
188 * @param count number of shaders
189 * @param context class used to help resolving the source location
190 * @param sourceFiles array of source locations, organized as <code>sourceFiles[count]</code> -> <code>shaderSources[count][1]</code>
191 * @return successfully created valid ShaderCodeRef or nullptr on failure
192 *
193 * @see #readShaderSource()
194 */
195 static ShaderCodeRef create(GL& gl, GLenum type, size_t count, const string_list_t& sourceFiles) noexcept {
197 ERR_PRINT("No shader compiler available for %s", gl.toString().c_str());
198 return nullptr;
199 }
200 if( !isValidShaderType(type) ) {
201 ERR_PRINT("Invalid shader type: %u", type);
202 return nullptr;
203 }
204 string_list_t one_string;
205 one_string.reserve(1);
206 one_string.resize(1, "");
207 const size_t sourceFileCount = sourceFiles.size();
208 source_list_t shaderSources;
209 bool ok = true;
210 if(sourceFileCount > 0) {
211 // sourceFiles.length and count is validated in ctor
212 shaderSources.reserve(sourceFileCount);
213 shaderSources.resize(sourceFileCount, one_string);
214 for(size_t i=0; i<sourceFileCount && ok; ++i) {
215 ok = readShaderSource(sourceFiles[i], shaderSources[i][0]);
216 }
217 }
218 if( ok ) {
219 ShaderCodeRef res = create(type, count, shaderSources);
220 if( res->isValid() ) {
221 return res;
222 }
223 }
224 return nullptr;
225 }
226
227#if 0
228// FIXME JAU COMPLETE
229
230 /**
231 * Creates a complete {@link ShaderCode} object while reading all shader sources from {@link Uri} <code>sourceLocations</code>
232 * via {@link #readShaderSource(Uri, boolean)}.
233 *
234 * @param gl current GL object to determine whether a shader compiler is available. If null, no validation is performed.
235 * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER},
236 * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}.
237 * @param count number of shaders
238 * @param sourceLocations array of {@link Uri} source locations, organized as <code>sourceFiles[count]</code> -> <code>shaderSources[count][1]</code>
239 * @return successfully created valid ShaderCodeRef or nullptr on failure
240 *
241 * @see #readShaderSource(Uri, boolean)
242 * @since 2.3.2
243 */
244 static ShaderCodeRef create(GL& gl, GLenum type, size_t count,
245 const std::vector<Uri>& sourceLocations, bool mutableStringBuilder) noexcept {
246 if(null != gl && !ShaderUtil.isShaderCompilerAvailable(gl)) {
247 return null;
248 }
249
250 CharSequence[][] shaderSources = null;
251 if(null!=sourceLocations) {
252 // sourceFiles.length and count is validated in ctor
253 shaderSources = new CharSequence[sourceLocations.length][1];
254 for(int i=0; i<sourceLocations.length; i++) {
255 try {
256 shaderSources[i][0] = readShaderSource(sourceLocations[i], mutableStringBuilder);
257 } catch (final IOException ioe) {
258 throw RuntimeException("readShaderSource("+sourceLocations[i]+") error: ", ioe);
259 }
260 if(null == shaderSources[i][0]) {
261 shaderSources = null;
262 }
263 }
264 }
265 if(null==shaderSources) {
266 return null;
267 }
268 return new ShaderCode(type, count, shaderSources);
269 }
270
271 /**
272 * Creates a complete {@link ShaderCode} object while reading the shader binary of <code>binaryFile</code>,
273 * which location is resolved using the <code>context</code> class, see {@link #readShaderBinary(Class, String)}.
274 *
275 * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER},
276 * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}.
277 * @param count number of shaders
278 * @param context class used to help resolving the source location
279 * @param binFormat a valid native binary format as they can be queried by {@link ShaderUtil#getShaderBinaryFormats(GL)}.
280 * @param sourceFiles array of source locations, organized as <code>sourceFiles[count]</code>
281 * @return successfully created valid ShaderCodeRef or nullptr on failure
282 *
283 * @see #readShaderBinary(Class, String)
284 * @see ShaderUtil#getShaderBinaryFormats(GL)
285 */
286 public static ShaderCode create(final int type, final int count, final Class<?> context, int binFormat, final String binaryFile) noexcept {
287 ByteBuffer shaderBinary = null;
288 if(null!=binaryFile && 0<=binFormat) {
289 try {
290 shaderBinary = readShaderBinary(context, binaryFile);
291 } catch (final IOException ioe) {
292 throw RuntimeException("readShaderBinary("+binaryFile+") error: ", ioe);
293 }
294 if(null == shaderBinary) {
295 binFormat = -1;
296 }
297 }
298 if(null==shaderBinary) {
299 return null;
300 }
301 return new ShaderCode(type, count, binFormat, shaderBinary);
302 }
303
304#endif
305
306 /**
307 * Returns a unique suffix for shader resources as follows:
308 * <ul>
309 * <li>Source<ul>
310 * <li>{@link GL2ES2#GL_VERTEX_SHADER vertex}: {@link #SUFFIX_VERTEX_SOURCE}</li>
311 * <li>{@link GL2ES2#GL_FRAGMENT_SHADER fragment}: {@link #SUFFIX_FRAGMENT_SOURCE}</li>
312 * <li>{@link GL3#GL_GEOMETRY_SHADER geometry}: {@link #SUFFIX_GEOMETRY_SOURCE}</li>
313 * <li>{@link GL4#GL_TESS_CONTROL_SHADER tess-ctrl}: {@link #SUFFIX_TESS_CONTROL_SOURCE}</li>
314 * <li>{@link GL4#GL_TESS_EVALUATION_SHADER tess-eval}: {@link #SUFFIX_TESS_EVALUATION_SOURCE}</li>
315 * <li>{@link GL3ES3#GL_COMPUTE_SHADER}: {@link #SUFFIX_COMPUTE_SOURCE}</li>
316 * </ul></li>
317 * <li>Binary<ul>
318 * <li>{@link GL2ES2#GL_VERTEX_SHADER vertex}: {@link #SUFFIX_VERTEX_BINARY}</li>
319 * <li>{@link GL2ES2#GL_FRAGMENT_SHADER fragment}: {@link #SUFFIX_FRAGMENT_BINARY}</li>
320 * <li>{@link GL3#GL_GEOMETRY_SHADER geometry}: {@link #SUFFIX_GEOMETRY_BINARY}</li>
321 * <li>{@link GL4#GL_TESS_CONTROL_SHADER tess-ctrl}: {@link #SUFFIX_TESS_CONTROL_BINARY}</li>
322 * <li>{@link GL4#GL_TESS_EVALUATION_SHADER tess-eval}: {@link #SUFFIX_TESS_EVALUATION_BINARY}</li>
323 * <li>{@link GL3ES3#GL_COMPUTE_SHADER}: {@link #SUFFIX_COMPUTE_BINARY}</li>
324 * </ul></li>
325 * </ul>
326 * @param binary true for a binary resource, false for a source resource
327 * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER},
328 * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}.
329 * @return suffix for valid shader type or zero sized view if invalid
330 *
331 * @see #create(GL2ES2, int, Class, String, String, String, boolean)
332 */
333 static std::string_view getFileSuffix(bool binary, GLenum type) noexcept {
334 switch (type) {
335 case GL_VERTEX_SHADER: // GL2ES2
337 case GL_FRAGMENT_SHADER: // GL2ES2
339 case GL_GEOMETRY_SHADER: // GL3ES3
341 case GL_TESS_CONTROL_SHADER: // GL3ES3
343 case GL_TESS_EVALUATION_SHADER: // GL3ES3
345 case GL_COMPUTE_SHADER: // GL3ES3
347 default:
348 return "";
349 }
350 }
351
352 /**
353 * Returns a unique relative path for binary shader resources as follows:
354 * <ul>
355 * <li>{@link GLES2#GL_NVIDIA_PLATFORM_BINARY_NV NVIDIA}: {@link #SUB_PATH_NVIDIA}</li>
356 * </ul>
357 * @return path for valid binary shader types or zero sized view if invalid
358 *
359 * @see #create(GL2ES2, int, Class, String, String, String, boolean)
360 */
361 static std::string_view getBinarySubPath(GLenum binFormat) noexcept {
362 switch (binFormat) {
363 case GL_NVIDIA_PLATFORM_BINARY_NV: // GLES2
364 return SUB_PATH_NVIDIA;
365 default:
366 return "";
367 }
368 }
369
370 /**
371 * Convenient creation method for instantiating a complete {@link ShaderCode} object
372 * either from source code using {@link #create(GL2ES2, int, int, Class, String[])},
373 * or from a binary code using {@link #create(int, int, Class, int, String)},
374 * whatever is available first.
375 * <p>
376 * The source and binary location names are expected w/o suffixes which are
377 * resolved and appended using the given {@code srcSuffixOpt} and {@code binSuffixOpt}
378 * if not {@code null}, otherwise {@link #getFileSuffix(boolean, int)} determines the suffixes.
379 * </p>
380 * <p>
381 * Additionally, the binary resource is expected within a subfolder of <code>binRoot</code>
382 * which reflects the vendor specific binary format, see {@link #getBinarySubPath(int)}.
383 * All {@link ShaderUtil#getShaderBinaryFormats(GL)} are being iterated
384 * using the binary subfolder, the first existing resource is being used.
385 * </p>
386 *
387 * Example:
388 * <pre>
389 * Your std JVM layout (plain or within a JAR):
390 *
391 * org/test/glsl/MyShaderTest.class
392 * org/test/glsl/shader/vertex.vp
393 * org/test/glsl/shader/fragment.fp
394 * org/test/glsl/shader/bin/nvidia/vertex.bvp
395 * org/test/glsl/shader/bin/nvidia/fragment.bfp
396 *
397 * Your Android APK layout:
398 *
399 * classes.dex
400 * assets/org/test/glsl/shader/vertex.vp
401 * assets/org/test/glsl/shader/fragment.fp
402 * assets/org/test/glsl/shader/bin/nvidia/vertex.bvp
403 * assets/org/test/glsl/shader/bin/nvidia/fragment.bfp
404 * ...
405 *
406 * Your invocation in org/test/glsl/MyShaderTest.java:
407 *
408 * ShaderCode vp0 = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, 1, this.getClass(),
409 * "shader", new String[] { "vertex" }, null,
410 * "shader/bin", "vertex", null, true);
411 * ShaderCode fp0 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, 1, this.getClass(),
412 * "shader", new String[] { "vertex" }, null,
413 * "shader/bin", "fragment", null, true);
414 * ShaderProgram sp0 = new ShaderProgram();
415 * sp0.add(gl, vp0, System.err);
416 * sp0.add(gl, fp0, System.err);
417 * st.attachShaderProgram(gl, sp0, true);
418 * </pre>
419 * A simplified entry point is {@link #create(GL, int, String, String, String, boolean)}.
420 *
421 * <p>
422 * The location is finally being resolved using the <code>context</code> class, see {@link #readShaderBinary(Class, String)}.
423 * </p>
424 *
425 * @param gl current GL object to determine whether a shader compiler is available (if <code>source</code> is used),
426 * or to determine the shader binary format (if <code>binary</code> is used).
427 * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER},
428 * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}.
429 * @param count number of shaders
430 * @param context class used to help resolving the source and binary location
431 * @param srcRoot relative <i>root</i> path for <code>srcBasenames</code> optional
432 * @param srcBasenames basenames w/o path or suffix relative to <code>srcRoot</code> for the shader's source code
433 * @param srcSuffixOpt optional custom suffix for shader's source file,
434 * if {@code null} {@link #getFileSuffix(boolean, int)} is being used.
435 * @param binRoot relative <i>root</i> path for <code>binBasenames</code>
436 * @param binBasename basename w/o path or suffix relative to <code>binRoot</code> for the shader's binary code
437 * @param binSuffixOpt optional custom suffix for shader's binary file,
438 * if {@code null} {@link #getFileSuffix(boolean, int)} is being used.
439 * @return successfully created valid ShaderCodeRef or nullptr on failure
440 *
441 * @see #create(GL2ES2, int, int, Class, String[])
442 * @see #create(int, int, Class, int, String)
443 * @see #readShaderSource(Class, String)
444 * @see #getFileSuffix(boolean, int)
445 * @see ShaderUtil#getShaderBinaryFormats(GL)
446 * @see #getBinarySubPath(int)
447 *
448 * @since 2.3.2
449 */
450 static ShaderCodeRef create(GL& gl, GLenum type, size_t count,
451 stringview_t srcRoot, const string_list_t& srcBasenames, stringview_t srcSuffixOpt,
452 stringview_t binRoot, stringview_t binBasename, stringview_t binSuffixOpt) noexcept {
453 ShaderCodeRef res;
454 string_t srcPathsString;
455 string_t binFileName;
456
457 if( !srcBasenames.empty() && ShaderUtil::isShaderCompilerAvailable(gl) ) {
458 string_list_t srcPaths;
459 srcPaths.resize(srcBasenames.size());
460 stringview_t srcSuffix = !srcSuffixOpt.empty() ? srcSuffixOpt : getFileSuffix(false, type);
461 if( !srcRoot.empty() ) {
462 for(size_t i=0; i<srcPaths.size(); ++i) {
463 srcPaths[i] = string_t(srcRoot).append("/").append(srcBasenames[i]).append(".").append(srcSuffix);
464 }
465 } else {
466 for(size_t i=0; i<srcPaths.size(); ++i) {
467 srcPaths[i] = string_t(srcBasenames[i]).append(".").append(srcSuffix);
468 }
469 }
470 res = create(gl, type, count, srcPaths);
471 if(res && res->isValid()) {
472 return res;
473 }
474 for(const string_t& s : srcPaths) {
475 if( !srcPathsString.empty() ) {
476 srcPathsString.append(";");
477 }
478 srcPathsString.append(s);
479 }
480 }
481 if( !binBasename.empty() ) {
483 stringview_t binSuffix = !binSuffixOpt.empty() ? binSuffixOpt : getFileSuffix(true, type);
484 for(GLenum bFmt : binFmts) {
485 string_t bFmtPath = string_t(getBinarySubPath(bFmt));
486 if(bFmtPath.empty()) continue;
487 binFileName = string_t(binRoot).append("/").append(bFmtPath).append("/").append(binBasename).append(".").append(binSuffix);
488 // res = create(type, count, bFmt, binFileName);
489 if(res && res->isValid()) {
490 return res;
491 }
492 }
493 }
494 return nullptr;
495 }
496
497 /**
498 * Simplified variation of {@link #create(GL2ES2, int, int, Class, String, String[], String, String, String, String, boolean)}.
499 * <p>
500 * Convenient creation method for instantiating a complete {@link ShaderCode} object
501 * either from source code using {@link #create(GL2ES2, int, int, Class, String[])},
502 * or from a binary code using {@link #create(int, int, Class, int, String)},
503 * whatever is available first.
504 * </p>
505 * <p>
506 * The source and binary location names are expected w/o suffixes which are
507 * resolved and appended using {@link #getFileSuffix(boolean, int)}.
508 * </p>
509 * <p>
510 * Additionally, the binary resource is expected within a subfolder of <code>binRoot</code>
511 * which reflects the vendor specific binary format, see {@link #getBinarySubPath(int)}.
512 * All {@link ShaderUtil#getShaderBinaryFormats(GL)} are being iterated
513 * using the binary subfolder, the first existing resource is being used.
514 * </p>
515 *
516 * Example:
517 * <pre>
518 * Your std JVM layout (plain or within a JAR):
519 *
520 * org/test/glsl/MyShaderTest.class
521 * org/test/glsl/shader/vertex.vp
522 * org/test/glsl/shader/fragment.fp
523 * org/test/glsl/shader/bin/nvidia/vertex.bvp
524 * org/test/glsl/shader/bin/nvidia/fragment.bfp
525 *
526 * Your Android APK layout:
527 *
528 * classes.dex
529 * assets/org/test/glsl/shader/vertex.vp
530 * assets/org/test/glsl/shader/fragment.fp
531 * assets/org/test/glsl/shader/bin/nvidia/vertex.bvp
532 * assets/org/test/glsl/shader/bin/nvidia/fragment.bfp
533 * ...
534 *
535 * Your invocation in org/test/glsl/MyShaderTest.java:
536 *
537 * ShaderCode vp0 = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, 1, this.getClass(),
538 * "shader", new String[] { "vertex" }, "shader/bin", "vertex", true);
539 * ShaderCode fp0 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, 1, this.getClass(),
540 * "shader", new String[] { "vertex" }, "shader/bin", "fragment", true);
541 * ShaderProgram sp0 = new ShaderProgram();
542 * sp0.add(gl, vp0, System.err);
543 * sp0.add(gl, fp0, System.err);
544 * st.attachShaderProgram(gl, sp0, true);
545 * </pre>
546 * A simplified entry point is {@link #create(GL2ES2, int, Class, String, String, String, boolean)}.
547 *
548 * <p>
549 * The location is finally being resolved using the <code>context</code> class, see {@link #readShaderBinary(Class, String)}.
550 * </p>
551 *
552 * @param gl current GL object to determine whether a shader compiler is available (if <code>source</code> is used),
553 * or to determine the shader binary format (if <code>binary</code> is used).
554 * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER},
555 * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}.
556 * @param count number of shaders
557 * @param context class used to help resolving the source and binary location
558 * @param srcRoot relative <i>root</i> path for <code>srcBasenames</code> optional
559 * @param srcBasenames basenames w/o path or suffix relative to <code>srcRoot</code> for the shader's source code
560 * @param binRoot relative <i>root</i> path for <code>binBasenames</code>
561 * @param binBasename basename w/o path or suffix relative to <code>binRoot</code> for the shader's binary code
562 * @return successfully created valid ShaderCodeRef or nullptr on failure
563 *
564 * @see #create(GL2ES2, int, int, Class, String, String[], String, String, String, String, boolean)
565 * @see #readShaderSource(Class, String)
566 * @see #getFileSuffix(boolean, int)
567 * @see ShaderUtil#getShaderBinaryFormats(GL)
568 * @see #getBinarySubPath(int)
569 */
570 static ShaderCodeRef create(GL& gl, GLenum type, size_t count,
571 stringview_t srcRoot, const string_list_t& srcBasenames,
572 stringview_t binRoot, stringview_t binBasename) noexcept {
573 return create(gl, type, count, srcRoot, srcBasenames, "", binRoot, binBasename, "");
574 }
575
576 /**
577 * Simplified variation of {@link #create(GL2ES2, int, int, Class, String, String[], String, String, String, String, boolean)}.
578 * <p>
579 * Example:
580 * <pre>
581 * Your std JVM layout (plain or within a JAR):
582 *
583 * org/test/glsl/MyShaderTest.class
584 * org/test/glsl/shader/vertex.vp
585 * org/test/glsl/shader/fragment.fp
586 * org/test/glsl/shader/bin/nvidia/vertex.bvp
587 * org/test/glsl/shader/bin/nvidia/fragment.bfp
588 *
589 * Your Android APK layout:
590 *
591 * classes.dex
592 * assets/org/test/glsl/shader/vertex.vp
593 * assets/org/test/glsl/shader/fragment.fp
594 * assets/org/test/glsl/shader/bin/nvidia/vertex.bvp
595 * assets/org/test/glsl/shader/bin/nvidia/fragment.bfp
596 * ...
597 *
598 * Your invocation in org/test/glsl/MyShaderTest.java:
599 *
600 * ShaderCode vp0 = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, this.getClass(),
601 * "shader", "shader/bin", "vertex", null, null, true);
602 * ShaderCode fp0 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, this.getClass(),
603 * "shader", "shader/bin", "fragment", null, null, true);
604 * ShaderProgram sp0 = new ShaderProgram();
605 * sp0.add(gl, vp0, System.err);
606 * sp0.add(gl, fp0, System.err);
607 * st.attachShaderProgram(gl, sp0, true);
608 * </pre>
609 * </p>
610 *
611 * @param gl current GL object to determine whether a shader compiler is available (if <code>source</code> is used),
612 * or to determine the shader binary format (if <code>binary</code> is used).
613 * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER},
614 * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}.
615 * @param context class used to help resolving the source and binary location
616 * @param srcRoot relative <i>root</i> path for <code>basename</code> optional
617 * @param binRoot relative <i>root</i> path for <code>basename</code>
618 * @param basename basename w/o path or suffix relative to <code>srcRoot</code> and <code>binRoot</code>
619 * for the shader's source and binary code.
620 * @param srcSuffixOpt optional custom suffix for shader's source file,
621 * if {@code null} {@link #getFileSuffix(boolean, int)} is being used.
622 * @param binSuffixOpt optional custom suffix for shader's binary file,
623 * if {@code null} {@link #getFileSuffix(boolean, int)} is being used.
624 * @return successfully created valid ShaderCodeRef or nullptr on failure
625 *
626 * @see #create(GL2ES2, int, int, Class, String, String[], String, String, String, String, boolean)
627 * @since 2.3.2
628 */
629 static ShaderCodeRef create(GL& gl, GLenum type, stringview_t srcRoot, stringview_t binRoot,
630 stringview_t basename, stringview_t srcSuffixOpt, stringview_t binSuffixOpt) noexcept {
631 string_list_t srcBasenames = { string_t(basename) };
632 return create(gl, type, 1, srcRoot, srcBasenames, srcSuffixOpt, binRoot, basename, binSuffixOpt);
633 }
634
635 /**
636 * Simplified variation of {@link #create(GL2ES2, int, Class, String, String, String, String, String, boolean)}.
637 * <p>
638 * Example:
639 * <pre>
640 * Your std JVM layout (plain or within a JAR):
641 *
642 * org/test/glsl/MyShaderTest.class
643 * org/test/glsl/shader/vertex.vp
644 * org/test/glsl/shader/fragment.fp
645 * org/test/glsl/shader/bin/nvidia/vertex.bvp
646 * org/test/glsl/shader/bin/nvidia/fragment.bfp
647 *
648 * Your Android APK layout:
649 *
650 * classes.dex
651 * assets/org/test/glsl/shader/vertex.vp
652 * assets/org/test/glsl/shader/fragment.fp
653 * assets/org/test/glsl/shader/bin/nvidia/vertex.bvp
654 * assets/org/test/glsl/shader/bin/nvidia/fragment.bfp
655 * ...
656 *
657 * Your invocation in org/test/glsl/MyShaderTest.java:
658 *
659 * ShaderCode vp0 = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, this.getClass(),
660 * "shader", "shader/bin", "vertex", true);
661 * ShaderCode fp0 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, this.getClass(),
662 * "shader", "shader/bin", "fragment", true);
663 * ShaderProgram sp0 = new ShaderProgram();
664 * sp0.add(gl, vp0, System.err);
665 * sp0.add(gl, fp0, System.err);
666 * st.attachShaderProgram(gl, sp0, true);
667 * </pre>
668 * </p>
669 *
670 * @param gl current GL object to determine whether a shader compiler is available (if <code>source</code> is used),
671 * or to determine the shader binary format (if <code>binary</code> is used).
672 * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER},
673 * {@link GL4#GL_TESS_CONTROL_SHADER}, {@link GL4#GL_TESS_EVALUATION_SHADER} or {@link GL3ES3#GL_COMPUTE_SHADER}.
674 * @param context class used to help resolving the source and binary location
675 * @param srcRoot relative <i>root</i> path for <code>basename</code> optional
676 * @param binRoot relative <i>root</i> path for <code>basename</code>
677 * @param basenames basename w/o path or suffix relative to <code>srcRoot</code> and <code>binRoot</code>
678 * for the shader's source and binary code.
679 * @return successfully created valid ShaderCodeRef or nullptr on failure
680 */
681 static ShaderCodeRef create(GL& gl, GLenum type,
682 stringview_t srcRoot, stringview_t binRoot, stringview_t basename) noexcept {
683 return create(gl, type, srcRoot, binRoot, basename, "", "");
684 }
685
686 /** Returns the unique shader id for successfully created instances, zero if instance creation failed. */
687 constexpr size_t id() const noexcept { return m_id; }
688
689 /** Returns true if this instance is valid, i.e. 0 != id() */
690 constexpr bool isValid() const noexcept { return 0 != m_id; }
691
692 /** Returns true if this instance is valid and compiled. */
693 constexpr bool isCompiled() const noexcept { return m_compiled; }
694
695 GLenum shaderType() const noexcept { return m_shaderType; }
696 string_t shaderTypeStr() const noexcept { return string_t(shaderTypeString(m_shaderType)); }
697
698 GLenum shaderBinaryFormat() const noexcept { return m_shaderBinaryFormat; }
699 const bytes_t& shaderBinary() const noexcept { return m_shaderBinary; }
700 const source_list_t& shaderSource() const noexcept { return m_shaderSource; }
701
702 const shader_list_t& shader() const noexcept { return m_shader; }
703
704 bool compile(GL& gl, bool verbose=false) {
705 if(!isValid()) return false;
706 if(isCompiled()) return true;
707
708 // Create & Compile the vertex/fragment shader objects
709 if(!m_shaderSource.empty()) {
710 if(DEBUG_CODE) {
711 jau::PLAIN_PRINT(true, "ShaderCode.compile");
712 dumpSource(); // NOLINT(bugprone-exception-escape)
713 }
714 m_compiled=ShaderUtil::createAndCompileShader(gl, m_shader, m_shaderType,
715 m_shaderSource, verbose);
716 } else if(!m_shaderBinary.empty()) {
717 m_compiled=ShaderUtil::createAndLoadShader(gl, m_shader, m_shaderType,
718 m_shaderBinaryFormat, m_shaderBinary, verbose);
719 } else if(DEBUG_CODE) {
720 jau::PLAIN_PRINT(true, "ShaderCode.compile: No code");
721 dumpSource(); // NOLINT(bugprone-exception-escape)
722 }
723 return m_compiled;
724 }
725
726 void destroy(GL& gl) noexcept {
727 if(isCompiled()) {
729 m_compiled=false;
730 }
731 if(!m_shaderBinary.empty()) {
732 m_shaderBinary.clear();
733 }
734 m_shaderSource.clear();
735 m_shaderBinaryFormat=0;
736 m_shaderType=0;
737 }
738
739 constexpr bool operator==(const ShaderCode& rhs) const noexcept {
740 if(this==&rhs) { return true; }
741 return m_id == rhs.m_id;
742 }
743 constexpr std::size_t hash_code() const noexcept { return m_id; }
744
746 string_t r("ShaderCode[id=");
747 r.append(std::to_string(m_id)).append(", type=").append(shaderTypeStr())
748 .append(", valid=").append(std::to_string(isValid())).append(", compiled=").append(std::to_string(m_compiled))
749 .append(", ").append(std::to_string(m_shader.size())).append(" shader: ");
750 for(GLuint s : m_shader) {
751 r.append(" ").append(std::to_string(s));
752 }
753 if(!m_shaderSource.empty()) {
754 r.append(", source]");
755 } else if(!m_shaderBinary.empty()) {
756 r.append(", binary]");
757 }
758 return r;
759 }
760
761
762 void dumpSource() {
763 if(m_shaderSource.empty()) {
764 jau::PLAIN_PRINT(true, "<no shader source>");
765 return;
766 }
767 const size_t sourceCount = m_shaderSource.size();
768 const size_t shaderCount = m_shader.size();
769 jau::PLAIN_PRINT(true, "");
770 jau::PLAIN_PRINT(true, "ShaderCode[id=%d, type=%s, valid=%d, compiled=%d, %zu/%zu shader:",
771 m_id, shaderTypeStr().c_str(), isValid(), m_compiled, m_shader.size(), shaderCount);
772 if( 0 == shaderCount ) {
773 jau::PLAIN_PRINT(true, "none]");
774 }
775 for(size_t i=0; i<shaderCount; ++i) {
776 jau::PLAIN_PRINT(true, "");
777 jau::PLAIN_PRINT(true, "Shader #%zu/%zu name %u", i, shaderCount, m_shader[i]);
778 jau::PLAIN_PRINT(true, "--------------------------------------------------------------");
779 if(i>=sourceCount) {
780 jau::PLAIN_PRINT(true, "<no shader source>");
781 } else {
782 const string_list_t& src = m_shaderSource[i];
783 int lineno=0;
784
785 for(size_t j=0; j<src.size(); j++) {
786 jau::PLAIN_PRINT(false, "%4d: // Segment %d/%d:", lineno, j, src.size());
787 std::istringstream reader(src[j]);
788 string_t line;
789 while (std::getline(reader, line)) {
790 ++lineno;
791 jau::PLAIN_PRINT(false, "%4d: %s", lineno, line.c_str());
792 }
793 }
794 }
795 jau::PLAIN_PRINT(true, "--------------------------------------------------------------");
796 }
797 jau::PLAIN_PRINT(true, "]");
798 }
799
800 /**
801 * Adds <code>data</code> after the line containing <code>tag</code>.
802 *
803 * @param shaderIdx the shader index to be used.
804 * @param tag search string
805 * @param fromIndex start search <code>tag</code> begininig with this index
806 * @param data the text to be inserted. Shall end with an EOL '\n' character.
807 * @return index after the inserted <code>data</code> or std::string::npos if tag wasn't found or on error
808 */
809 size_t insertShaderSource(size_t shaderIdx, stringview_t tag, size_t fromIndex, stringview_t data) noexcept {
810 if(m_shaderSource.empty()) {
811 ERR_PRINT("no shader source");
812 return string_t::npos;
813 }
814 const size_t shaderCount = m_shader.size();
815 if(shaderIdx>=shaderCount) {
816 ERR_PRINT("shaderIdx %zu not within shader bounds %zu", shaderIdx, shaderCount);
817 return string_t::npos;
818 }
819 const size_t sourceCount = m_shaderSource.size();
820 if(shaderIdx>=sourceCount) {
821 ERR_PRINT("shaderIdx %zu not within source bounds %zu", shaderIdx, shaderCount);
822 return string_t::npos;
823 }
824 string_list_t& src = m_shaderSource[shaderIdx];
825 if(src.empty()) {
826 ERR_PRINT("no shader source at for shader index %zu", shaderIdx);
827 return string_t::npos;
828 }
829 size_t curEndIndex = 0;
830 for(auto & sb : src) {
831 curEndIndex += sb.length();
832 if(fromIndex < curEndIndex) {
833 size_t insertIdx = sb.find(tag, fromIndex);
834 if(string_t::npos != insertIdx) {
835 insertIdx += tag.length();
836 size_t eol = sb.find('\n', insertIdx); // eol: covers \n and \r\n
837 if(string_t::npos == eol) {
838 eol = sb.find('\r', insertIdx); // eol: covers \r 'only'
839 }
840 if(string_t::npos != eol) {
841 insertIdx = eol+1; // eol found
842 } else {
843 sb.insert(insertIdx, "\n"); // add eol
844 ++insertIdx;
845 }
846 sb.insert(insertIdx, data);
847 return insertIdx+data.length();
848 }
849 }
850 }
851 return string_t::npos;
852 }
853
854 /**
855 * Adds <code>data</code> at <code>position</code> in shader source for shader <code>shaderIdx</code>.
856 *
857 * @param shaderIdx the shader index to be used.
858 * @param position in shader source segments of shader <code>shaderIdx</code>, if exceeding source length (like std::string::npos or) data will be appended
859 * @param data the text to be inserted. Shall end with an EOL '\n' character
860 * @return index after the inserted `data` or std::string::npos on error
861 */
862 size_t insertShaderSource(size_t shaderIdx, size_t position, stringview_t data) noexcept {
863 if(m_shaderSource.empty()) {
864 ERR_PRINT("no shader source");
865 return string_t::npos;
866 }
867 const size_t shaderCount = m_shader.size();
868 if(shaderIdx>=shaderCount) {
869 ERR_PRINT("shaderIdx %zu not within shader bounds %zu", shaderIdx, shaderCount);
870 return string_t::npos;
871 }
872 const size_t sourceCount = m_shaderSource.size();
873 if(shaderIdx>=sourceCount) {
874 ERR_PRINT("shaderIdx %zu not within source bounds %zu", shaderIdx, shaderCount);
875 return string_t::npos;
876 }
877 string_list_t& src = m_shaderSource[shaderIdx];
878 if(src.empty()) {
879 ERR_PRINT("no shader source at for shader index %zu", shaderIdx);
880 return string_t::npos;
881 }
882 size_t curEndIndex = 0;
883 size_t j=0;
884 for(; j<src.size()-1 && position > curEndIndex; ++j) {
885 curEndIndex += src[j].length();
886 }
887 string_t& sb = src[j];
888 const size_t lastEndIndex = curEndIndex;
889 curEndIndex += src[j].length();
890 if( position > curEndIndex ) {
891 position = curEndIndex;
892 }
893 sb.insert(position - lastEndIndex, data);
894 return position+data.length();
895 }
896
897 /**
898 * Adds shader source located in <code>path</code>,
899 * either relative to the <code>location</code> or absolute <i>as-is</i>
900 * at <code>position</code> in shader source for shader <code>shaderIdx</code>.
901 *
902 * @param shaderIdx the shader index to be used.
903 * @param position in shader source segments of shader <code>shaderIdx</code>, -1 will append data
904 * @param path location of shader source
905 * @return index after the inserted code or std::string::npos on error
906 */
907 size_t insertShaderSourceFile(size_t shaderIdx, size_t position, const string_t& path) noexcept {
909 if( !readShaderSource(path, data) ) {
910 return string_t::npos;
911 }
912 return insertShaderSource(shaderIdx, position, data);
913 }
914
915 /**
916 * Replaces <code>oldName</code> with <code>newName</code> in all shader sources.
917 * <p>
918 * In case <code>oldName</code> and <code>newName</code> are equal, no action is performed.
919 * </p>
920 *
921 * @param oldName the to be replace string
922 * @param newName the replacement string
923 * @return the number of replacements, zero on error or no replacement
924 */
925 size_t replaceInShaderSource(const string_t& oldName, const string_t& newName) noexcept {
926 if(m_shaderSource.empty() || oldName == newName) {
927 return 0;
928 }
929 const size_t oldNameLen = oldName.length();
930 const size_t newNameLen = newName.length();
931 size_t num = 0;
932 const size_t sourceCount = m_shaderSource.size();
933 for(size_t shaderIdx = 0; shaderIdx<sourceCount; ++shaderIdx) {
934 string_list_t& src = m_shaderSource[shaderIdx];
935 for(auto & sb : src) {
936 size_t curPos = 0;
937 while(curPos<sb.length()-oldNameLen+1) {
938 const size_t startIdx = sb.find(oldName, curPos);
939 if(string_t::npos != startIdx) {
940 sb.replace(startIdx, oldNameLen, newName);
941 curPos = startIdx + newNameLen;
942 ++num;
943 } else {
944 curPos = sb.length();
945 }
946 }
947 }
948 }
949 return num;
950 }
951
952 private:
953 /** Returns true if successful, otherwise false. */
954 static bool readShaderSource(const string_t& conn, string_t& result, int& lineno) noexcept {
955 if(DEBUG_CODE) {
956 if(0 == lineno) {
957 result.append("// '"+conn+"'\n"); // conn.getURL().toExternalForm()
958 } else {
959 result.append("// included @ line "+std::to_string(lineno)+": '"+conn+"'\n"); // conn.getURL().toExternalForm()
960 }
961 }
962 std::ifstream reader(conn);
963 string_t line;
964 while ( std::getline(reader, line) ) {
965 ++lineno;
966 if (line.starts_with("#include ")) {
967 string_t includeFile;
968 {
969 string_t s = line.substr(9);
970 jau::trimInPlace( s );
971 // Bug 1283: Remove shader include filename quotes if exists at start and end only
972 if( s.starts_with("\"") && s.ends_with("\"")) {
973 s = s.substr(1, s.length()-2);
974 }
975 includeFile = s;
976 }
977 string_t nextConn = gamp::resolve_asset(includeFile);
978
979 if( nextConn.empty() && !jau::fs::isAbsolute(includeFile) ) {
980 // Try relative of current shader location
981 includeFile = jau::fs::dirname(conn).append("/").append(includeFile);
982 if( jau::fs::exists(includeFile) ) {
983 nextConn = includeFile;
984 }
985 }
986 if( nextConn.empty() ) {
987 return false;
988 }
989 if( DEBUG_CODE ) {
990 jau::PLAIN_PRINT(true, "readShaderSource: including '%s' -> '%s'", includeFile.c_str(), nextConn.c_str());
991 }
992 lineno = readShaderSource(nextConn, result, lineno);
993 } else {
994 result.append(line + "\n");
995 }
996 }
997 return true;
998 }
999
1000 public:
1001 /**
1002 * Reads shader source from file located in `path` and appends it to result.
1003 *
1004 * @param path location of shader source
1005 * @param result storage to appends the source code
1006 * @return true if successful, otherwise false
1007 */
1008 static bool readShaderSource(stringview_t path0, string_t& result) noexcept {
1009 const string_t path(path0);
1010 const string_t conn = gamp::resolve_asset(path);
1011 if( DEBUG_CODE ) {
1012 jau::PLAIN_PRINT(true, "readShaderSource: %s -> %s", path.c_str(), conn.c_str());
1013 }
1014 if (!conn.empty()) {
1015 int lineno=0;
1016 return readShaderSource(conn, result, lineno);
1017 }
1018 return false;
1019 }
1020
1021 /**
1022 * Reads shader source from file located in `path`, returning the string.
1023 *
1024 * Final location lookup is performed via `gamp::resolve_asset(path)`.
1025 *
1026 * @param path location of shader source
1027 * @return a non empty string containing the source code if successful, otherwise an empty string
1028 * @see gamp::resolve_asset
1029 */
1031 string_t result;
1032 if( !readShaderSource(path, result) ) {
1033 result.clear();
1034 }
1035 return result;
1036 }
1037
1038#if 0
1039// FIXME JAU COMPLETE
1040
1041 /**
1042 * Reads shader binary located in <code>path</code>,
1043 * either relative to the <code>context</code> class or absolute <i>as-is</i>.
1044 * <p>
1045 * Final location lookup is perfomed via {@link ClassLoader#getResource(String)} and {@link ClassLoader#getSystemResource(String)},
1046 * see {@link IOUtil#getResource(Class, String)}.
1047 * </p>
1048 *
1049 * @param context class used to help resolve the source location
1050 * @param path location of shader binary
1051 *
1052 * @see IOUtil#getResource(Class, String)
1053 */
1054 public static ByteBuffer readShaderBinary(final Class<?> context, final String path) noexcept {
1055 final URLConnection conn = IOUtil.getResource(path, context.getClassLoader(), context);
1056 if (conn == null) {
1057 return null;
1058 }
1059 final BufferedInputStream bis = new BufferedInputStream( conn.getInputStream() );
1060 try {
1061 return IOUtil.copyStream2ByteBuffer( bis );
1062 } finally {
1063 IOUtil.close(bis, false);
1064 }
1065 }
1066
1067 /**
1068 * Reads shader binary located from {@link Uri#absolute} {@link Uri} <code>binLocation</code>.
1069 * @param binLocation {@link Uri} location of shader binary
1070 * @since 2.3.2
1071 */
1072 public static ByteBuffer readShaderBinary(final Uri binLocation) noexcept {
1073 final URLConnection conn = IOUtil.openURL(binLocation.toURL(), "ShaderCode ");
1074 if (conn == null) {
1075 return null;
1076 }
1077 final BufferedInputStream bis = new BufferedInputStream( conn.getInputStream() );
1078 try {
1079 return IOUtil.copyStream2ByteBuffer( bis );
1080 } finally {
1081 IOUtil.close(bis, false);
1082 }
1083 }
1084#endif
1085
1086 // Shall we use: #ifdef GL_FRAGMENT_PRECISION_HIGH .. #endif for using highp in fragment shader if avail ?
1087 /** Default precision of {@link GL#isGLES2() ES2} for {@link GL2ES2#GL_VERTEX_SHADER vertex-shader}: {@value #es2_default_precision_vp} */
1088 constexpr static std::string_view es2_default_precision_vp = "\nprecision highp float;\nprecision highp int;\n/*precision lowp sampler2D;*/\n/*precision lowp samplerCube;*/\n";
1089 /** Default precision of {@link GL#isGLES2() ES2} for {@link GL2ES2#GL_FRAGMENT_SHADER fragment-shader}: {@value #es2_default_precision_fp} */
1090 constexpr static std::string_view es2_default_precision_fp = "\nprecision mediump float;\nprecision mediump int;\n/*precision lowp sampler2D;*/\n/*precision lowp samplerCube;*/\n";
1091
1092 /** Default precision of {@link GL#isGLES3() ES3} for {@link GL2ES2#GL_VERTEX_SHADER vertex-shader}: {@value #es3_default_precision_vp} */
1093 constexpr static std::string_view es3_default_precision_vp = es2_default_precision_vp;
1094 /**
1095 * Default precision of {@link GL#isGLES3() ES3} for {@link GL2ES2#GL_FRAGMENT_SHADER fragment-shader}: {@value #es3_default_precision_fp},
1096 * same as for {@link GL2ES2#GL_VERTEX_SHADER vertex-shader}, i.e {@link #es3_default_precision_vp},
1097 * due to ES 3.x requirements of using same precision for uniforms!
1098 */
1099 constexpr static std::string_view es3_default_precision_fp = es3_default_precision_vp;
1100
1101 /** Default precision of GLSL &ge; 1.30 as required until &lt; 1.50 for {@link GL2ES2#GL_VERTEX_SHADER vertex-shader} or {@link GL3#GL_GEOMETRY_SHADER geometry-shader}: {@value #gl3_default_precision_vp_gp}. See GLSL Spec 1.30-1.50 Section 4.5.3. */
1102 constexpr static std::string_view gl3_default_precision_vp_gp = "\nprecision highp float;\nprecision highp int;\n";
1103 /** Default precision of GLSL &ge; 1.30 as required until &lt; 1.50 for {@link GL2ES2#GL_FRAGMENT_SHADER fragment-shader}: {@value #gl3_default_precision_fp}. See GLSL Spec 1.30-1.50 Section 4.5.3. */
1104 constexpr static std::string_view gl3_default_precision_fp = "\nprecision highp float;\nprecision mediump int;\n/*precision mediump sampler2D;*/\n";
1105
1106 /** <i>Behavior</i> for GLSL extension directive, see {@link #createExtensionDirective(String, String)}, value {@value}. */
1107 constexpr static std::string_view REQUIRE = "require";
1108 /** <i>Behavior</i> for GLSL extension directive, see {@link #createExtensionDirective(String, String)}, value {@value}. */
1109 constexpr static std::string_view ENABLE = "enable";
1110 /** <i>Behavior</i> for GLSL extension directive, see {@link #createExtensionDirective(String, String)}, value {@value}. */
1111 constexpr static std::string_view DISABLE = "disable";
1112 /** <i>Behavior</i> for GLSL extension directive, see {@link #createExtensionDirective(String, String)}, value {@value}. */
1113 constexpr static std::string_view WARN = "warn";
1114
1115 /**
1116 * Creates a GLSL extension directive.
1117 * <p>
1118 * Prefer {@link #ENABLE} over {@link #REQUIRE}, since the latter will force a failure if not supported.
1119 * </p>
1120 *
1121 * @param extensionName
1122 * @param behavior shall be either {@link #REQUIRE}, {@link #ENABLE}, {@link #DISABLE} or {@link #WARN}
1123 * @return the complete extension directive
1124 */
1125 static string_t createExtensionDirective(const string_t& extensionName, const string_t& behavior) {
1126 return "#extension " + extensionName + " : " + behavior + "\n";
1127 }
1128
1129 /**
1130 * Add GLSL version at the head of this shader source code.
1131 * @param gl a GL context, which must have been made current once
1132 * @return the index after the inserted data, maybe 0 if nothing has be inserted.
1133 */
1134 size_t addGLSLVersion(const GL& gl) noexcept {
1135 return insertShaderSource(0, 0, gl.glProfile().getGLSLVersionString());
1136 }
1137
1138 /**
1139 * Adds default precision to source code at given position if required, i.e.
1140 * {@link #es2_default_precision_vp}, {@link #es2_default_precision_fp},
1141 * {@link #gl3_default_precision_vp_gp}, {@link #gl3_default_precision_fp} or none,
1142 * depending on the {@link GLContext#getGLSLVersionNumber() GLSL version} being used.
1143 * @param gl a GL context, which must have been made current once
1144 * @param pos position within this mutable shader source.
1145 * @return the index after the inserted data, maybe 0 if nothing has be inserted.
1146 */
1147 size_t addDefaultShaderPrecision(const GL& gl, size_t pos) {
1148 std::string_view defaultPrecision;
1149 if( gl.glProfile().nativeGLES3() ) {
1150 switch ( m_shaderType ) {
1151 case GL_VERTEX_SHADER:
1152 defaultPrecision = es3_default_precision_vp; break;
1153 case GL_FRAGMENT_SHADER:
1154 defaultPrecision = es3_default_precision_fp; break;
1155 case GL_COMPUTE_SHADER:
1156 defaultPrecision = es3_default_precision_fp; break;
1157 default:
1158 defaultPrecision = "";
1159 break;
1160 }
1161 } else if( gl.glProfile().nativeGLES2() ) {
1162 switch ( m_shaderType ) {
1163 case GL_VERTEX_SHADER:
1164 defaultPrecision = es2_default_precision_vp; break;
1165 case GL_FRAGMENT_SHADER:
1166 defaultPrecision = es2_default_precision_fp; break;
1167 default:
1168 defaultPrecision = "";
1169 break;
1170 }
1171 } else if( requiresGL3DefaultPrecision(gl) ) {
1172 // GLSL [ 1.30 .. 1.50 [ needs at least fragement float default precision!
1173 switch ( m_shaderType ) {
1174 case GL_VERTEX_SHADER:
1175 case GL_GEOMETRY_SHADER:
1176 case GL_TESS_CONTROL_SHADER:
1177 case GL_TESS_EVALUATION_SHADER:
1178 defaultPrecision = gl3_default_precision_vp_gp; break;
1179 case GL_FRAGMENT_SHADER:
1180 defaultPrecision = gl3_default_precision_fp; break;
1181 case GL_COMPUTE_SHADER:
1182 defaultPrecision = gl3_default_precision_fp; break;
1183 default:
1184 defaultPrecision = "";
1185 break;
1186 }
1187 } else {
1188 defaultPrecision = "";
1189 }
1190 if( defaultPrecision.length() > 0 ) {
1191 return insertShaderSource(0, pos, string_t(defaultPrecision));
1192 }
1193 return 0;
1194 }
1195
1196 /** Returns true, if GLSL version requires default precision, i.e. ES2 or GLSL [1.30 .. 1.50[. */
1197 constexpr static bool requiresDefaultPrecision(const GL& gl) noexcept {
1198 if( gl.glProfile().nativeGLES() ) {
1199 return true;
1200 }
1202 }
1203
1204 /** Returns true, if GL3 GLSL version requires default precision, i.e. GLSL [1.30 .. 1.50[. */
1205 constexpr static bool requiresGL3DefaultPrecision(const GL& gl) noexcept {
1206 if( !gl.glProfile().nativeGLES() ) {
1207 const jau::util::VersionNumber& glslVersion = gl.glProfile().glslVersion();
1208 return glslVersion >= Version1_30 && glslVersion < Version1_50;
1209 } else {
1210 return false;
1211 }
1212 }
1213
1214 /**
1215 * Default customization of this shader source code.
1216 * @param gl a GL context, which must have been made current once
1217 * @param preludeVersion if true {@link GLContext#getGLSLVersionString()} is preluded, otherwise not.
1218 * @param addDefaultPrecision if <code>true</code> default precision source code line(s) are added, i.e.
1219 * {@link #es2_default_precision_vp}, {@link #es2_default_precision_fp},
1220 * {@link #gl3_default_precision_vp_gp}, {@link #gl3_default_precision_fp} or none,
1221 * depending on the {@link GLContext#getGLSLVersionNumber() GLSL version} being used.
1222 * @return the index after the inserted data, maybe 0 if nothing has be inserted.
1223 * @see #addGLSLVersion(GL2ES2)
1224 * @see #addDefaultShaderPrecision(GL2ES2, int)
1225 */
1226 size_t defaultShaderCustomization(const GL& gl, bool preludeVersion, bool addDefaultPrecision) {
1227 size_t pos;
1228 if( preludeVersion ) {
1229 pos = addGLSLVersion(gl);
1230 } else {
1231 pos = 0;
1232 }
1233 if( addDefaultPrecision ) {
1234 pos = addDefaultShaderPrecision(gl, pos);
1235 }
1236 return pos;
1237 }
1238
1239 /**
1240 * Default customization of this shader source code.
1241 * @param gl a GL context, which must have been made current once
1242 * @param preludeVersion if true {@link GLContext#getGLSLVersionString()} is preluded, otherwise not.
1243 * @param esDefaultPrecision optional default precision source code line(s) preluded if not null and if {@link GL#isGLES()}.
1244 * You may use {@link #es2_default_precision_fp} for fragment shader and {@link #es2_default_precision_vp} for vertex shader.
1245 * @return the index after the inserted data, maybe 0 if nothing has be inserted.
1246 * @see #addGLSLVersion(GL2ES2)
1247 * @see #addDefaultShaderPrecision(GL2ES2, int)
1248 */
1249 size_t defaultShaderCustomization(const GL& gl, bool preludeVersion, const string_t& esDefaultPrecision) {
1250 size_t pos;
1251 if( preludeVersion ) {
1252 pos = addGLSLVersion(gl);
1253 } else {
1254 pos = 0;
1255 }
1256 if( gl.glProfile().nativeGLES() && esDefaultPrecision.length()>0 ) {
1257 pos = insertShaderSource(0, pos, esDefaultPrecision);
1258 } else {
1259 pos = addDefaultShaderPrecision(gl, pos);
1260 }
1261 return pos;
1262 }
1263
1264 //----------------------------------------------------------------------
1265 // Internals only below this point
1266 //
1267
1268 private:
1269 source_list_t m_shaderSource;
1270 bytes_t m_shaderBinary;
1271 GLenum m_shaderBinaryFormat;
1272 shader_list_t m_shader;
1273 GLenum m_shaderType;
1274 size_t m_id;
1275 bool m_compiled;
1276
1277 static size_t nextID() { return m_nextID++; }
1278 static std::atomic<size_t> m_nextID;
1279 };
1280
1281 inline std::ostream& operator<<(std::ostream& out, const ShaderCode& v) {
1282 return out << v.toString();
1283 }
1284
1285 /**@}*/
1286}
1287
1288// injecting specialization of std::hash to namespace std of our types above
1289namespace std
1290{
1291 /** \addtogroup Gamp_GLSL
1292 *
1293 */
1294
1295 template<> struct hash<gamp::render::gl::glsl::ShaderCode> {
1296 std::size_t operator()(gamp::render::gl::glsl::ShaderCode const& a) const noexcept {
1297 return a.hash_code();
1298 }
1299 };
1300 template<> struct hash<gamp::render::gl::glsl::ShaderCodeRef> {
1301 std::size_t operator()(gamp::render::gl::glsl::ShaderCodeRef const& a) const noexcept {
1302 return a->hash_code();
1303 }
1304 };
1305
1306 /**@}*/
1307}
1308
1309#endif // GAMP_GLSLSHADERCODE_HPP_
Convenient shader code class to use and instantiate vertex or fragment programs.
size_t insertShaderSource(size_t shaderIdx, size_t position, stringview_t data) noexcept
Adds data at position in shader source for shader shaderIdx.
ShaderCode(Private, GLenum type, size_t count, const source_list_t &sources) noexcept
Private ctor for `ShaderCodeRef create(...).
static ShaderCodeRef create(GL &gl, GLenum type, size_t count, stringview_t srcRoot, const string_list_t &srcBasenames, stringview_t binRoot, stringview_t binBasename) noexcept
Simplified variation of create(GL2ES2, int, int, Class, String, String[], String, String,...
static constexpr bool requiresGL3DefaultPrecision(const GL &gl) noexcept
Returns true, if GL3 GLSL version requires default precision, i.e.
static constexpr std::string_view gl3_default_precision_fp
Default precision of GLSL ≥ 1.30 as required until < 1.50 for fragment-shader: {@value gl3_default_pr...
static constexpr std::string_view SUFFIX_TESS_CONTROL_BINARY
Unique resource suffix for GL4#GL_TESS_CONTROL_SHADER in binary: {@value}
GLenum shaderType() const noexcept
constexpr bool operator==(const ShaderCode &rhs) const noexcept
constexpr std::size_t hash_code() const noexcept
constexpr size_t id() const noexcept
Returns the unique shader id for successfully created instances, zero if instance creation failed.
static string_t createExtensionDirective(const string_t &extensionName, const string_t &behavior)
Creates a GLSL extension directive.
static ShaderCodeRef create(GL &gl, GLenum type, size_t count, const string_list_t &sourceFiles) noexcept
Creates a complete ShaderCode object while reading all shader source of sourceFiles,...
size_t replaceInShaderSource(const string_t &oldName, const string_t &newName) noexcept
Replaces oldName with newName in all shader sources.
static ShaderCodeRef create(GL &gl, GLenum type, size_t count, stringview_t srcRoot, const string_list_t &srcBasenames, stringview_t srcSuffixOpt, stringview_t binRoot, stringview_t binBasename, stringview_t binSuffixOpt) noexcept
Convenient creation method for instantiating a complete ShaderCode object either from source code usi...
static constexpr std::string_view SUFFIX_FRAGMENT_SOURCE
Unique resource suffix for GL2ES2#GL_FRAGMENT_SHADER in source code: {@value}
static constexpr std::string_view WARN
Behavior for GLSL extension directive, see createExtensionDirective(String, String),...
static constexpr std::string_view SUFFIX_COMPUTE_BINARY
Unique resource suffix for GL3ES3#GL_COMPUTE_SHADER in binary: {@value}
static constexpr std::string_view SUFFIX_TESS_EVALUATION_BINARY
Unique resource suffix for GL4#GL_TESS_EVALUATION_SHADER in binary: {@value}
static constexpr std::string_view SUFFIX_TESS_EVALUATION_SOURCE
Unique resource suffix for GL4#GL_TESS_EVALUATION_SHADER in source code: {@value}
static constexpr std::string_view SUFFIX_VERTEX_SOURCE
Unique resource suffix for GL2ES2#GL_VERTEX_SHADER in source code: {@value}
size_t defaultShaderCustomization(const GL &gl, bool preludeVersion, bool addDefaultPrecision)
Default customization of this shader source code.
bool compile(GL &gl, bool verbose=false)
GLenum shaderBinaryFormat() const noexcept
static constexpr std::string_view SUFFIX_VERTEX_BINARY
Unique resource suffix for GL2ES2#GL_VERTEX_SHADER in binary: {@value}
static string_t readShaderSource(stringview_t path) noexcept
Reads shader source from file located in path, returning the string.
constexpr bool isCompiled() const noexcept
Returns true if this instance is valid and compiled.
static ShaderCodeRef create(GLenum type, size_t count, GLenum binFormat, const bytes_t &binary) noexcept
const bytes_t & shaderBinary() const noexcept
static constexpr std::string_view SUB_PATH_NVIDIA
Unique relative path for binary shader resources for NVIDIA: {@value}
static constexpr std::string_view REQUIRE
Behavior for GLSL extension directive, see createExtensionDirective(String, String),...
static constexpr std::string_view ENABLE
Behavior for GLSL extension directive, see createExtensionDirective(String, String),...
static constexpr std::string_view gl3_default_precision_vp_gp
Default precision of GLSL ≥ 1.30 as required until < 1.50 for vertex-shader or geometry-shader: {@val...
ShaderCode(Private, GLenum type, size_t count, GLenum binFormat, const bytes_t &binary) noexcept
Private ctor for `ShaderCodeRef create(...).
static ShaderCodeRef create(GLenum type, size_t count, const source_list_t &sources) noexcept
static ShaderCodeRef create(GL &gl, GLenum type, stringview_t srcRoot, stringview_t binRoot, stringview_t basename, stringview_t srcSuffixOpt, stringview_t binSuffixOpt) noexcept
Simplified variation of create(GL2ES2, int, int, Class, String, String[], String, String,...
static constexpr std::string_view SUFFIX_TESS_CONTROL_SOURCE
Unique resource suffix for GL4#GL_TESS_CONTROL_SHADER in source code: {@value}
constexpr bool isValid() const noexcept
Returns true if this instance is valid, i.e.
const shader_list_t & shader() const noexcept
string_t shaderTypeStr() const noexcept
static constexpr std::string_view DISABLE
Behavior for GLSL extension directive, see createExtensionDirective(String, String),...
static std::string_view getFileSuffix(bool binary, GLenum type) noexcept
Returns a unique suffix for shader resources as follows:
static bool readShaderSource(stringview_t path0, string_t &result) noexcept
Reads shader source from file located in path and appends it to result.
static constexpr std::string_view SUFFIX_GEOMETRY_BINARY
Unique resource suffix for GL3#GL_GEOMETRY_SHADER in binary: {@value}
static constexpr std::string_view es3_default_precision_fp
Default precision of ES3 for fragment-shader: {@value es3_default_precision_fp}, same as for vertex-s...
static constexpr std::string_view es2_default_precision_vp
Default precision of ES2 for vertex-shader: {@value es2_default_precision_vp}.
const source_list_t & shaderSource() const noexcept
static constexpr std::string_view SUFFIX_FRAGMENT_BINARY
Unique resource suffix for GL2ES2#GL_FRAGMENT_SHADER in binary: {@value}
size_t addGLSLVersion(const GL &gl) noexcept
Add GLSL version at the head of this shader source code.
size_t insertShaderSourceFile(size_t shaderIdx, size_t position, const string_t &path) noexcept
Adds shader source located in path, either relative to the location or absolute as-is at position in ...
size_t insertShaderSource(size_t shaderIdx, stringview_t tag, size_t fromIndex, stringview_t data) noexcept
Adds data after the line containing tag.
static constexpr std::string_view SUFFIX_COMPUTE_SOURCE
Unique resource suffix for GL3ES3#GL_COMPUTE_SHADER in source code: {@value}
static ShaderCodeRef create(GL &gl, GLenum type, stringview_t srcRoot, stringview_t binRoot, stringview_t basename) noexcept
Simplified variation of create(GL2ES2, int, Class, String, String, String, String,...
static constexpr std::string_view es3_default_precision_vp
Default precision of ES3 for vertex-shader: {@value es3_default_precision_vp}.
size_t addDefaultShaderPrecision(const GL &gl, size_t pos)
Adds default precision to source code at given position if required, i.e.
static constexpr std::string_view es2_default_precision_fp
Default precision of ES2 for fragment-shader: {@value es2_default_precision_fp}.
static std::string_view getBinarySubPath(GLenum binFormat) noexcept
Returns a unique relative path for binary shader resources as follows:
static constexpr std::string_view SUFFIX_GEOMETRY_SOURCE
Unique resource suffix for GL3#GL_GEOMETRY_SHADER in source code: {@value}
static constexpr bool requiresDefaultPrecision(const GL &gl) noexcept
Returns true, if GLSL version requires default precision, i.e.
size_t defaultShaderCustomization(const GL &gl, bool preludeVersion, const string_t &esDefaultPrecision)
Default customization of this shader source code.
static bool createAndCompileShader(GL &gl, shader_list_t &shader, GLenum shaderType, const source_list_t &sources, bool verbose=false)
Creates shader.size() new shaders, stored in the shader list.
static void deleteShader(GL &, const shader_list_t &shaders) noexcept
static bool createAndLoadShader(GL &gl, shader_list_t &shader, GLenum shaderType, GLenum binFormat, const bytes_t &bin, bool verbose=false)
Creates shader.size() new shaders, stored in the shader list.
static name_list_t getShaderBinaryFormats(GL &gl) noexcept
If supported, queries the natively supported shader binary formats using GL2ES2#GL_NUM_SHADER_BINARY_...
static bool isShaderCompilerAvailable(GL &gl) noexcept
Returns true if a hader compiler is available, otherwise false.
Simple version number class containing a version number either being defined explicit or derived from...
#define ERR_PRINT(...)
Use for unconditional error messages, prefix '[elapsed_time] Error @ FILE:LINE FUNC: '.
Definition debug.hpp:112
std::ostream & operator<<(std::ostream &os, const T v)
bool exists(const std::string &path, bool verbose_on_error=false) noexcept
Returns true if path exists and is accessible.
std::string dirname(const std::string_view &path) noexcept
Return stripped last component from given path separated by /, excluding the trailing separator /.
bool isAbsolute(const std::string_view &path) noexcept
Returns true if first character is / or - in case of Windows - \\.
@ null
Denotes a func::null_target_t.
std::vector< uint8_t > bytes_t
std::vector< GLenum > name_list_t
std::string_view stringview_t
std::shared_ptr< ShaderCode > ShaderCodeRef
std::vector< string_t > string_list_t
std::vector< GLuint > shader_list_t
std::vector< string_list_t > source_list_t
static constexpr jau::util::VersionNumber Version1_30
Version 1.30, i.e.
static constexpr GLenum GL_NVIDIA_PLATFORM_BINARY_NV
static constexpr jau::util::VersionNumber Version1_50
Version 1.50, i.e.
constexpr std::string_view shaderTypeString(GLenum type) noexcept
constexpr bool isValidShaderType(GLenum type) noexcept
@ verbose
Verbose operations (debugging).
std::string resolve_asset(const std::string &asset_file, bool lookup_direct=false) noexcept
Definition gamp.cpp:37
void trimInPlace(std::string &s) noexcept
trim in place
Gamp: Graphics, Audio, Multimedia and Processing Framework (Native C++, WebAssembly,...
Definition Gamp.hpp:29
void PLAIN_PRINT(const bool printPrefix, const char *format,...) noexcept
Use for unconditional plain messages, prefix '[elapsed_time] ' if printPrefix == true.
Definition debug.cpp:258
STL namespace.
std::size_t operator()(gamp::render::gl::glsl::ShaderCodeRef const &a) const noexcept
std::size_t operator()(gamp::render::gl::glsl::ShaderCode const &a) const noexcept