veldrid / veldrid-spirv

SPIR-V shader translation for Veldrid, using SPIRV-Cross
MIT License
50 stars 36 forks source link

ShaderDescription entryPoint different to "main" #4

Open pzgulyas opened 5 years ago

pzgulyas commented 5 years ago

When I try to load a shader with an entryPoint of the ShaderDescription set to something else than "main", I get a null reference exception in CreateFromSpirv, no matter if that function actually exists in shader or not.

(Since includes are quite mystical to mee, how they are supposed to work in glsl and also here, my idea was to just shovel everything into a single file, and generating different actual shaders by setting different entry points, but it doesn't work with this parameter.)

mellinoe commented 5 years ago

@pzgulyas EDIT: Oops, I see now that you're describing a different issue than I initially thought. There is a parallel issue in the Vulkan backend that prevents it from working with different entry point names at the moment.

Could you provide more info about how you are producing the SPIR-V that causes this issue?

pzgulyas commented 5 years ago

I have my glsl sources, and do a ReadAllBytes on them. So it is the same way how you describe it here in the README.md Usage section.

EDIT: Oh, sorry, not exactly. I have the glsl sources in the files, not the compiled bytecode. Hmmm, maybe that was the problem? But it still works with "main" and ReadAllBytes.

EDIT2: Just to add some code how I thought:

byte[] vertexShader = File.ReadAllBytes("sky.vert");
byte[] fragmentShader = File.ReadAllBytes("sky.frag");
Shader[] SkyShader = factory.CreateFromSpirv(
    new ShaderDescription(ShaderStages.Vertex, vertexShader, "main"),
    new ShaderDescription(ShaderStages.Fragment, fragmentShader, "main"));
Shader[] CloudsShader = factory.CreateFromSpirv(
    new ShaderDescription(ShaderStages.Vertex, vertexShader, "main"),
    new ShaderDescription(ShaderStages.Fragment, fragmentShader, "clouds"));

or something similar.

mellinoe commented 5 years ago

As far as I know, there's no way to use an entrypoint other than "main()" in GLSL. So unless I'm missing something, you'll need to specify "main" in the ShaderDescription since that's what the code will have. This isn't true for HLSL (it lets you specify any function name you want), so that's what I initially thought you were referring to. Could you provide a sample GLSL shader that would exhibit this problem?

pzgulyas commented 5 years ago

Below is a sample of my technical experiment. Not that this particular one could not be easily exploded into multiple files, but I also have some more difficult ones. :-)

