I am working on a DSL that should make writing OpenGL code much easier. For that I try to infer as much as possible from the environment, because low level OpenGL is not only tedious, but there are also a lot of things that can be done wrong. Currently I try to find a good solution to infer the out variables of the fragment shader from the currently bound Framebuffer. Let me give you an example how it roughly works at the moment, and what the problems are.
Basically, there exists a macro shadingDsl that expands the GLSL sourcecode with everything that can be inferred from the environment:
const fragmentOutputs = @["color"] # written from the environment
shadingDsl:
[...]
fragmentMain:
"""
color = vec4(1,0,1,1);
"""
this expands to:
[...]
let fragmentSource = """
[...]
layout(location = 0) out vec4 color;
void main() {
// begin of user code
color = vec4(1,0,1,1);
// end of user code
}
"""
compileFragmentShader(fragmentSource)
[...]
For the non default Framebuffer, there is a template that binds a framebuffer for e block of code, and overshadows the fragmentOutputs constant:
template bindFramebuffer*(name, tpe, blok: untyped): untyped =
var name {.global.}: tpe
var drawfb, readfb: GLint
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, drawfb.addr)
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, readfb.addr)
name.initAndBindInternal
block:
let currentFramebuffer {. inject .} = name
const fragmentOutputs {.inject.} = name.type.fragmentOutputSeq
blok
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, drawfb.GLuint)
glBindFramebuffer(GL_READ_FRAMEBUFFER, readfb.GLuint)
this works pretty nice, as long as all rendering is done in one big block of code without calls into other procedures, and the renderDsl macro?
proc renderEverything() : void =
shadingDsl:
[...]
fragmentMain:
"""
color = vec4(1,0,1,1);
"""
bindFramebuffer(fb1, FooBarFramebuffer):
shadingDsl:
[...]
fragmentMain:
"""
normal = vec4(0,0,1,0);
colorA = vec4(1,0,1,1);
colorB = vec4(0,1,0,1);
"""
the problem is, that the system breaks as soon as a portion of the rendering is done in other procedures.
proc renderPartB() : void
proc renderEverything() : void =
shadingDsl:
[...]
fragmentMain:
"""
color = vec4(1,0,1,1);
"""
bindFramebuffer(fb1, FooBarFramebuffer):
renderPartB()
proc renderPartB() : void =
# fragmentOutputs is now the default @["color"] again.
shadingDsl:
[...]
fragmentMain:
"""
normal = vec4(0,0,1,0); # error # The only known output is color,
colorA = vec4(1,0,1,1); # error # even though a different
colorB = vec4(0,1,0,1); # error # framebuffer is bound.
"""
Another problem is, that fragmentOutputs is exposed to the user of the library, and can be overshadowed by the user, but it is not intended to be used by anything but the library itself.
The question now is, how do I properly (at best automatically) pass the currently bound framebuffer information, to sub rendering procedures?
Here are some constraints:
- The shader code is generated at compile time, so the fragmentOutputs may not be a runtime value.
- I would like to have an environment, where it is impossible to have one framebuffer bound, but outputs of another framebuffer are passed to the renderDsl macro.
- state changes is OpenGL can be very expensive, so binding the framebuffer for each rendering call, is not an option.
Out of interest, would it work if you used a template instead of a proc for renderPartB, as it should be inlined?
Regardless, I just wanted to say - as someone who only knows the bare basics of gpu shading right now, but will be looking into writing fragment shaders soon - this looks really interesting!
regarding templates. Yes it would work, but it would also mean that when procs that are used to reduce code duplication are transformed into templates, more shader program objects are created, and the overall compilation time and executable size and gpu usage will increase. Additionally I think it's odd to add this requirement.
I am thinking more of something like this: scala implicit parameters. But I would also be interested if there is something in Nim that I do not know yet, that might be better suitable for this job.
If you are interested, I am developing on github but don't expect that everything makes a lot of sense, there is no documentation, and everything is at it's best uncomplete, but it already has some nice features, and examples that show, how the shading dsl can be used.
the default fallback is already a global, so I do not know what you mean. And changing the value of a global is not going to work, because everything needs to be known at compile time. So what do you mean? Can you give an example?
Abot rewriting procedure call's with a custom compiler pass. No.
Actually I have an idea now, but I am not sure how well it will work out, I still have to try it. I could use generics, because generics are passed at compile time
# CFBT stands for current framebuffer type
proc renderB[CFBT]() : void
proc render() : void =
bindFramebuffer(fb1, FooBarFramebuffer): # will define the type CFBT locally
renderB[CFBT]() # CFBT can now be passed as a generic
proc renderB[CFBT]() : void =
shadingDsl:
[...]
fragmentMain:
"""
normal = vec4(0,0,1,0); # these outputs are now read from CFBT
colorA = vec4(1,0,1,1); #
colorB = vec4(0,1,0,1); #
"""
the obvious drawback would be, that each rendering function (except from the entry rendering function) needs to have [CFBT] as a generic parameter. But at the moment I cant't think of a better solution.