Start reworking Program class template

This commit is contained in:
Josh Holtrop 2024-01-22 19:17:06 -05:00
parent 1aa3650ee3
commit 2ec33ff1c0

View File

@ -4,21 +4,187 @@ import gltk.shader;
import std.string; import std.string;
import gl; 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. */ /** Program ID. */
static assert((uniforms.length % 2) == 0);
private GLuint m_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) this(Args...)(Args args)
{ {
m_id = glCreateProgram(); m_id = glCreateProgram();
@ -32,22 +198,37 @@ class Program(uniforms...)
} }
} }
/**
* Destruct program object.
*/
~this() ~this()
{ {
glDeleteProgram(m_id); glDeleteProgram(m_id);
} }
void attach_shader(Shader shader) const /**
{ * Bind an attribute to a specific location.
glAttachShader(m_id, shader.id); */
} public void bind_attrib_location(string name, uint index) const
void bind_attrib_location(string name, uint index) const
{ {
glBindAttribLocation(m_id, index, name.toStringz()); 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); glLinkProgram(m_id);
@ -69,21 +250,28 @@ class Program(uniforms...)
throw new Exception(message); throw new Exception(message);
} }
static foreach(i, v; uniforms) static foreach(uniform_name, uniform_type; uniforms)
{ {
static if ((i % 2) == 0) mixin("m_uniform_" ~ uniform_name ~ " = glGetUniformLocation(m_id, \"" ~ uniform_name ~ "\");");
{
mixin("m_uniform_" ~ v ~ " = glGetUniformLocation(m_id, \"" ~ v ~ "\");");
} }
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()); 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); *uniform_location = get_uniform_location(uniform_name);
static if (args.length > 0u) 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; return m_id;
} }
void use() const /**
* Use the program.
*/
public void use() const
{ {
glUseProgram(m_id); 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 common_source;
string type; string shader_source;
if (spec[1] == 'i') GLenum shader_type;
foreach (line; program_source.lineSplitter)
{ {
type = "GLint"; GLenum new_shader_type;
switch (line)
{
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;
} }
else if (spec[1..2] == "ui") if (new_shader_type != 0)
{ {
type = "GLuint"; if (shader_type != 0)
{
Shader shader = new Shader(shader_type, shader_source);
attach_shader(shader);
shader_source = "";
} }
else if (spec[1] == 'f') shader_type = new_shader_type;
{ shader_source = common_source[];
type = "GLfloat";
} }
assert(type != ""); else if (shader_type != 0)
if (spec[$ - 1] == 'v')
{ {
return "uint count, const " ~ type ~ " * v"; shader_source ~= line;
} }
else else
{ {
string[] decls; common_source ~= line;
for (int i = 0; i < count; i++)
{
decls ~= type ~ " v" ~ cast(char)('0' + i);
}
return decls.join(", ");
} }
} }
private static string uniform_param_list(string spec) if (shader_type != 0 && shader_source != "")
{ {
int count = spec[0] - '0'; Shader shader = new Shader(shader_type, shader_source);
if (spec[$ - 1] == 'v') attach_shader(shader);
{
return "count, v";
}
else
{
string[] vees;
for (int i = 0; i < count; i++)
{
vees ~= "v" ~ cast(char)('0' + i);
}
return vees.join(", ");
}
} }
static foreach (i, v; uniforms) link();
{
static if ((i % 2) == 0)
{
mixin("void set_" ~ v ~ "(" ~ uniform_param_decl_list(uniforms[i + 1]) ~ ")" ~
"{" ~
" glUniform" ~ uniforms[i + 1] ~ "(m_uniform_" ~ v ~ ", " ~ uniform_param_list(uniforms[i + 1]) ~ ");" ~
"}");
}
} }
/**
* Internal constructor helper method.
*/
private void build(Args...)(Shader s, Args args) private void build(Args...)(Shader s, Args args)
{ {
attach_shader(s); attach_shader(s);
build(args); build(args);
} }
private void build(Args...)(string attrib_name, uint index, Args args) /**
{ * Internal constructor helper method.
bind_attrib_location(attrib_name, index); */
build(args);
}
private void build() private void build()
{ {
link(); link();