pouët.net

Function pointers and closures in HLSL

category: code [glöplog]
 
Some of you may be interested in this cool hack of HLSL by using function pointers and closures on SM3/SM4/SM5.

There are lots of opportunities to use it in intros as well as in your regular HLSL code, as It provides a great way to reuse patterns of code. For example in raymarching using multiple different ISO functions with the same raymarching/normal/ao/lighting global function. You should be able to write this kind of thing:

Code: interface Iso { float Compute(float3 p); }; void RenderScene(Iso iso) { x = float2(.001,0); // Main RayMarching loop for(i=0;i<STEPS&&z<MAX&&(d=iso.Compute(p+=r*d))>x.x;i++,f+=d); // Normal g=normalize(float3(iso.Compute(p+x.xyy),iso.Compute(p+x.yxy),iso.Compute(p+x.yyx))-d); // AO d=1; for(i=0;i<6;i++,a-=(i*.65-iso.Compute(p+g*i*.65))/(d*=2)); ... etc. }


And use it like this
Code: float4 PS1(...) : SV_TARGET { class LocalFunction : Iso { float Compute(float3 p) { ... insert your iso function here... }} instance; RenderScene(instance); }


Not sure that It will help for 4k size, but It would be worth to use it for lots of other situations!
added on the 2011-11-26 13:35:48 by xoofx xoofx
will it work in dx9?
added on the 2011-11-26 18:35:08 by unc unc
One other solution to get equivalent functionality is to make a shader stitcher?
added on the 2011-11-26 18:58:55 by nystep nystep
Size comparison needed?
added on the 2011-11-26 20:38:10 by msqrt msqrt
mentor used macros in his raymarching intros exactly for this, to keep the marcher generic and only change the distance function per scene. see recepto by tbc
added on the 2011-11-26 20:55:24 by iq iq
to extend on what nystep said, there's this article I had written about shader stitching on displayhack.

there must be some source code that comes with it too, and it's well-commented iirc.
added on the 2011-11-26 21:52:27 by decipher decipher
Stitching is pretty basic generics, templates or other kinds of meta programming sounds cool ;)
Add . Somewhere
guys, this will lead to bigger code, so if you are talking about 4k you might want do it the other way around!
added on the 2011-11-26 22:48:11 by pantaloon pantaloon
@unc yes

About all macros, stitching, methods, yeah i knew and used them already of course, but that was exactly what I wanted to avoid: macros because it is laborious to maintain, compiler errors becomes harder to find...etc and stitching has all desavantages of macros + requires an external "preprocessor". Both are making step by step debugging shaders almost impractical...

Note that i don't find function pointers efficient for 4k (I used macros) but these function pointers are only relevant if you want to add sone clean abstraction to your code, while keeping the same generated asm (it was not intended with size enhancement in mind)
added on the 2011-11-26 23:02:52 by xoofx xoofx
Ah, well I was thinking about real world applications not 4ks in particular in this respect.
In opengl you can stitch automaticly multiple shader source strings on the load, so it's not a problem for 4ks.
added on the 2011-11-26 23:13:41 by nystep nystep
@nystep, I won't code real world application with a stich method, as I said, it is a preprocessing method (btw, i didn't get what is this opengl builtin stich mechanism, not aware of), and requires you to split piece of partial code in the way you are going to assemble them.

By analogy, I doubt that because you have function pointers in C, you would still go with macros/stiching to make your code more composable/abstractable.
added on the 2011-11-26 23:36:03 by xoofx xoofx
glShaderSource is stitching for you.
added on the 2011-11-26 23:45:11 by nystep nystep
@nystep, oh, ok, nice function indeed.. but again, would you use this kind of function for code composition if you had class/interface & function pointers?
added on the 2011-11-26 23:49:45 by xoofx xoofx
@lx: yes I would. also I think you got stitching completely wrong. stitching has nothing to do with using preprocessor blocks to disable / enable snippets of übershaders. it's more about generating the shader code in runtime by combining atomic strings of building blocks.

stitching has absolutely nothing to do with a preprocessor or preprocessor macros. it's purely runtime, heck even realtime (if you want it to be) code generation.
added on the 2011-11-26 23:56:07 by decipher decipher
@alx Not if I can afford to give up earlier shader models than the 5th of course :-)
Polymorphism is the nicest way to go.
But for shader models 2,3,4.. Why not.
added on the 2011-11-27 00:00:27 by nystep nystep
@Decipher, we miss understood. When I'm talking about preprocessing technique, I'm not talking about C preprocessing exclusively. Stiching is a pre-processing technique, whatever you want to call it: It glues piece of separate code into a single one (as C preprocessor is doing it). Also C preprocessing is runtime in the same way stiching is realtime.
added on the 2011-11-27 00:07:49 by xoofx xoofx
all the GLSL and opengl shaders API philosophy orbitates around the idea of having small pieces of shader code, for maintainability. as nystep mentioned, it's in the very intended use of glShaderSource to do so. not relevant here, but to follow this philosophy, afterwards you LINK your programs object programs. so in theory it's all about splitting your code in small chunks.

added on the 2011-11-27 00:34:43 by iq iq
Also, for stiching/macros, whenever you need to compose code where the composition needs to be performed inside another functions, the maintainability is getting really ugly.
That's exactly the example I was taking about noise generation.

If you have a function A that calls B and calls C (and you want even to recompose the chain, like B -> A -> C ... etc.). You want to be able to plug at call sites those functions (replace B in A, replace C in B), you have two options: 1) split your code in the middle of those functions in oder to be able to compose the code (and this is starting to be very ugly, and unmaintainable)... or 2) generate pseudo call sites (like A is calling A1, B is calling B1, and apply a stich-preprocess, that generate a A1 function that calls a B function, and a B1 function that calls C (or predefined #define A1 before including A to redirect to B...etc.)

If you had function pointers/interface/class from the beginning, I doubt seriously that you would have gone to the stiching-preprocessing route (unless for size optimized tricks)... but ok, I see that most of the people that respond to my suggestion here are mainly programming in GLSL... so of course, for GLSL, you don't have any choice than going with GLSL philosophy! :P
added on the 2011-11-27 00:58:40 by xoofx xoofx
I think most people use D3D/HLSL actually, even here at pouet. Except for a few zealots like me who haven't evolved and noticed there was a world outside of the cavern. :)
added on the 2011-11-27 12:05:46 by nystep nystep
But it's nice that these type of small hacks can enable greater expressivity in the programming language.
added on the 2011-11-27 12:10:29 by nystep nystep

login