Sky.frag ```glsl #version 450 #extension GL_ARB_separate_shader_objects : enable #extension GL_ARB_shading_language_420pack : enable layout(location = 0) in vec3 InNormal; layout(location = 1) in vec2 InTexCoords; layout(set = 2, binding = 0) uniform PerBatchFragment { vec4 lightVector; // Direction vector to sun, w = 1/length of vector vec4 overcast; // x = alpha, y = contrast, z = brightness, w = !Overcast.y && !Overcast.z vec3 skyColor; float time; // Used for moving textures across the sky vec3 fogColor; float cloudColor; vec4 fog; vec2 windDisplacement; vec2 moonColor; vec2 moonTexCoord; vec2 padding; }; layout(set = 1, binding = 0) uniform texture2D PrimaryTexture; layout(set = 1, binding = 1) uniform texture2D SecondaryTexture; layout(set = 1, binding = 2) uniform sampler SkySampler; layout(location = 0) out vec4 OutColor; // This function adjusts brightness, saturation and contrast // By Romain Dura aka Romz vec3 ContrastSaturationBrightness(vec3 color, float brt, float sat, float con) { // Increase or decrease theese values to adjust r, g and b color channels separately const float AvgLumR = 0.5; const float AvgLumG = 0.5; const float AvgLumB = 0.5; const vec3 LumCoeff = vec3(0.2125, 0.7154, 0.0721); vec3 AvgLumin = vec3(AvgLumR, AvgLumG, AvgLumB); vec3 brtColor = color * brt; float intensityf = dot(brtColor, LumCoeff); vec3 intensity = vec3(intensityf, intensityf, intensityf); vec3 satColor = mix(intensity, brtColor, sat); vec3 conColor = mix(AvgLumin, satColor, con); return conColor; } void main() { // Get the color information for the current pixel OutColor = texture(sampler2D(PrimaryTexture, SkySampler), InTexCoords); vec2 texCoords = vec2((1.0 - InTexCoords.x) + time, InTexCoords.y); vec4 starColor = texture(sampler2D(SecondaryTexture, SkySampler), texCoords); // Adjust sky color brightness for time of day OutColor *= skyColor.x; // Stars OutColor = mix(starColor, OutColor, skyColor.y); // Fogging OutColor.rgb = mix(OutColor.rgb, fogColor.rgb, clamp((1 - InNormal.y) * fog.x, 0, 1)); // Calculate angular difference between LightVector and vertex normal, radians float dotproduct = dot(lightVector.xyz, InNormal); float angleRcp = 1 / acos(dotproduct * lightVector.w / length(InNormal)); // Sun glow // Coefficients selected by the author to achieve the desired appearance - fot limits the effect OutColor += angleRcp * fog.y; // increase orange at sunset - fog limits the effect if (lightVector.x < 0) { OutColor.r += skyColor.z * angleRcp * fog.z; OutColor.g += OutColor.r * fog.w; } // Keep alpha opaque OutColor.a = 1.0; } void moon() { // Get the color information for the current pixel vec2 texCoords = vec2(moonTexCoord.x + InTexCoords.x * 0.5, moonTexCoord.y + InTexCoords.y * 0.25); OutColor = texture(sampler2D(PrimaryTexture, SkySampler), texCoords); vec4 moonMask = texture(sampler2D(SecondaryTexture, SkySampler), InTexCoords); // Fade moon during daylight OutColor.a *= moonColor.x; // Fogging OutColor.rgb = mix(OutColor.rgb, fogColor.rgb, clamp((1 - InNormal.y) * fog.x, 0, 1)); // Mask stars behind dark side (mask fades in) OutColor.a += moonMask.r * moonColor.y; } void clouds() { // Get the color information for the current pixel // Cloud map is tiled. Tiling factor: 4 // Move cloud map to suit wind conditions vec2 texCoords = vec2(InTexCoords.x * 4 + windDisplacement.x, InTexCoords.y * 4 + windDisplacement.y); OutColor = texture(sampler2D(PrimaryTexture, SkySampler), texCoords); float alpha = OutColor.a; // Fogging OutColor.rgb = mix(OutColor.rgb, fogColor.rgb, clamp((1 - InNormal.y) * fog.x, 0, 1)); // Adjust amount of overcast by adjusting alpha if (overcast.w != 0) { alpha += overcast.x; // Reduce contrast and brightness vec3 color = ContrastSaturationBrightness(OutColor.xyz, 1.0, overcast.z, overcast.y); // Brightness and saturation are really need to be exchanged? OutColor = vec4(color, alpha); } else { alpha *= overcast.x; } // Adjust cloud color brightness for time of day OutColor *= cloudColor; OutColor.a = alpha; } ```

Anyway, temporarily I am just doing some replacing code, like

var shaderBytes = File.ReadAllText(shaderFile)
    .Replace("void main()", "void origmain()")
    .Replace($"void {shaderEntry}()", "void main()")
    .Select(c => (byte)c).ToArray();
mellinoe commented 5 years ago

Okay, I'm able to compile that shader using the following glslangvalidator command:

glslangvalidator sky.frag -V -S frag -e moon --sep main glslangvalidator sky.frag -V -S frag -e clouds --sep main

The confusing part is this --sep parameter always needs to be "main", as far as I can tell. Nothing else seems to work, and you can't leave the parameter off. However, the actual "main" function needs to be deleted, otherwise it complains about duplicate functions. It feels like this feature is a little half-baked, but it does work. I'm looking at the SPIR-V output and the shader's "OpEntryPoint" correctly matches what the input function's name was, and the function body looks correct. It should be possible to support this same functionality through the shaderc library, instead of just ignoring the entry point as it currently does.

I expect that I will fix this when I finish up the HLSL support, which will hopefully be soon. HLSL has better support for defining multiple shaders in a single file, so being able to control which entry point is used will be more valuable there, anyways.