In regular GLSL I can do something like this to conditionally enable a GLSL extension:
#if defined(GL_ARB_shader_viewport_layer_array)
#extension GL_ARB_shader_viewport_layer_array : enable
// Some other stuff here
#endif
My question is, how can I achieve the same in SPIR-V using the glslang library? I'm assuming I'll need to compile multiple versions of the same shader to achieve this? Or is there a way to conditionalize SPIR-V on the existence of an extension without generating two binaries for two versions?
In regular GLSL I can do something like this to conditionally enable a GLSL extension:
First of all, no you can't. Not every OpenGL extension ot GLSL exposes such a #define. For example, nowhere in the GL_ARB_shader_viewport_layer_array extension will you find that define to be specified to be there. By contrast, the GL_ARB_shader_group_vote extension does specify a #define.
Second, even if the extension provides a #define, that still won't work. Because the #define is only exposed if you activate the extension with a #extension directive. So your example will never get shader_viewport_layer_array working.
If you want an extension to be conditionally present, you use #extension NAME : enable. If the implementation doesn't support the extension, you won't get it (you'll get a warning in the shader log file). You detect whether or not the extension is active with the #define discussed earlier.
As for how to do that with SPIR-V... you don't. SPIR-V is an intermediate language, not a high-level one. The expectation is that the SPIR-V is written against a particular version of the host environment, including extensions. If you want to conditionally support some extensions and not others, you have to generate multiple SPIR-V shaders for those combinations of extensions that you intend to support.
SPIR-V has no equivalent to #ifdef, again because it is an intermediate language.
Related
I learnt to use legacy OpenGl (v1 / 2) around a year back but now I am trying to make something that is a bit more up to date (i.e. >OpenGL 3.3).
I want to use a lot of my old code, however I could really do with the compiler flagging up an error when it tries to compile something legacy (e.g. glBegin() ... glEnd()).
I compiled on a mac a while back and it flagged up this error when trying to compile, but now I'm using a raspberry pi running raspbian.
Thanks for your help in advance!
Depending on your use-case, you might be able to use the OpenGL ES header instead of the standard OpenGL header. The OpenGL ES header doesn't contain the deprecated functions.
Another possibility would be to use a loader like gl3w which will also make your code more portable.
I'd recommend using the OpenGL loader generator glad to generate a loader for core profile of the OpenGL version you want to target. The resulting headers will not contain any of the deprected compatibility profile functions and GLenum definitions.
However, be aware that this will not catch all deprecated GL usage at compile time. For example, a core profiles mandates that a VAO != 0 is bound when rendering, that vertex arrays come from VBOs and not client-side memory, and a shader program != 0 is used. Such issues can't really be detected at compile time. I recommend to use the OpenGL Debug Output functionality to catch those remaining issues at runtime. Most GL implementations will produce very useful error or warning messages that way.
Many GLSL programs use macros
#ifdef
#else
#endif
to solve different configurations, is there some elegant method to solve this issue when vulkan build pipeline layout and descriptor set?
Using normal if()\else() blocks and Vulkan specialization constants should solve this one for most cases. I'd expect any sensible compiler to optimize out an entire if() basic block if the specialization constant is zero at compile time.
It is straightforward for a reverse engineer to attach a graphics debugger to an OpenGL application to extract the shader source code. It is my understanding that Vulkan, on the other hand, uses SPIR-V bytecode, rather than passing plaintext shaders to the graphics API.
Does SPIR-V bytecode obfuscate the shader source, or is it fairly easy to decompile?
There is an entire specification explaining, in explicit detail, the behavior of each and every SPIR-V opcode. That's kinda the opposite of obfuscation. But there's more to it than that.
SPIR-V, despite being "assembly", retains a rich amount of information about the source program. It contains structure definitions, function definitions with parameter and return types, looping and conditional constructs, etc. Writing a decompiler for SPIR-V is not at all difficult.
SPIR-V also can optionally contain fragments of text that annotate various SPIR-V definitions. This is more of a function of the environment that compiled to SPIR-V, but the output SPIR-V can contain variable names, structure names, and etc. These OpName decorations can all be easily culled if you wish.
But even without names, all of the important structural information is there. So the security gains from SPIR-V compared to raw GLSL is rather minimal.
It doesn't do any real obfuscation. The only thing it could really do is strip the variable names.
If the application is not willing to complicate the actual calculation then that's about it.
It cannot do much with control flow because vulkan requires structured control flow. Where each conditional branch must have a merge block and every loop has a strict structure.
SPIR opcodes behaves like bytecodes in Java:
it creates a neutral meta-operators, the opcodes, closer of machine raw codes, to easy Spir driver translation to raw GPU code.
as advantage, the opcodes avoids the distribution of plain source codes, and the compiled spir opcodes should have compile issues, as typo or syntax errors - it is already compiled;
a disadvantage is the reversibility of binary representation to plain source code again.
There is no easy workaround to the reversibility of opcodes to plain code. Some solutions used in Java field are:
obfuscation - like ProGuard does for Java's bytecode - not sure if this is possible with SPIR;
code encryption with symmetric key - the key is hardcoded in your C code.
code encryption with asymmetric keys - private key comes from web, after login to server.
The SPIR-V specification allows a module to request that a branch will be flattened or a loop unrolled using control decorations for the appropriate instructions. This has a significant impact on the final performance profile of the shader. However, standard GLSL, unlike HLSL, doesn't have a way to express this. The intent is that the driver can make those decisions for you, though arguably only the developer can have enough information to do so.
Is there a way to specify how a control operation should be compiled from GLSL when using glslang, or is this left up to the driver to make these decisions? Do we still have to manually unroll loops to be sure they won't branch?
Is there a way to specify how a control operation should be compiled from GLSL when using glslang
There is no explicit means in GLSL to request such things. There may be glslangValidator switches that can control it, but even then, that would be a global setting, not a per-loop setting.
Do we still have to manually unroll loops to be sure they won't branch?
That's the only way to "be sure they won't branch". Even with SPIR-V's unroll decoration, that is a request, not a guarantee. If the internal SPIR-V compiler doesn't want to unroll that loop, then it won't, regardless of what you tell it.
Is it possible to create shaders without the use of D3DX functions?
The HLSL compiler is, as of D3D9, part of the D3DX library. To write your shaders in HLSL, you have to use D3DX.
However, there's IDirect3DDevice9::CreatePixelShader and IDirect3DDevice9::CreateVertexShader, which create a shader handle from shader byte code, that is, from what is generated by the HLSL compiler.
You can run the HLSL compiler offline (see D3DXCompileShader), save the machine code to a file and load it at runtime using the aforementioned functions. Sadly this means that you cannot rely on the work that is otherwise done by the D3DX framework. Uploading your constants and optimizing changes is totally up to you in this case.