libacfutils
A general purpose library of utility functions designed to make it easier to develop addons for the X-Plane flight simulator.
Loading...
Searching...
No Matches
shader.h
1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license in the file COPYING
10 * or http://www.opensource.org/licenses/CDDL-1.0.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file COPYING.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2021 Saso Kiselkov. All rights reserved.
24 */
25
26#ifndef _ACF_UTILS_SHADER_H_
27#define _ACF_UTILS_SHADER_H_
28
29#include <stdarg.h>
30#include <time.h>
31
32#include "delay_line.h"
33#include "glew.h"
34
35#ifdef __cplusplus
36extern "C" {
37#endif
38
39typedef struct {
40 const char *name;
41 GLuint idx;
43
44/*
45 * A specialization constant to pass to the shader loading routines as part
46 * of a shader_info_t structure to specialize SPIR-V shaders. If not necessary,
47 * you can provide NULL in the spec_const field in shader_info_t to mean "no
48 * specialization constants required". If you do want to employ specialization
49 * constants, set `is_last' on the last entry in the list of specialization
50 * constants. Please note that the last entry is ignored, so simply use the
51 * last entry as a list terminator, not as an actual specialization constants
52 * that you want to pass to the loader. To enforce this, the loader checks
53 * that the last entry in the list has both idx and val set to zero.
54 * `is_float' is a hint used when constructing defines for a GLSL fallback
55 * program. If set to true, then `val' is interpreted as an IEEE-754-encoded
56 * floating point value.
57 */
58typedef struct {
59 bool_t is_last;
60 GLuint idx;
61 GLuint val;
62 bool_t is_float;
64
65/*
66 * Shader construction information structure. This lets you specify a shader
67 * to be used in the construction of a shader program. The fields have the
68 * following meanings:
69 *
70 * @field filename An optional filename (set to NULL if not used). This
71 * attempts to load the shader from the provided filename. The filename
72 * extension and case IS significant. Use ".spv" for SPIR-V shaders.
73 * If a filename ends in any other extension other than ".spv", it is
74 * treated as a GLSL shader.
75 * If you provide a SPIR-V shader and SPIR-V is not supported by the
76 * driver, we search for a fallback shader with the extension replaced
77 * ".vert" for vertex shaders and ".frag" for fragment shaders. The
78 * second fallback filename optiona ttempted is ".glsl.vert" for vertex
79 * shaders and ".glsl.frag" for fragment shaders.
80 * For example, if you are loading a vertex shader named "myshader.spv",
81 * if the driver doesn't support SPIR-V, the library also looks for
82 * "myshader.vert" and "myshader.glsl".
83 * If SPIR-V is supported, the library ONLY attempts to load the SPIR-V
84 * shader.
85 * @field glsl Direct GLSL program text to use in compiling the shader.
86 * This field must ONLY be used in place of `filename'. It is NOT legal
87 * set both `filename' and `glsl'. However, you MUST provide either a
88 * `filename' or `glsl'.
89 * @field entry_pt A SPIR-V shader entry point. If the shader isn't SPIR-V,
90 * this field is ignored. If the shader IS SPIR-V and entry_pt = NULL,
91 * the loader falls back using "main" as the SPIR-V shader entry point.
92 * @field spec_const An optional array of specialization constants to be used
93 * during specialization of a SPIR-V shader. If specialization constants
94 * aren't required, set this field to NULL. Otherwise set it to a list
95 * of shader_spec_const_t structures. This list MUST be terminated by
96 * a shader_spec_const_t structure with the `is_last' field set to
97 * B_TRUE and both the `idx' and `val' fields set to 0.
98 */
99typedef struct {
100 const char *filename;
101 const char *glsl;
102 const char *entry_pt;
103 const shader_spec_const_t *spec_const;
105
106/*
107 * Shader program construction information structure. You must pass this
108 * to shader_prog_from_info to construct a shader program ready for use in
109 * render passes. Please note that you MUST provide at least one of `vert'
110 * or `frag'. The fields have the following meanings:
111 *
112 * @field progname Readable program name that can be used in error messages
113 * to identify the shader that encountered a loading problem. This is
114 * not used during shader execution.
115 * @field vert Vertex shader specification. Set to NULL if not used.
116 * See shader_info_t for details.
117 * @field vert Fragment shader specification. Set to NULL if not used.
118 * See shader_info_t for details.
119 * @field comp Compute shader specification. Set to NULL if not used.
120 * See shader_info_t for details.
121 * @field attr_binds Vertex attribute array bindings. Set to NULL if not used.
122 * See shader_attr_bind_t for details.
123 */
124typedef struct {
125 const char *progname;
126 const shader_info_t *vert;
127 const shader_info_t *frag;
128 const shader_info_t *comp;
129 const shader_attr_bind_t *attr_binds;
131
132/*
133 * Apparently these are the standard vertex attribute indices:
134 * gl_Vertex 0
135 * gl_Normal 2
136 * gl_Color 3
137 * gl_SecondaryColor 4
138 * gl_FogCoord 5
139 * gl_MultiTexCoord0 8
140 * gl_MultiTexCoord1 9
141 * gl_MultiTexCoord2 10
142 * gl_MultiTexCoord3 11
143 * gl_MultiTexCoord4 12
144 * gl_MultiTexCoord5 13
145 * gl_MultiTexCoord6 14
146 * gl_MultiTexCoord7 15
147 */
148enum {
149 VTX_ATTRIB_POS = 0,
150 VTX_ATTRIB_NORM = 2,
151 VTX_ATTRIB_TEX0 = 8,
152 VTX_ATTRIB_TEX1 = 9
153};
154
155#define DEFAULT_VTX_ATTRIB_BINDINGS \
156 "vtx_pos", VTX_ATTRIB_POS, "vtx_norm", VTX_ATTRIB_NORM, \
157 "vtx_tex0", VTX_ATTRIB_TEX0, "vtx_tex1", VTX_ATTRIB_TEX1
158
159/* MSVC doesn't speak C99 */
160#if defined(__GNUC__) || defined(__clang__)
161static shader_attr_bind_t UNUSED_ATTR default_vtx_attr_binds[] = {
162 { .name = "vtx_pos", .idx = VTX_ATTRIB_POS },
163 { .name = "vtx_norm", .idx = VTX_ATTRIB_NORM },
164 { .name = "vtx_tex0", .idx = VTX_ATTRIB_TEX0 },
165 { .name = "vtx_tex1", .idx = VTX_ATTRIB_TEX1 },
166 { /* list terminator */ }
167};
168#endif /* defined(__GNUC__) || defined(__clang__) */
169
170#define shader_prog_from_file ACFSYM(shader_prog_from_file)
171API_EXPORT GLuint shader_prog_from_file(const char *progname,
172 const char *vert_file, const char *frag_file, ...);
173
174#define shader_prog_from_text ACFSYM(shader_prog_from_text)
175API_EXPORT GLuint shader_prog_from_text(const char *progname,
176 const char *vert_shader_text, const char *frag_shader_text, ...);
177
178#define shader_prog_from_info ACFSYM(shader_prog_from_info)
179API_EXPORT GLuint shader_prog_from_info(const char *dirpath,
180 const shader_prog_info_t *info);
181
182/*
183 * Shader Objects
184 *
185 * A shader object (shader_obj_t) is a convenient shader control mechanism
186 * that can be used to efficiently perform attribute and uniform location
187 * lookups. Rather than having to store each location in your own code
188 * and work with a raw GLuint shader program, a shader_obj_t takes care
189 * of loading the shader from disk from a shader_prog_info_t and also
190 * performs all attribute and uniform lookups ahead of time. You then
191 * simply refer to attributes and uniforms by an enum value, rather than
192 * by name, removing the costly name lookup in the driver. This facility
193 * is generic, so it allows for a single code path of utilizing a shader
194 * in your code, while allowing for substituting the shader as needed
195 * based on rendering needs.
196 *
197 * To initialize a shader object, use `shader_obj_init' and then destroy
198 * the object using `shader_obj_fini'. On allocation, make sure the
199 * shader_obj_t is set to all zeros (e.g. using safe_calloc). This way,
200 * you can call `shader_obj_fini' even if `shader_obj_init' was never
201 * called.
202 *
203 * Once initialized, you can then bind the shader program using
204 * `shader_obj_bind_prog', which is equivalent to calling `glUseProgram'
205 * with the shader's program number. To fetch attribute and uniform
206 * locations, you use `shader_obj_get_a' and `shader_obj_get_u' with
207 * the enum describing the attribute or uniform you want the location of.
208 * Please note that the enum list must be contiguous. For efficiency
209 * reasons, the getter functions simply use the enum value as an index
210 * into the array of cached locations.
211 *
212 * CODE SAMPLE
213 *
214 * -- Static shader info --
215 * static const shader_info_t foo_vert_info = { .filename = "foo.vert.spv" };
216 * static const shader_info_t foo_frag_info = { .filename = "foo.frag.spv" };
217 * static const shader_prog_info_t foo_prog_info = {
218 * .progname = "foo_prog",
219 * .vert = &foo_vert_info,
220 * .frag = &foo_frag_info
221 * };
222 * -- Attribute definitions --
223 * enum {
224 * A_VTX_POS,
225 * A_VTX_NORM,
226 * A_VTX_TEX0
227 * NUM_ATTRS
228 * };
229 * static const char *attr_names[NUM_ATTRS] = {
230 * [A_VTX_POS] = "vtx_pos",
231 * [A_VTX_NORM] = "vtx_norm",
232 * [A_VTX_TEX0] = "vtx_tex0"
233 * };
234 * -- Uniform definitions --
235 * enum {
236 * U_PROJ_MATRIX,
237 * U_MV_MATRIX,
238 * NUM_UNIFORMS
239 * };
240 * static const char *uniform_names[NUM_UNIFORMS] = {
241 * [U_PROJ_MATRIX] = "proj_matrix",
242 * [U_MV_MATRIX] = "mv_matrix"
243 * };
244 *
245 * -- Initializing a shader_obj_t
246 * static shader_obj_t foo_so;
247 * if (!shader_obj_init(&foo_so, "/foo/shader/dir", &foo_prog_info,
248 * attr_names, NUM_ATTRS, uniform_names, NUM_UNIFORMS)) {
249 * logMsg("shader %s load error", foo_prog_info.progname);
250 * }
251 *
252 * -- Utilizing a shader_obj_t during rendering
253 * shader_obj_bind(&foo_so);
254 * glUniformMatrix4fv(shader_obj_get_u(&foo_so, U_PROJ_MATRIX),
255 * 1, GL_FALSE, proj_matrix);
256 * glUniformMatrix4fv(shader_obj_get_u(&foo_so, U_MV_MATRIX),
257 * 1, GL_FALSE, mv_matrix);
258 *
259 * -- Destroying a shader_obj_t
260 * shader_obj_fini(&foo_so);
261 */
262#define SHADER_OBJ_MAX_ATTRS 128
263#define SHADER_OBJ_MAX_UNIFORMS 128
264typedef struct {
265 const shader_prog_info_t *info;
266 char *dirpath;
267 GLuint prog;
268 const char **attr_names;
269 unsigned num_attrs;
270 GLint attr_loc[SHADER_OBJ_MAX_ATTRS];
271 const char **uniform_names;
272 unsigned num_uniforms;
273 GLint uniform_loc[SHADER_OBJ_MAX_UNIFORMS];
274 delay_line_t check_delay;
275 time_t load_time;
277
278/*
279 * Initializes a shader_obj_t.
280 *
281 * @param obj The shader object to be initialized.
282 * @param dirpath The directory path containing the files of the shader.
283 * You can free this after calling shader_obj_init. The shader object
284 * copies it.
285 * @param info The shader_prog_info_t structure describing how the shader
286 * is to be constructed. You must NOT free this structure until calling
287 * `shader_obj_fini', the shader_obj_t doesn't copy it. Ideally this
288 * should be a `static const' object in the program.
289 * @param attr_names An optional array of attribute names. This parameter
290 * can be NULL, provided `num_attrs' is zero. You must NOT free this
291 * array until calling `shader_obj_fini', the shader_obj_t doesn't copy
292 * it. Ideally this should be a `static const' array in the program.
293 * ALL the elements of this name array must be valid strings (not NULL).
294 * @param num_attrs Number of elements in `attr_names'. This must be
295 * less than SHADER_OBJ_MAX_ATTRS (128).
296 * @param uniform_names An optional array of uniform names. This parameter
297 * can be NULL, provided `num_uniforms' is zero. You must NOT free this
298 * array until calling `shader_obj_fini', the shader_obj_t doesn't copy
299 * it. Ideally this should be a `static const' array in the program.
300 * ALL the elements of this name array must be valid strings (not NULL).
301 * @param num_uniforms Number of elements in `uniform_names'. This must be
302 * less than SHADER_OBJ_MAX_UNIFORMS (128).
303 */
304API_EXPORT bool_t shader_obj_init(shader_obj_t *obj,
305 const char *dirpath, const shader_prog_info_t *info,
306 const char **attr_names, unsigned num_attrs,
307 const char **uniform_names, unsigned num_uniforms);
308/*
309 * Destroys a shader_obj_t. If you allocated the the shader_obj_t so that its
310 * storage is zero-initialized, you can safely call this function even if you
311 * didn't call `shader_obj_init'. The `obj' argument must NOT be NULL.
312 */
313API_EXPORT void shader_obj_fini(shader_obj_t *obj);
314/*
315 * Performs a reload of a shader_obj_t from disk and refreshes all attribute
316 * and uniform locations. Use this if you have altered the shader on disk
317 * and want to start using the new version for rendering.
318 * This function returns B_TRUE if the reload was successful, B_FALSE if not.
319 * The old shader program is automatically destroyed only if the reload was
320 * successful.
321 */
322API_EXPORT bool_t shader_obj_reload(shader_obj_t *obj);
323/*
324 * Similar to shader_obj_reload, but instead only reloads the shader if
325 * the on-disk version has changed. To avoid excessive disk I/O, this only
326 * does the check every few seconds using an internal timer.
327 * The function returns B_TRUE if the shader was reloaded successfully.
328 * If the reload wasn't necessary, or failed, B_FALSE is returned instead.
329 */
330API_EXPORT bool_t shader_obj_reload_check(shader_obj_t *obj);
331
332/*
333 * Binds the shader object's program to the current OpenGL context. This
334 * is equivalent to calling `glUseProgram(shader_obj_get_prog(&shader_obj))'.
335 * The shader_obj_t must have previous been successfully initialized using
336 * shader_obj_init, otherwise this function trips an assertion.
337 */
338static inline void
339shader_obj_bind(const shader_obj_t *obj)
340{
341 ASSERT(obj != NULL);
342 ASSERT(obj->prog != 0);
343 glUseProgram(obj->prog);
344}
345
346/*
347 * Returns the OpenGL shader program number in a shader_obj_t. The
348 * shader_obj_t must have previous been successfully initialized using
349 * shader_obj_init, otherwise this function trips an assertion.
350 */
351static inline GLuint
352shader_obj_get_prog(const shader_obj_t *obj)
353{
354 ASSERT(obj != NULL);
355 ASSERT(obj->prog != 0);
356 return (obj->prog);
357}
358
359/*
360 * Returns an attribute location in the shader_obj_t. The shader_obj_t must
361 * have previously been successfully initialized using shader_obj_init. The
362 * attr_ID must be an index into the attr_names array previously used in
363 * `shader_obj_init'.
364 */
365static inline GLint
366shader_obj_get_a(shader_obj_t *obj, unsigned attr_ID)
367{
368 ASSERT(obj != NULL);
369 ASSERT3U(attr_ID, <, obj->num_attrs);
370 return (obj->attr_loc[attr_ID]);
371}
372
373/*
374 * Returns a uniform location in the shader_obj_t. The shader_obj_t must
375 * must have previously been successfully initialized using shader_obj_init.
376 * The uniform_ID must be a valid index into the uniform_names array
377 * previously used in `shader_obj_init'.
378 */
379static inline GLint
380shader_obj_get_u(shader_obj_t *obj, unsigned uniform_ID)
381{
382 ASSERT(obj != NULL);
383 ASSERT3U(uniform_ID, <, obj->num_uniforms);
384 return (obj->uniform_loc[uniform_ID]);
385}
386
387#ifdef __cplusplus
388}
389#endif
390
391#endif /* _ACF_UTILS_SHADER_H_ */
#define ASSERT3U(x, op, y)
Definition assert.h:210
#define ASSERT(x)
Definition assert.h:208
#define UNUSED_ATTR
Definition core.h:95