From 2ec33ff1c0ee18f0de6f0628fd8d3daaa4792238 Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Mon, 22 Jan 2024 19:17:06 -0500 Subject: [PATCH] Start reworking Program class template --- libs/gltk/gltk/program.d | 354 ++++++++++++++++++++++++++++++--------- 1 file changed, 276 insertions(+), 78 deletions(-) diff --git a/libs/gltk/gltk/program.d b/libs/gltk/gltk/program.d index 670bacd..1a3abe5 100644 --- a/libs/gltk/gltk/program.d +++ b/libs/gltk/gltk/program.d @@ -4,21 +4,187 @@ import gltk.shader; import std.string; import gl; -class Program(uniforms...) +/** + * OpenGL program object. + * + * This template allows creating a program class with specific uniforms and + * attributes. Uniforms specified via the uniforms template argument will + * generate setter functions to set the uniform values. Attributes specified + * via the attributes template argument will bind attribute locations after + * the program is linked. + * + * Uniforms are specified as an associative array with uniform names as keys + * and uniform types as values. The uniform types are the strings following + * the "glProgramUniform" prefix (for example, "1i", "3f", "Matrix4fv", etc.). + * + * Attributes are specified as an associative array with attribute names as + * keys and attribute locations as values. + */ +class Program(string[string] uniforms = [], int[string] attributes = []) { - /* The number of template parameters must be a multiple of 2. */ - static assert((uniforms.length % 2) == 0); - + /** Program ID. */ private GLuint m_id; - static foreach (i, v; uniforms) + /** + * Uniform type attributes. + */ + private struct UniformTypeInfo { - static if ((i % 2) == 0) + ubyte count; + ubyte count2; + char type; + bool v; + + public @property bool matrix() const { - mixin("private GLint m_uniform_" ~ v ~ ";"); + return count2 != 0; } } + /** + * Get uniform type attributes from uniform type string. + */ + private static UniformTypeInfo uniform_type_info(string uniform_type) + { + switch (uniform_type) + { + case "1f": return UniformTypeInfo(1, 0, 'f', false); + case "2f": return UniformTypeInfo(2, 0, 'f', false); + case "3f": return UniformTypeInfo(3, 0, 'f', false); + case "4f": return UniformTypeInfo(4, 0, 'f', false); + + case "1i": return UniformTypeInfo(1, 0, 'i', false); + case "2i": return UniformTypeInfo(2, 0, 'i', false); + case "3i": return UniformTypeInfo(3, 0, 'i', false); + case "4i": return UniformTypeInfo(4, 0, 'i', false); + + case "1ui": return UniformTypeInfo(1, 0, 'u', false); + case "2ui": return UniformTypeInfo(2, 0, 'u', false); + case "3ui": return UniformTypeInfo(3, 0, 'u', false); + case "4ui": return UniformTypeInfo(4, 0, 'u', false); + + case "1fv": return UniformTypeInfo(1, 0, 'f', true); + case "2fv": return UniformTypeInfo(2, 0, 'f', true); + case "3fv": return UniformTypeInfo(3, 0, 'f', true); + case "4fv": return UniformTypeInfo(4, 0, 'f', true); + + case "1iv": return UniformTypeInfo(1, 0, 'i', true); + case "2iv": return UniformTypeInfo(2, 0, 'i', true); + case "3iv": return UniformTypeInfo(3, 0, 'i', true); + case "4iv": return UniformTypeInfo(4, 0, 'i', true); + + case "1uiv": return UniformTypeInfo(1, 0, 'u', true); + case "2uiv": return UniformTypeInfo(2, 0, 'u', true); + case "3uiv": return UniformTypeInfo(3, 0, 'u', true); + case "4uiv": return UniformTypeInfo(4, 0, 'u', true); + + case "Matrix2fv": return UniformTypeInfo(2, 2, 'f', true); + case "Matrix3fv": return UniformTypeInfo(3, 3, 'f', true); + case "Matrix4fv": return UniformTypeInfo(4, 4, 'f', true); + case "Matrix2x3fv": return UniformTypeInfo(2, 3, 'f', true); + case "Matrix3x2fv": return UniformTypeInfo(3, 2, 'f', true); + case "Matrix2x4fv": return UniformTypeInfo(2, 4, 'f', true); + case "Matrix4x2fv": return UniformTypeInfo(4, 2, 'f', true); + case "Matrix3x4fv": return UniformTypeInfo(3, 4, 'f', true); + case "Matrix4x3fv": return UniformTypeInfo(4, 3, 'f', true); + + default: throw new Exception("Unknown uniform type"); + } + } + + /** + * Get uniform setter function parameter declaration list. + */ + private static string uniform_param_decl_list(string uniform_type) + { + UniformTypeInfo uti = uniform_type_info(uniform_type); + string type; + if (uti.type == 'i') + { + type = "GLint"; + } + else if (uti.type == 'u') + { + type = "GLuint"; + } + else if (uti.type == 'f') + { + type = "GLfloat"; + } + assert(type != ""); + if (uti.v) + { + return "const(" ~ type ~ ") * v"; + } + string[] decls; + for (int i = 0; i < uti.count; i++) + { + decls ~= type ~ " v" ~ cast(char)('0' + i); + } + return decls.join(", "); + } + + /** + * Get uniform setter function argument list. + */ + private static string uniform_param_list(string uniform_type) + { + UniformTypeInfo uti = uniform_type_info(uniform_type); + if (uti.v) + { + return "v"; + } + string[] vees; + for (int i = 0; i < uti.count; i++) + { + vees ~= "v" ~ cast(char)('0' + i); + } + return vees.join(", "); + } + + /* + * Define uniform setter function for each uniform specified in the + * uniforms template parameter. + */ + static foreach (uniform_name, uniform_type; uniforms) + { + mixin("private GLint m_uniform_" ~ uniform_name ~ ";"); + mixin("public void set_" ~ uniform_name ~ "(" ~ uniform_param_decl_list(uniform_type) ~ ") const" ~ + "{" ~ + " glProgramUniform" ~ uniform_type ~ "(m_id, m_uniform_" ~ uniform_name ~ ", " ~ (uniform_type_info(uniform_type).v ? "1, " : "") ~ (uniform_type_info(uniform_type).matrix ? "false, " : "") ~ uniform_param_list(uniform_type) ~ ");" ~ + "}"); + static if (uniform_type_info(uniform_type).v) + { + mixin("public void set_" ~ uniform_name ~ "(GLsizei count, " ~ uniform_param_decl_list(uniform_type) ~ ") const" ~ + "{" ~ + " glProgramUniform" ~ uniform_type ~ "(m_id, m_uniform_" ~ uniform_name ~ ", count, " ~ (uniform_type_info(uniform_type).matrix ? "false, " : "") ~ uniform_param_list(uniform_type) ~ ");" ~ + "}"); + } + } + + /** + * Construct program object. + * + * There are multiple ways to construct a program object: + * + * (1) Pass a single string argument to the constructor. The string + * specifies the source for program shaders. A source line matching + * "vertex:", "fragment:", "compute:", "tess_control:", + * "tess_evaluation:", or "geometry:" begins the source section for + * the corresponding shader. A Shader object is automatically created + * from the source in its corresponding section. Any source lines + * before a line indicating the start of a shader source are copied to + * all shaders. This allows, for example, specifying the shader source + * version or any uniform declarations that are common to all shaders. + * The program is linked after shaders are created and attached. + * + * (2) Pass one or more Shader objects to the constructor. The program will + * attach the given shaders and then link the program. + * + * (3) Pass no arguments to the constructor. In this case the program will + * not yet be linked and the user should call attach_shader() for each + * shader to add and then link() to link the program. + */ this(Args...)(Args args) { m_id = glCreateProgram(); @@ -32,22 +198,37 @@ class Program(uniforms...) } } + /** + * Destruct program object. + */ ~this() { glDeleteProgram(m_id); } - void attach_shader(Shader shader) const - { - glAttachShader(m_id, shader.id); - } - - void bind_attrib_location(string name, uint index) const + /** + * Bind an attribute to a specific location. + */ + public void bind_attrib_location(string name, uint index) const { glBindAttribLocation(m_id, index, name.toStringz()); } - void link() + /** + * Attach a Shader object to the program. + */ + public void attach_shader(Shader shader) const + { + glAttachShader(m_id, shader.id); + } + + /** + * Link the program. + * + * This function should only be called if no arguments are passed to the + * constructor. + */ + public void link() { glLinkProgram(m_id); @@ -69,21 +250,28 @@ class Program(uniforms...) throw new Exception(message); } - static foreach(i, v; uniforms) + static foreach(uniform_name, uniform_type; uniforms) { - static if ((i % 2) == 0) - { - mixin("m_uniform_" ~ v ~ " = glGetUniformLocation(m_id, \"" ~ v ~ "\");"); - } + mixin("m_uniform_" ~ uniform_name ~ " = glGetUniformLocation(m_id, \"" ~ uniform_name ~ "\");"); + } + static foreach(attribute_name, attribute_location; attributes) + { + glBindAttribLocation(m_id, attribute_location, attribute_name.toStringz()); } } - GLint get_uniform_location(string uniform_name) const + /** + * Get uniform location. + */ + public GLint get_uniform_location(string uniform_name) const { return glGetUniformLocation(m_id, uniform_name.toStringz()); } - void get_uniform_locations(Args...)(string uniform_name, GLint * uniform_location, Args args) const + /** + * Get multiple uniform locations. + */ + public void get_uniform_locations(Args...)(string uniform_name, GLint * uniform_location, Args args) const { *uniform_location = get_uniform_location(uniform_name); static if (args.length > 0u) @@ -92,89 +280,99 @@ class Program(uniforms...) } } - @property GLuint id() const + /** + * Get the program ID. + */ + public @property GLuint id() const { return m_id; } - void use() const + /** + * Use the program. + */ + public void use() const { glUseProgram(m_id); } - private static string uniform_param_decl_list(string spec) + /** + * Internal constructor helper method. + */ + private void build(string program_source) { - int count = spec[0] - '0'; - string type; - if (spec[1] == 'i') + string common_source; + string shader_source; + GLenum shader_type; + + foreach (line; program_source.lineSplitter) { - type = "GLint"; - } - else if (spec[1..2] == "ui") - { - type = "GLuint"; - } - else if (spec[1] == 'f') - { - type = "GLfloat"; - } - assert(type != ""); - if (spec[$ - 1] == 'v') - { - return "uint count, const " ~ type ~ " * v"; - } - else - { - string[] decls; - for (int i = 0; i < count; i++) + GLenum new_shader_type; + switch (line) { - decls ~= type ~ " v" ~ cast(char)('0' + i); + case "fragment:": + new_shader_type = GL_FRAGMENT_SHADER; + break; + case "vertex:": + new_shader_type = GL_VERTEX_SHADER; + break; + case "compute:": + new_shader_type = GL_COMPUTE_SHADER; + break; + case "tess_control:": + new_shader_type = GL_TESS_CONTROL_SHADER; + break; + case "tess_evaluation:": + new_shader_type = GL_TESS_EVALUATION_SHADER; + break; + case "geometry:": + new_shader_type = GL_GEOMETRY_SHADER; + break; + default: + break; } - return decls.join(", "); - } - } - - private static string uniform_param_list(string spec) - { - int count = spec[0] - '0'; - if (spec[$ - 1] == 'v') - { - return "count, v"; - } - else - { - string[] vees; - for (int i = 0; i < count; i++) + if (new_shader_type != 0) { - vees ~= "v" ~ cast(char)('0' + i); + if (shader_type != 0) + { + Shader shader = new Shader(shader_type, shader_source); + attach_shader(shader); + shader_source = ""; + } + shader_type = new_shader_type; + shader_source = common_source[]; + } + else if (shader_type != 0) + { + shader_source ~= line; + } + else + { + common_source ~= line; } - return vees.join(", "); } - } - static foreach (i, v; uniforms) - { - static if ((i % 2) == 0) + if (shader_type != 0 && shader_source != "") { - mixin("void set_" ~ v ~ "(" ~ uniform_param_decl_list(uniforms[i + 1]) ~ ")" ~ - "{" ~ - " glUniform" ~ uniforms[i + 1] ~ "(m_uniform_" ~ v ~ ", " ~ uniform_param_list(uniforms[i + 1]) ~ ");" ~ - "}"); + Shader shader = new Shader(shader_type, shader_source); + attach_shader(shader); } + + link(); } + /** + * Internal constructor helper method. + */ private void build(Args...)(Shader s, Args args) { attach_shader(s); build(args); } - private void build(Args...)(string attrib_name, uint index, Args args) - { - bind_attrib_location(attrib_name, index); - build(args); - } - + /** + * Internal constructor helper method. + */ private void build() { link();