diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 4b33eeb..3436453 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,6 @@ { "recommendations": [ + "shader-slang.slang-language-extension", "vadimcn.vscode-lldb", "ziglang.vscode-zig", ], diff --git a/assets/shaders/equirect_to_cube.comp b/assets/shaders/equirect_to_cube.comp deleted file mode 100644 index a0cca9e..0000000 --- a/assets/shaders/equirect_to_cube.comp +++ /dev/null @@ -1,68 +0,0 @@ -#version 460 - -layout(set = 0, binding = 0) uniform sampler _EquirectangularSampler; - -layout(set = 0, binding = 1) uniform texture2D _EquirectangularTexture; -layout(set = 0, binding = 2, rgba16f) uniform writeonly restrict imageCube _CubemapImage; - -const float INV_PI = 0.31830987; -const float HALF_INV_PI = 0.15915494; - -const mat3x3 MATRIX_2D_TO_CUBE[6] = mat3x3[]( - // Positive X - mat3x3( - 0, 0, -1, - 0, -1, 0, - 1, 0, 0 - ), - // Negative X - mat3x3( - 0, 0, 1, - 0, -1, 0, - -1, 0, 0 - ), - // Positive Y - mat3x3( - 1, 0, 0, - 0, 0, 1, - 0, 1, 0 - ), - // Negative Y - mat3x3( - 1, 0, 0, - 0, 0, -1, - 0, -1, 0 - ), - // Positive Z - mat3x3( - 1, 0, 0, - 0, -1, 0, - 0, 0, 1 - ), - // Negative Z - mat3x3( - -1, 0, 0, - 0, -1, 0, - 0, 0, -1 - ) -); - -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; -void main() { - vec2 size = vec2(imageSize(_CubemapImage).xy); - vec2 texCoord = (vec2(gl_GlobalInvocationID.xy) + vec2(0.5)) / size; - uint layerIndex = gl_GlobalInvocationID.z; - - texCoord = texCoord * vec2(2.0) - vec2(1.0); // Map to range [-1, 1] - - vec3 cubeCoord = MATRIX_2D_TO_CUBE[layerIndex] * vec3(texCoord, 1.0); - vec3 cubeDir = normalize(cubeCoord); - - float theta = atan(cubeDir.y, cubeDir.x); - float phi = asin(cubeDir.z); - - vec2 equirectCoord = vec2(theta * HALF_INV_PI, phi * INV_PI) + vec2(0.5); - - vec4 irradiance = texture(sampler2D(_EquirectangularTexture, _EquirectangularSampler), equirectCoord); - imageStore(_CubemapImage, ivec3(gl_GlobalInvocationID.xyz), irradiance); -} diff --git a/assets/shaders/equirect_to_cube.slang b/assets/shaders/equirect_to_cube.slang new file mode 100644 index 0000000..3af9d9c --- /dev/null +++ b/assets/shaders/equirect_to_cube.slang @@ -0,0 +1,72 @@ +import "includes/math"; + +[[vk::binding(0, 0)]] SamplerState _EquirectangularSampler; +[[vk::binding(1, 0)]] Texture2D _EquirectangularTexture; +[[vk::binding(2, 0)]] WTexture2DArray _CubemapImage; + +static const float3x3 MATRIX_2D_TO_CUBE[6] = { + // Positive X + { + 0, 0, 1, + 0, -1, 0, + -1, 0, 0, + }, + // Negative X + { + 0, 0, -1, + 0, -1, 0, + 1, 0, 0, + }, + // Positive Y + { + 1, 0, 0, + 0, 0, 1, + 0, 1, 0, + }, + // Negative Y + { + 1, 0, 0, + 0, 0, -1, + 0, -1, 0, + }, + // Positive Z + { + 1, 0, 0, + 0, -1, 0, + 0, 0, 1, + }, + // Negative Z + { + -1, 0, 0, + 0, -1, 0, + 0, 0, -1, + } +}; + +float2 cubemapSize() { + uint width, height, elements; + _CubemapImage.GetDimensions(width, height, elements); + return float2(width, height); +} + +[shader("compute")] +[numthreads(8, 8, 1)] +void main(uint3 threadID: SV_DispatchThreadID) +{ + let size = cubemapSize(); + var texCoord = (float2(threadID.xy) + 0.5) / size; + let layerIndex = threadID.z; + + texCoord = 2.0 * texCoord - 1.0; // Map to range [-1, 1] + + let cubeCoord = mul(MATRIX_2D_TO_CUBE[layerIndex], float3(texCoord, 1.0)); + let cubeDir = normalize(cubeCoord); + + let theta = atan2(cubeDir.y, cubeDir.x); + let phi = asin(cubeDir.z); + + let equirectCoord = float2(theta * HALF_INV_PI, phi * INV_PI) + 0.5; + + let irradiance = _EquirectangularTexture.SampleLevel(_EquirectangularSampler, equirectCoord, 0.0); + _CubemapImage.Store(threadID, irradiance); +} diff --git a/assets/shaders/gui_box.frag b/assets/shaders/gui_box.frag deleted file mode 100644 index 7217a25..0000000 --- a/assets/shaders/gui_box.frag +++ /dev/null @@ -1,39 +0,0 @@ -#version 460 -#extension GL_EXT_nonuniform_qualifier : require -#extension GL_EXT_scalar_block_layout : require -#extension GL_EXT_shader_16bit_storage : require - -in Varyings { - layout(location = 0) flat uint instance; - layout(location = 1) vec2 positionSSPX; -} var; - -#include "includes/gui_box_common.glsl" - -layout(location = 0) out vec4 fragColor; - -float boxSDF(vec2 p, vec2 center, vec2 halfExtents, float borderRadius) { - vec2 q = abs(p - center) - halfExtents + borderRadius; - return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - borderRadius; -} - -#define BOX _Boxes[var.instance] - -void main() { - vec2 halfExtentsPX = 0.5 * BOX.sizePX; - vec2 centerSSPX = BOX.positionSSPX + halfExtentsPX; - float borderHalfWidthPX = 0.5 * BOX.borderWidthPX; - - float interiorSDF = boxSDF(var.positionSSPX, centerSSPX, halfExtentsPX, BOX.borderRadiusPX) + BOX.borderWidthPX; - float borderSDF = abs(interiorSDF - borderHalfWidthPX) - borderHalfWidthPX; - - float interiorCoverage = clamp(-interiorSDF + 0.5, 0.0, 1.0) * BOX.backgroundColor.a; - float borderCoverage = clamp(-borderSDF + 0.5, 0.0, 1.0) * BOX.borderColor.a; - float totalCoverage = interiorCoverage + borderCoverage; - - fragColor = vec4( - interiorCoverage * BOX.backgroundColor.rgb + - borderCoverage * BOX.borderColor.rgb, - totalCoverage - ); -} diff --git a/assets/shaders/gui_box.slang b/assets/shaders/gui_box.slang new file mode 100644 index 0000000..8515778 --- /dev/null +++ b/assets/shaders/gui_box.slang @@ -0,0 +1,65 @@ +import "includes/math"; +import "includes/structs"; + +struct Varyings { + float4 positionCS : SV_Position; + nointerpolation uint instance; + float2 positionSSPX; +} + +struct GuiBox { + float4 backgroundColor; + float4 borderColor; + float2 positionSSPX; + float2 sizePX; + float borderWidthPX; + float borderRadiusPX; +} + +[[vk::binding(0, 0)]] ConstantBuffer _Global; +[[vk::binding(1, 0)]] StructuredBuffer _Boxes; + +static const float2 VERTICES[4] = { + float2(0, 1), + float2(1, 1), + float2(0, 0), + float2(1, 0), +}; + +[shader("vertex")] +Varyings vertexMain(uint vertexID : SV_VulkanVertexID, uint instanceID : SV_VulkanInstanceID) { + Varyings out; + + let vertex = VERTICES[vertexID]; + let box = _Boxes[instanceID]; + + let positionSSPX = vertex * box.sizePX + box.positionSSPX; + let positionCS = float4(mul(_Global.matrixSSPXtoCS, float3(positionSSPX, 1.0)), 0.0, 1.0); + + out.positionCS = positionCS; + out.instance = instanceID; + out.positionSSPX = positionSSPX; + return out; +} + +[shader("fragment")] +float4 fragmentMain(Varyings frag) : SV_Target { + let box = _Boxes[frag.instance]; + + let halfExtentsPX = 0.5 * box.sizePX; + let centerSSPX = box.positionSSPX + halfExtentsPX; + let borderHalfWidthPX = 0.5 * box.borderWidthPX; + + let interiorSDF = boxSDF(frag.positionSSPX, centerSSPX, halfExtentsPX, box.borderRadiusPX) + box.borderWidthPX; + float borderSDF = abs(interiorSDF - borderHalfWidthPX) - borderHalfWidthPX; + + float interiorCoverage = saturate(-interiorSDF + 0.5) * box.backgroundColor.a; + float borderCoverage = saturate(-borderSDF + 0.5) * box.borderColor.a; + float totalCoverage = interiorCoverage + borderCoverage; + + return float4( + interiorCoverage * box.backgroundColor.rgb + + borderCoverage * box.borderColor.rgb, + totalCoverage, + ); +} diff --git a/assets/shaders/gui_box.vert b/assets/shaders/gui_box.vert deleted file mode 100644 index 5a6ff46..0000000 --- a/assets/shaders/gui_box.vert +++ /dev/null @@ -1,30 +0,0 @@ -#version 460 -#extension GL_EXT_nonuniform_qualifier : require -#extension GL_EXT_scalar_block_layout : require -#extension GL_EXT_shader_16bit_storage : require - -out Varyings { - layout(location = 0) flat uint instance; - layout(location = 1) vec2 positionSSPX; -} var; - -#include "includes/gui_box_common.glsl" - -const vec2 VERTICES[4] = vec2[]( - vec2(0, 1), - vec2(1, 1), - vec2(0, 0), - vec2(1, 0) -); - -#define BOX _Boxes[gl_InstanceIndex] -#define VERTEX VERTICES[gl_VertexIndex] - -void main() { - vec2 positionSSPX = VERTEX * BOX.sizePX + BOX.positionSSPX; - vec4 positionCS = vec4(_Global.matrixSSPXtoCS * vec3(positionSSPX, 1.0), 0.0, 1.0); - - gl_Position = positionCS; - var.instance = gl_InstanceIndex; - var.positionSSPX = positionSSPX; -} diff --git a/assets/shaders/gui_image.frag b/assets/shaders/gui_image.frag deleted file mode 100644 index 1cc108a..0000000 --- a/assets/shaders/gui_image.frag +++ /dev/null @@ -1,20 +0,0 @@ -#version 460 -#extension GL_EXT_nonuniform_qualifier : require -#extension GL_EXT_scalar_block_layout : require -#extension GL_EXT_shader_16bit_storage : require - -in Varyings { - layout(location = 0) flat uint instance; - layout(location = 1) vec2 texCoord; -} var; - -#include "includes/gui_image_common.glsl" - -layout(location = 0) out vec4 fragColor; - -#define IMAGE _Images[var.instance] - -void main() { - vec4 texel = texture(sampler2D(_Textures[uint(IMAGE.textureId)], _Sampler), var.texCoord); - fragColor = texel * IMAGE.tint; -} diff --git a/assets/shaders/gui_image.slang b/assets/shaders/gui_image.slang new file mode 100644 index 0000000..0d37537 --- /dev/null +++ b/assets/shaders/gui_image.slang @@ -0,0 +1,53 @@ +import "includes/structs"; + +struct Varyings { + float4 positionCS : SV_Position; + nointerpolation uint instance; + float2 texCoord; +} + +struct GuiImage { + float4 tint; + float2 positionSSPX; + float2 sizePX; + float2 uvMin; + float2 uvMax; + uint16_t texture; +} + +[[vk::binding(0, 0)]] ConstantBuffer _Global; +[[vk::binding(1, 0)]] StructuredBuffer _Images; +[[vk::binding(2, 0)]] SamplerState _Sampler; +[[vk::binding(3, 0)]] Texture2D _Textures[]; + +static const float2 VERTICES[4] = { + float2(0, 1), + float2(1, 1), + float2(0, 0), + float2(1, 0), +}; + +[shader("vertex")] +Varyings vertexMain(uint vertexID : SV_VulkanVertexID, uint instanceID : SV_VulkanInstanceID) { + Varyings out; + + let vertex = VERTICES[vertexID]; + let image = _Images[instanceID]; + + let positionSSPX = vertex * image.sizePX + image.positionSSPX; + let positionCS = float4(mul(_Global.matrixSSPXtoCS, float3(positionSSPX, 1.0)), 0.0, 1.0); + + let texCoord = vertex * (image.uvMax - image.uvMin) + image.uvMin; + + out.positionCS = positionCS; + out.instance = instanceID; + out.texCoord = texCoord; + return out; +} + +[shader("fragment")] +float4 fragmentMain(Varyings frag) : SV_Target { + let image = _Images[frag.instance]; + let texel = _Textures[image.texture].Sample(_Sampler, frag.texCoord); + return texel * image.tint; +} diff --git a/assets/shaders/gui_image.vert b/assets/shaders/gui_image.vert deleted file mode 100644 index 3e4329f..0000000 --- a/assets/shaders/gui_image.vert +++ /dev/null @@ -1,32 +0,0 @@ -#version 460 -#extension GL_EXT_nonuniform_qualifier : require -#extension GL_EXT_scalar_block_layout : require -#extension GL_EXT_shader_16bit_storage : require - -out Varyings { - layout(location = 0) flat uint instance; - layout(location = 1) vec2 texCoord; -} var; - -#include "includes/gui_image_common.glsl" - -const vec2 VERTICES[4] = vec2[]( - vec2(0, 1), - vec2(1, 1), - vec2(0, 0), - vec2(1, 0) -); - -#define IMAGE _Images[gl_InstanceIndex] -#define VERTEX VERTICES[gl_VertexIndex] - -void main() { - vec2 positionSSPX = VERTEX * IMAGE.sizePX + IMAGE.positionSSPX; - vec4 positionCS = vec4(_Global.matrixSSPXtoCS * vec3(positionSSPX, 1.0), 0.0, 1.0); - - vec2 texCoord = VERTEX * (IMAGE.uvMax - IMAGE.uvMin) + IMAGE.uvMin; - - gl_Position = positionCS; - var.instance = gl_InstanceIndex; - var.texCoord = texCoord; -} diff --git a/assets/shaders/gui_text.frag b/assets/shaders/gui_text.frag deleted file mode 100644 index 433244c..0000000 --- a/assets/shaders/gui_text.frag +++ /dev/null @@ -1,21 +0,0 @@ -#version 460 -#extension GL_EXT_nonuniform_qualifier : require -#extension GL_EXT_scalar_block_layout : require -#extension GL_EXT_shader_16bit_storage : require - -layout(location = 0) out vec4 fragColor; - -uint calcRootCode(float y1, float y2, float y3) { - uint i1 = floatBitsToUint(y1) >> 31U; - uint i2 = floatBitsToUint(y2) >> 30U; - uint i3 = floatBitsToUint(y3) >> 29U; - - uint shift = (i2 & 2U) | (i1 & ~2U); - shift = (i3 & 4U) | (shift & ~4U); - - return ((0x2E74U >> shift) & 0x0101U); -} - -void main() { - fragColor = vec4(1.0, 0.0, 1.0, 1.0); -} diff --git a/assets/shaders/gui_text.slang b/assets/shaders/gui_text.slang new file mode 100644 index 0000000..918f148 --- /dev/null +++ b/assets/shaders/gui_text.slang @@ -0,0 +1,271 @@ +import "includes/structs"; + +struct Varyings { + float4 positionCS : SV_Position; + nointerpolation uint instance; + float2 texCoordEM : U_TEXCOORD; +} + +struct GuiText { + float4 color; + // Index into _GlyphData + uint glyph; + float2 bottomLeftSSPX; + float fontSizePX; +} + +struct GlyphData { + float2 bottomLeftEM; + float2 sizeEM; + // Index into _BandData of the first vertical/horizontal band data. + // `bandPtr.x` refers to vertical bands and `bandPtr.y` refers to horizontal + // bands. + uint2 bandPtr; + // Number of vertical/horizontal bands. `bandCount.x` is the number of + // vertical bands and `bandCount.y` is the number of horizontal bands. + uint2 bandCount; +} + +[[vk::binding(0, 0)]] ConstantBuffer _Global; +[[vk::binding(1, 0)]] StructuredBuffer _Instances; + +[[vk::binding(0, 1)]] StructuredBuffer _GlyphData; +[[vk::binding(1, 1)]] StructuredBuffer _ControlPoints; +[[vk::binding(2, 1)]] StructuredBuffer _BandData; + +static const float2 VERTICES[4] = { + float2(0, 1), + float2(1, 1), + float2(0, 0), + float2(1, 0), +}; + +// Classify a quadratic Bézier curve based on the sign bits of the ray-relative +// Y coordinates. Returns `(t2 << 1) | t1` where `t1` and `t2` are bits +// signifying the root eligibility; `t1` possibly adding one to the winding +// number and `t2` possibly subtracting one from the winding number. +uint classifyQuadCurve(float y0, float y1, float y2) +{ + // ┌─────┰────┬────┬────┰────┬────┐ + // │class┃ y2 │ y1 │ y0 ┃ t2 │ t1 │ + // ├─────╂────┼────┼────╂────┼────┤ + // │ A ┃ 0 │ 0 │ 0 ┃ 0 │ 0 │ + // │ B ┃ 0 │ 0 │ 1 ┃ 1 │ 0 │ + // │ C ┃ 0 │ 1 │ 0 ┃ 1 │ 1 │ + // │ D ┃ 0 │ 1 │ 1 ┃ 1 │ 0 │ + // │ E ┃ 1 │ 0 │ 0 ┃ 0 │ 1 │ + // │ F ┃ 1 │ 0 │ 1 ┃ 1 │ 1 │ + // │ G ┃ 1 │ 1 │ 0 ┃ 0 │ 1 │ + // │ H ┃ 1 │ 1 │ 1 ┃ 0 │ 0 │ + // └─────┸────┴────┴────┸────┴────┘ + // + // Lookup table constant: + // H G F E D C B A + // 00 01 11 01 10 11 10 00 = 0x1DB8 + + let sign0 = asuint(y0) >> 31; + let sign1 = asuint(y1) >> 31; + let sign2 = asuint(y2) >> 31; + + let class = (sign0 << 0) | (sign1 << 1) | (sign2 << 2); + let shift = 2 * class; + + return (0x2E74U >> shift) & 0b11U; +} + +// Find intersections between a quadratic Bézier curve and `y = 0` line by +// solving a quadratic equation. +// +// Returns the X coordinates at witch the intersections occur for roots t1 and +// t2. Imaginary solutions to the quadratic equation will return double root at +// the global minimum. +float2 raycastQuadCurveHorizontally(float2 cp0, float2 cp1, float2 cp2) +{ + static const float EPSILON = 1.0 / 65536.0; + + // When solving for y = 0, we get a quadratic polynomial given by: + // + // at² - 2bt + c + // + // where: + // * a = cp0.y - 2 * cp1.y + cp2.y + // * b = cp0.y - cp1.y + // * c = cp0.y + // + // Let Δ = b² - ac + // Then: + // + // t1 = (b - sqrt(Δ)) / a + // t2 = (b + sqrt(Δ)) / a + + let a = cp0 - 2.0 * cp1 + cp2; + let b = cp0 - cp1; + let c = cp0; + + let Δ = sqrt(max(b.y * b.y - a.y * c.y, 0.0)); + var t = (b.y + float2(-Δ, Δ)) / a.y; + + // If nearly linear, solve -2bt + c = 0 directly + if (abs(a.y) < EPSILON) { + t = float2(0.5 * c.y / b.y); + } + + return (a.x * t - 2.0 * b.x) * t + c.x; +} + +// Find intersections between a quadratic Bézier curve and `x = 0` line by +// solving a quadratic equation. +// +// Returns the Y coordinates at witch the intersections occur for roots t1 and +// t2. Imaginary solutions to the quadratic equation will return double root at +// the global minimum. +float2 raycastQuadCurveVertically(float2 cp0, float2 cp1, float2 cp2) +{ + static const float EPSILON = 1.0 / 65536.0; + + let a = cp0 - 2.0 * cp1 + cp2; + let b = cp0 - cp1; + let c = cp0; + + let Δ = sqrt(max(b.x * b.x - a.x * c.x, 0.0)); + var t = (b.x + float2(-Δ, Δ)) / a.x; + + if (abs(a.x) < EPSILON) { + t = float2(0.5 * c.x / b.x); + } + + return (a.y * t - 2.0 * b.y) * t + c.y; +} + +float calculateCoverage(float xCoverage, float yCoverage, float xWeight, float yWeight) { + static const float EPSILON = 1.0 / 65536.0; + + var coverage = max( + abs(xCoverage * xWeight + yCoverage * yWeight) / max(xWeight + yWeight, EPSILON), + min(abs(xCoverage), abs(yCoverage)), + ); + // non-zero fill rule + coverage = saturate(coverage); + + return coverage; +} + +[shader("vertex")] +Varyings vertexMain(uint vertexID : SV_VulkanVertexID, uint instanceID : SV_VulkanInstanceID) { + Varyings out; + + let vertex = VERTICES[vertexID]; + let instance = _Instances[instanceID]; + let glyph = _GlyphData[instance.glyph]; + let normal = 2.0 * vertex - 1.0; + + let sizePX = instance.fontSizePX * glyph.sizeEM; + + let positionSSPX = vertex * sizePX + instance.bottomLeftSSPX; + let dilatedPositionSSPX = positionSSPX + 0.5 * normal; + let dilatedPositionCS = float4(mul(_Global.matrixSSPXtoCS, float3(positionSSPX, 1.0)), 0.0, 1.0); + + let texCoordEM = vertex * glyph.sizeEM + glyph.bottomLeftEM; + let dilatedTexCoordEM = texCoordEM + (0.5 / instance.fontSizePX) * normal; + + out.positionCS = dilatedPositionCS; + out.instance = instanceID; + out.texCoordEM = dilatedTexCoordEM; + return out; +} + +[shader("fragment")] +float4 fragmentMain(Varyings frag) : SV_Target { + let instance = _Instances[frag.instance]; + let glyph = _GlyphData[instance.glyph]; + + let emPerPX = fwidth(frag.texCoordEM); + let pxPerEM = 1.0 / emPerPX; + + let bandF = float2(glyph.bandCount) * (frag.texCoordEM - glyph.bottomLeftEM) / glyph.sizeEM; + let band = uint2(clamp(bandF, float2(0, 0), float2(glyph.bandCount - 1U))); + + // --- HORIZONTAL BAND ----------------------------------------------------- + + var xCoverage = 0.0; + var xWeight = 0.0; + + let horizontalBandPtr = glyph.bandPtr.y + band.y; + let horizontalBandData = _BandData[horizontalBandPtr]; + let horizontalBandCurvePtr = horizontalBandPtr + (horizontalBandData >> 16); + let horizontalBandCurveCount = horizontalBandData & 0xFFFF; + + for (uint horizontalBandCurveID = 0; horizontalBandCurveID < horizontalBandCurveCount; horizontalBandCurveID++) { + let cpPtr = _BandData[horizontalBandCurvePtr + horizontalBandCurveID]; + let cp0 = float2(_ControlPoints[cpPtr]) - frag.texCoordEM; + let cp1 = float2(_ControlPoints[cpPtr + 1]) - frag.texCoordEM; + let cp2 = float2(_ControlPoints[cpPtr + 2]) - frag.texCoordEM; + + if (max(max(cp0.x, cp1.x), cp2.x) * pxPerEM.x < -0.5) break; + + let rootFlags = classifyQuadCurve(cp0.y, cp1.y, cp2.y); + if (rootFlags != 0U) { + + let result = raycastQuadCurveHorizontally(cp0, cp1, cp2) * pxPerEM.x; + + // consider t1 at X position result.x + if ((rootFlags & 0b01U) != 0U) { + xCoverage += saturate(result.x + 0.5); + xWeight = max(xWeight, saturate(1.0 - abs(result.x) * 2.0)); + } + + // consider t2 at X position result.y + if ((rootFlags & 0b10U) != 0U) { + xCoverage -= saturate(result.y + 0.5); + xWeight = max(xWeight, saturate(1.0 - abs(result.y) * 2.0)); + } + } + } + + // --- VERTICAL BAND ------------------------------------------------------- + + var yCoverage = 0.0; + var yWeight = 0.0; + + let verticalBandPtr = glyph.bandPtr.x + band.x; + let verticalBandData = _BandData[verticalBandPtr]; + let verticalBandCurvePtr = verticalBandPtr + (verticalBandData >> 16); + let verticalBandCurveCount = verticalBandData & 0xFFFF; + + for (uint verticalBandCurveID = 0; verticalBandCurveID < verticalBandCurveCount; verticalBandCurveID++) { + let cpPtr = _BandData[verticalBandCurvePtr + verticalBandCurveID]; + let cp0 = float2(_ControlPoints[cpPtr]) - frag.texCoordEM; + let cp1 = float2(_ControlPoints[cpPtr + 1]) - frag.texCoordEM; + let cp2 = float2(_ControlPoints[cpPtr + 2]) - frag.texCoordEM; + + if (max(max(cp0.y, cp1.y), cp2.y) * pxPerEM.y < -0.5) break; + + let rootFlags = classifyQuadCurve(cp0.x, cp1.x, cp2.x); + if (rootFlags != 0U) { + + let result = raycastQuadCurveVertically(cp0, cp1, cp2) * pxPerEM.y; + + // consider t1 at Y position result.x + if ((rootFlags & 0b01U) != 0U) + { + yCoverage -= saturate(result.x + 0.5); + yWeight = max(yWeight, saturate(1.0 - abs(result.x) * 2.0)); + } + + // consider t2 at Y position result.y + if ((rootFlags & 0b10U) != 0U) + { + yCoverage += saturate(result.y + 0.5); + yWeight = max(yWeight, saturate(1.0 - abs(result.y) * 2.0)); + } + } + } + + // --- RESOLVE ------------------------------------------------------------- + + let coverage = calculateCoverage(xCoverage, yCoverage, xWeight, yWeight) * instance.color.a; + return float4( + coverage * instance.color.rgb, + coverage, + ); +} diff --git a/assets/shaders/gui_text.vert b/assets/shaders/gui_text.vert deleted file mode 100644 index 4b54db8..0000000 --- a/assets/shaders/gui_text.vert +++ /dev/null @@ -1,14 +0,0 @@ -#version 460 -#extension GL_EXT_nonuniform_qualifier : require -#extension GL_EXT_scalar_block_layout : require -#extension GL_EXT_shader_16bit_storage : require - -layout(location = 0) in vec4 positionOS_normalOS; -layout(location = 1) in vec4 texCoordEM_band_flags; -layout(location = 2) in vec4 jacobian; -layout(location = 3) in vec4 scale_offset; -layout(location = 4) in vec4 color; - -void main() { - gl_Position = vec4(0.0, 0.0, 0.0, 1.0); -} diff --git a/assets/shaders/includes/global_uniforms.glsl b/assets/shaders/includes/global_uniforms.glsl deleted file mode 100644 index c73f940..0000000 --- a/assets/shaders/includes/global_uniforms.glsl +++ /dev/null @@ -1,6 +0,0 @@ -layout(set = 0, binding = 0, scalar) uniform GlobalUniforms { - mat4 matrixWStoVS; - mat4 matrixVStoCS; - mat3x2 matrixSSPXtoCS; - vec3 ambientLight; -} _Global; diff --git a/assets/shaders/includes/gui_box_common.glsl b/assets/shaders/includes/gui_box_common.glsl deleted file mode 100644 index 1becd0b..0000000 --- a/assets/shaders/includes/gui_box_common.glsl +++ /dev/null @@ -1,14 +0,0 @@ -struct Box { - vec4 backgroundColor; - vec4 borderColor; - vec2 positionSSPX; - vec2 sizePX; - float borderWidthPX; - float borderRadiusPX; -}; - -#include "global_uniforms.glsl" - -layout(set = 0, binding = 1, scalar) readonly buffer Boxes { - Box _Boxes[]; -}; diff --git a/assets/shaders/includes/gui_image_common.glsl b/assets/shaders/includes/gui_image_common.glsl deleted file mode 100644 index 7842858..0000000 --- a/assets/shaders/includes/gui_image_common.glsl +++ /dev/null @@ -1,17 +0,0 @@ -struct Image { - vec4 tint; - vec2 positionSSPX; - vec2 sizePX; - vec2 uvMin; - vec2 uvMax; - uint16_t textureId; -}; - -#include "global_uniforms.glsl" - -layout(set = 0, binding = 1, scalar) readonly buffer Images { - Image _Images[]; -}; - -layout(set = 0, binding = 2) uniform sampler _Sampler; -layout(set = 0, binding = 3) uniform texture2D _Textures[]; diff --git a/assets/shaders/includes/main_common.glsl b/assets/shaders/includes/main_common.glsl deleted file mode 100644 index 2042f2c..0000000 --- a/assets/shaders/includes/main_common.glsl +++ /dev/null @@ -1,58 +0,0 @@ -// --- SET 0 --- GLOBAL -------------------------------------------------------- - -struct PointLight { - vec3 positionWS; - vec3 color; -}; - -struct DirectionalLight { - vec3 directionWS; - vec3 color; -}; - -struct Material { - vec3 baseColor; - vec3 emissive; - float ior; - float metallic; - float normalScale; - float occlusionTextureStrength; - float roughness; - - uint16_t baseColorTexture; - uint16_t emissiveTexture; - uint16_t normalTexture; - uint16_t occlusionRoughnessMetallicTexture; -}; - -struct ObjectUniforms { - mat4 matrixOStoWS; - mat4 matrixOStoWSNormal; - - uint16_t material; -}; - -#include "global_uniforms.glsl" - -layout(set = 0, binding = 1, scalar) readonly buffer PointLights { - uint count; - PointLight lights[]; -} _PointLights; - -layout(set = 0, binding = 2, scalar) readonly buffer DirectionalLights { - uint count; - DirectionalLight lights[]; -} _DirectionalLights; - -layout(set = 0, binding = 3, scalar) readonly buffer Materials { - Material _Materials[]; -}; - -layout(set = 0, binding = 4) uniform sampler _Sampler; -layout(set = 0, binding = 5) uniform texture2D _Textures[]; - -// --- SET 1 --- PER BATCH ----------------------------------------------------- - -layout(set = 1, binding = 0, scalar) readonly buffer ObjectsUniforms { - ObjectUniforms _Object[]; -}; diff --git a/assets/shaders/includes/math.slang b/assets/shaders/includes/math.slang new file mode 100644 index 0000000..7d38b49 --- /dev/null +++ b/assets/shaders/includes/math.slang @@ -0,0 +1,77 @@ +import "structs"; + +static const float INV_PI = 0.31830987; +static const float HALF_INV_PI = 0.15915494; + +static const float3 F90 = float3(1.0); + +float4 texture2DAA(Texture2D texture, SamplerState sampler, float2 texCoord) { + uint width, height; + texture.GetDimensions(width, height); + let size = float2(width, height); + + var texCoordPX = texCoord * size; + let seam = floor(texCoordPX + 0.5); + + texCoordPX = (texCoordPX - seam) / fwidth(texCoordPX) + seam; + texCoordPX = clamp(texCoordPX, seam - 0.5, seam + 0.5); + + texCoord = texCoordPX / size; + return texture.Sample(sampler, texCoord); +} + +float3 fresnelSchlick(float dotVH, float3 f0) { + return lerp(f0, F90, pow(1.0 - dotVH, 5.0)); +} + +float visibilityGGX(float dotNL, float dotNV, float alpha) { + let alphaSquared = alpha * alpha; + + let vGGX = dotNL * sqrt(dotNV * dotNV * (1.0 - alphaSquared) + alphaSquared); + let lGGX = dotNV * sqrt(dotNL * dotNL * (1.0 - alphaSquared) + alphaSquared); + let GGX = vGGX + lGGX; + return select(GGX > 0.0, 0.5 / GGX, 0.0); +} + +float distributionGGX(float dotNH, float alpha) { + let alphaSquared = alpha * alpha; + let tmp = dotNH * dotNH * (alphaSquared - 1.0) + 1.0; + return alphaSquared * INV_PI / (tmp * tmp); +} + +float3 lightOutgoingRadiance( + float3 viewDirectionVS, float3 normalVS, float dotNV, + Surface surf, IncomingLight light, +) { + let halfVectorVS = normalize(light.directionVS + viewDirectionVS); + let dotVH = saturate(dot(viewDirectionVS, halfVectorVS)); + let dotNH = saturate(dot(normalVS, halfVectorVS)); + let dotNL = saturate(dot(normalVS, light.directionVS)); + + let fresnel = fresnelSchlick(dotVH, surf.f0); + let visibility = visibilityGGX(dotNL, dotNV, surf.alpha); + let distribution = distributionGGX(dotNH, surf.alpha); + + let scatteredFactor = (1.0 - fresnel) * (1.0 - surf.metallic) * surf.baseColor * INV_PI; + let reflectedFactor = fresnel * visibility * distribution; + + return (scatteredFactor + reflectedFactor) * light.radiance * dotNL; +} + +float3 toneMapAcesNarkowicz(float3 color) { + static const float A = 2.51; + static const float B = 0.03; + static const float C = 2.43; + static const float D = 0.59; + static const float E = 0.14; + return saturate((color * (A * color + B)) / (color * (C * color + D) + E)); +} + +float3 linearToSrgbColor(float3 color) { + return pow(color, 1.0 / 2.2); +} + +float boxSDF(float2 p, float2 center, float2 halfExtents, float borderRadius) { + let q = abs(p - center) - halfExtents + borderRadius; + return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - borderRadius; +} diff --git a/assets/shaders/includes/structs.slang b/assets/shaders/includes/structs.slang new file mode 100644 index 0000000..37c309c --- /dev/null +++ b/assets/shaders/includes/structs.slang @@ -0,0 +1,82 @@ +struct GlobalUniforms { + float4x4 matrixWStoVS; + float4x4 matrixVStoCS; + float2x3 matrixSSPXtoCS; + + uint pointLightCount; + uint directionalLightCount; + float3 ambientLight; +} + +struct PointLight { + float3 positionWS; + float3 color; + + IncomingLight getIncoming(float4x4 matrixWStoVS, float3 positionVS) { + IncomingLight light; + + let lightPositionVS = mul(matrixWStoVS, float4(positionWS, 1.0)).xyz; + let lightDirectionVS = normalize(lightPositionVS - positionVS); + let lightDistance = distance(positionVS, lightPositionVS); + let lightAttenuation = 1.0 / (lightDistance * lightDistance); + let incomingRadiance = color * lightAttenuation; + + light.radiance = incomingRadiance; + light.directionVS = lightDirectionVS; + return light; + } +} + +struct DirectionalLight { + float3 directionWS; + float3 color; + + IncomingLight getIncoming(float4x4 matrixWStoVS) { + IncomingLight light; + + light.radiance = color; + light.directionVS = normalize(mul(matrixWStoVS, float4(-directionWS, 0.0)).xyz); + return light; + } +} + +struct Material { + float3 baseColor; + float3 emissive; + float ior; + float metallic; + float normalScale; + float occlusionTextureStrength; + float roughness; + + uint16_t baseColorTexture; + uint16_t emissiveTexture; + uint16_t normalTexture; + uint16_t occlusionRoughnessMetallicTexture; +} + +struct ObjectUniforms { + float4x4 matrixOStoWS; + float4x4 matrixOStoWSNormal; + + uint16_t material; +} + +struct Surface { + float3 baseColor; + float alpha; + float metallic; + float3 f0; + + __init(float3 baseColor, float alpha, float metallic, float3 f0) { + this.baseColor = baseColor; + this.alpha = alpha; + this.metallic = metallic; + this.f0 = f0; + } +} + +struct IncomingLight { + float3 radiance; + float3 directionVS; +} diff --git a/assets/shaders/includes/tone_mapping.glsl b/assets/shaders/includes/tone_mapping.glsl deleted file mode 100644 index 43e9f55..0000000 --- a/assets/shaders/includes/tone_mapping.glsl +++ /dev/null @@ -1,8 +0,0 @@ -vec3 toneMapAcesNarkowicz(vec3 color) { - const float A = 2.51; - const float B = 0.03; - const float C = 2.43; - const float D = 0.59; - const float E = 0.14; - return clamp((color * (A * color + B)) / (color * (C * color + D) + E), 0.0, 1.0); -} diff --git a/assets/shaders/main.frag b/assets/shaders/main.frag deleted file mode 100644 index b6303e9..0000000 --- a/assets/shaders/main.frag +++ /dev/null @@ -1,146 +0,0 @@ -#version 460 -#extension GL_EXT_nonuniform_qualifier : require -#extension GL_EXT_scalar_block_layout : require -#extension GL_EXT_shader_16bit_storage : require - -in Varyings { - layout(location = 0) flat uint instance; - layout(location = 1) vec3 positionVS; - layout(location = 2) vec2 texCoord; - layout(location = 3) vec3 normalVS; - layout(location = 4) vec3 tangentVS; - layout(location = 5) vec3 bitangentVS; -} var; - -#include "includes/main_common.glsl" -#include "includes/tone_mapping.glsl" - -layout(location = 0) out vec4 fragColor; - -const float INV_PI = 0.31830987; -const float IOR = 1.45; -const vec3 F90 = vec3(1.0); - -vec3 fresnelSchlick(float dotVH, vec3 f0) { - return mix(f0, F90, pow(1.0 - dotVH, 5.0)); -} - -float visibilityGGX(float dotNL, float dotNV, float alpha) { - float alphaSquared = alpha * alpha; - - float vGGX = dotNL * sqrt(dotNV * dotNV * (1.0 - alphaSquared) + alphaSquared); - float lGGX = dotNV * sqrt(dotNL * dotNL * (1.0 - alphaSquared) + alphaSquared); - float GGX = vGGX + lGGX; - return mix(0.0, 0.5 / GGX, GGX > 0.0); -} - -float distributionGGX(float dotNH, float alpha) { - float alphaSquared = alpha * alpha; - float tmp = dotNH * dotNH * (alphaSquared - 1.0) + 1.0; - return alphaSquared * INV_PI / (tmp * tmp); -} - -vec3 lightOutgoingRadiance( - vec3 viewDirectionVS, vec3 normalVS, float dotNV, - vec3 baseColor, float alpha, float metallic, vec3 f0, - vec3 incomingRadiance, vec3 lightDirectionVS -) { - vec3 halfVectorVS = normalize(lightDirectionVS + viewDirectionVS); - float dotVH = clamp(dot(viewDirectionVS, halfVectorVS), 0.0, 1.0); - float dotNH = clamp(dot(normalVS, halfVectorVS), 0.0, 1.0); - float dotNL = clamp(dot(normalVS, lightDirectionVS), 0.0, 1.0); - - vec3 fresnel = fresnelSchlick(dotVH, f0); - float visibility = visibilityGGX(dotNL, dotNV, alpha); - float distribution = distributionGGX(dotNH, alpha); - - vec3 scatteredFactor = (1.0 - fresnel) * (1.0 - metallic) * baseColor * INV_PI; - vec3 reflectedFactor = fresnel * visibility * distribution; - - return (scatteredFactor + reflectedFactor) * incomingRadiance * dotNL; -} - -vec4 texture2DAA(texture2D tex, vec2 texCoord) { - vec2 size = vec2(textureSize(sampler2D(tex, _Sampler), 0).xy); - vec2 texCoordPX = texCoord * size; - vec2 seam = floor(texCoordPX + vec2(0.5)); - - texCoordPX = (texCoordPX - seam) / fwidth(texCoordPX) + seam; - texCoordPX = clamp(texCoordPX, seam - 0.5, seam + 0.5); - - texCoord = texCoordPX / size; - return texture(sampler2D(tex, _Sampler), texCoord); -} - -#define OBJECT _Object[var.instance] -#define MATERIAL _Materials[uint(OBJECT.material)] - -void main() { - vec4 baseColorTexel = texture2DAA(_Textures[uint(MATERIAL.baseColorTexture)], var.texCoord); - if (baseColorTexel.a < 0.5) { - discard; - } - - vec4 occlusionRoughnessMetallicTexel = texture2DAA(_Textures[uint(MATERIAL.occlusionRoughnessMetallicTexture)], var.texCoord); - vec4 normalTexel = texture2DAA(_Textures[uint(MATERIAL.normalTexture)], var.texCoord); - vec4 emissiveTexel = texture2DAA(_Textures[uint(MATERIAL.emissiveTexture)], var.texCoord); - - vec3 baseColor = MATERIAL.baseColor * baseColorTexel.rgb; - float occlusion = 1.0 + MATERIAL.occlusionTextureStrength * (occlusionRoughnessMetallicTexel.r - 1.0); - float roughness = MATERIAL.roughness * occlusionRoughnessMetallicTexel.g; - float metallic = MATERIAL.metallic * occlusionRoughnessMetallicTexel.b; - vec3 emissive = MATERIAL.emissive * emissiveTexel.rgb; - float ior = MATERIAL.ior; - - vec3 tangentVS = normalize(var.tangentVS); - vec3 bitangentVS = normalize(var.bitangentVS); - mat3 matrixTStoVS = mat3(tangentVS, bitangentVS, var.normalVS); - vec3 normalTS = normalTexel.xyz; - vec3 normalVS = normalize(matrixTStoVS * normalTS); - - vec3 positionVS = var.positionVS; - vec3 viewDirectionVS = normalize(-positionVS); - float dotNV = clamp(dot(normalVS, viewDirectionVS), 0.0, 1.0); - float alpha = roughness * roughness; - - vec3 f0 = vec3(pow((ior - 1.0) / (ior + 1.0), 2.0)); - f0 = mix(f0, baseColor, metallic); - - vec3 outgoingRadiance = vec3(0.0); - - for (uint i = 0; i < _PointLights.count; i++) { - PointLight light = _PointLights.lights[i]; - - vec3 lightPositionVS = (_Global.matrixWStoVS * vec4(light.positionWS, 1.0)).xyz; - vec3 lightDirectionVS = normalize(lightPositionVS - positionVS); - float lightDistance = distance(positionVS, lightPositionVS); - float lightAttenuation = 1.0 / (lightDistance * lightDistance); - vec3 incomingRadiance = light.color * lightAttenuation; - - outgoingRadiance += lightOutgoingRadiance( - viewDirectionVS, normalVS, dotNV, - baseColor, alpha, metallic, f0, - incomingRadiance, lightDirectionVS - ); - } - - for (int i = 0; i < _DirectionalLights.count; i++) { - DirectionalLight light = _DirectionalLights.lights[i]; - - vec3 lightDirectionVS = normalize((_Global.matrixWStoVS * vec4(-light.directionWS, 0.0)).xyz); - vec3 incomingRadiance = light.color; - - outgoingRadiance += lightOutgoingRadiance( - viewDirectionVS, normalVS, dotNV, - baseColor, alpha, metallic, f0, - incomingRadiance, lightDirectionVS - ); - } - - outgoingRadiance += _Global.ambientLight * baseColor * occlusion; - - vec3 toneMappedLinearColor = toneMapAcesNarkowicz(outgoingRadiance); - vec3 toneMappedSrgbColor = pow(toneMappedLinearColor, vec3(1.0 / 2.2)); - - fragColor = vec4(toneMappedSrgbColor, 1.0); -} diff --git a/assets/shaders/main.slang b/assets/shaders/main.slang new file mode 100644 index 0000000..a6ae891 --- /dev/null +++ b/assets/shaders/main.slang @@ -0,0 +1,122 @@ +import "includes/math"; +import "includes/structs"; + +struct Vertex { + [[vk::location(0)]] float3 positionOS; + [[vk::location(1)]] float2 texCoord; + [[vk::location(2)]] float3 normalOS; + [[vk::location(3)]] float4 tangentOS; +} + +struct Varyings { + float4 positionCS : SV_Position; + nointerpolation uint instance; + float3 positionVS; + float2 texCoord; + float3 normalVS; + float3 tangentVS; + float3 bitangentVS; +} + +[[vk::binding(0, 0)]] ConstantBuffer _Global; +[[vk::binding(1, 0)]] StructuredBuffer _PointLights; +[[vk::binding(2, 0)]] StructuredBuffer _DirectionalLights; +[[vk::binding(3, 0)]] StructuredBuffer _Materials; +[[vk::binding(4, 0)]] SamplerState _Sampler; +[[vk::binding(5, 0)]] Texture2D _Textures[]; + +[[vk::binding(0, 1)]] StructuredBuffer _Objects; + +[shader("vertex")] +Varyings vertexMain(Vertex vert, uint instanceID : SV_VulkanInstanceID) { + Varyings out; + + let object = _Objects[instanceID]; + + let positionWS = mul(object.matrixOStoWS, float4(vert.positionOS, 1.0)).xyz; + let positionVS = mul(_Global.matrixWStoVS, float4(positionWS, 1.0)).xyz; + let positionCS = mul(_Global.matrixVStoCS, float4(positionVS, 1.0)); + + let normalWS = normalize(mul(object.matrixOStoWSNormal, float4(vert.normalOS, 0.0)).xyz); + let normalVS = normalize(mul(_Global.matrixWStoVS, float4(normalWS, 0.0)).xyz); + + let tangentWS = normalize(mul(object.matrixOStoWSNormal, float4(vert.tangentOS.xyz, 0.0)).xyz); + let tangentVS = normalize(mul(_Global.matrixWStoVS, float4(tangentWS, 0.0)).xyz); + + let bitangentVS = vert.tangentOS.w * normalize(cross(normalVS, tangentVS)); + + out.positionCS = positionCS; + out.instance = instanceID; + out.positionVS = positionVS; + out.texCoord = vert.texCoord; + out.normalVS = normalVS; + out.tangentVS = tangentVS; + out.bitangentVS = bitangentVS; + return out; +} + +[shader("fragment")] +float4 fragmentMain(Varyings frag) : SV_Target { + let material = _Materials[_Objects[frag.instance].material]; + + let baseColorTexel = texture2DAA(_Textures[material.baseColorTexture], _Sampler, frag.texCoord); + if (baseColorTexel.a < 0.5) { + discard; + } + + let occlusionRoughnessMetallicTexel = texture2DAA(_Textures[material.occlusionRoughnessMetallicTexture], _Sampler, frag.texCoord); + let normalTexel = texture2DAA(_Textures[material.normalTexture], _Sampler, frag.texCoord); + let emissiveTexel = texture2DAA(_Textures[material.emissiveTexture], _Sampler, frag.texCoord); + + let baseColor = material.baseColor * baseColorTexel.rgb; + let occlusion = 1.0 + material.occlusionTextureStrength * (occlusionRoughnessMetallicTexel.r - 1.0); + let roughness = material.roughness * occlusionRoughnessMetallicTexel.g; + let metallic = material.metallic * occlusionRoughnessMetallicTexel.b; + let emissive = material.emissive * emissiveTexel.rgb; + let ior = material.ior; + + let tangentVS = normalize(frag.tangentVS); + let bitangentVS = normalize(frag.bitangentVS); + let matrixTStoVS = transpose(float3x3(tangentVS, bitangentVS, frag.normalVS)); + let normalTS = normalTexel.xyz; + let normalVS = normalize(mul(matrixTStoVS, normalTS)); + + let positionVS = frag.positionVS; + let viewDirectionVS = normalize(-positionVS); + let dotNV = saturate(dot(normalVS, viewDirectionVS)); + let alpha = roughness * roughness; + + var f0 = float3(pow((ior - 1.0) / (ior + 1.0), 2.0)); + f0 = lerp(f0, baseColor, metallic); + + let surface = Surface(baseColor, alpha, metallic, f0); + + var outgoingRadiance = float3(0.0); + + let pointLightCount = _Global.pointLightCount; + for (uint i = 0; i < pointLightCount; i++) { + let light = _PointLights[i].getIncoming(_Global.matrixWStoVS, positionVS); + + outgoingRadiance += lightOutgoingRadiance( + viewDirectionVS, normalVS, dotNV, + surface, light, + ); + } + + let directionalLightCount = _Global.directionalLightCount; + for (uint i = 0; i < directionalLightCount; i++) { + let light = _DirectionalLights[i].getIncoming(_Global.matrixWStoVS); + + outgoingRadiance += lightOutgoingRadiance( + viewDirectionVS, normalVS, dotNV, + surface, light, + ); + } + + outgoingRadiance += _Global.ambientLight * baseColor * occlusion; + + let toneMappedLinearColor = toneMapAcesNarkowicz(outgoingRadiance); + let toneMappedSrgbColor = linearToSrgbColor(toneMappedLinearColor); + + return float4(toneMappedSrgbColor, 1.0); +} diff --git a/assets/shaders/main.vert b/assets/shaders/main.vert deleted file mode 100644 index f041cb7..0000000 --- a/assets/shaders/main.vert +++ /dev/null @@ -1,44 +0,0 @@ -#version 460 -#extension GL_EXT_nonuniform_qualifier : require -#extension GL_EXT_scalar_block_layout : require -#extension GL_EXT_shader_16bit_storage : require - -layout(location = 0) in vec3 positionOS; -layout(location = 1) in vec2 texCoord; -layout(location = 2) in vec3 normalOS; -layout(location = 3) in vec4 tangentOS; - -out Varyings { - layout(location = 0) flat uint instance; - layout(location = 1) vec3 positionVS; - layout(location = 2) vec2 texCoord; - layout(location = 3) vec3 normalVS; - layout(location = 4) vec3 tangentVS; - layout(location = 5) vec3 bitangentVS; -} var; - -#include "includes/main_common.glsl" - -#define OBJECT _Object[gl_InstanceIndex] - -void main() { - vec3 positionWS = (OBJECT.matrixOStoWS * vec4(positionOS, 1.0)).xyz; - vec3 positionVS = (_Global.matrixWStoVS * vec4(positionWS, 1.0)).xyz; - vec4 positionCS = _Global.matrixVStoCS * vec4(positionVS, 1.0); - - vec3 normalWS = normalize((OBJECT.matrixOStoWSNormal * vec4(normalOS, 0.0)).xyz); - vec3 normalVS = normalize((_Global.matrixWStoVS * vec4(normalWS, 0.0)).xyz); - - vec3 tangentWS = normalize((OBJECT.matrixOStoWSNormal * vec4(tangentOS.xyz, 0.0)).xyz); - vec3 tangentVS = normalize((_Global.matrixWStoVS * vec4(tangentWS, 0.0)).xyz); - - vec3 bitangentVS = tangentOS.w * normalize(cross(normalVS, tangentVS)); - - gl_Position = positionCS; - var.instance = gl_InstanceIndex; - var.positionVS = positionVS; - var.texCoord = texCoord; - var.normalVS = normalVS; - var.tangentVS = tangentVS; - var.bitangentVS = bitangentVS; -} diff --git a/assets/shaders/skybox.frag b/assets/shaders/skybox.frag deleted file mode 100644 index d306458..0000000 --- a/assets/shaders/skybox.frag +++ /dev/null @@ -1,22 +0,0 @@ -#version 460 - -in Varyings { - layout(location = 0) vec3 texCoord; -} var; - -layout(set = 0, binding = 1) uniform sampler _Sampler; -layout(set = 0, binding = 2) uniform textureCube _Texture; - -layout(location = 0) out vec4 fragColor; - -#include "includes/tone_mapping.glsl" - -void main() { - vec4 texel = texture(samplerCube(_Texture, _Sampler), var.texCoord); - vec3 outgoingRadiance = texel.rgb; - - vec3 toneMappedLinearColor = toneMapAcesNarkowicz(outgoingRadiance); - vec3 toneMappedSrgbColor = pow(toneMappedLinearColor, vec3(1.0 / 2.2)); - - fragColor = vec4(toneMappedSrgbColor, 1.0); -} diff --git a/assets/shaders/skybox.slang b/assets/shaders/skybox.slang new file mode 100644 index 0000000..69b061c --- /dev/null +++ b/assets/shaders/skybox.slang @@ -0,0 +1,38 @@ +import "includes/math"; +import "includes/structs"; + +[[vk::binding(0, 0)]] ConstantBuffer _Global; +[[vk::binding(1, 0)]] SamplerState _Sampler; +[[vk::binding(2, 0)]] TextureCube _Texture; + +struct Vertex { + [[vk::location(0)]] float3 directionWS; +} + +struct Varyings { + float4 positionCS : SV_Position; + float3 texCoord; +} + +[shader("vertex")] +Varyings vertexMain(Vertex vert) { + Varyings out; + + let directionVS = mul(_Global.matrixWStoVS, float4(vert.directionWS, 0.0)).xyz; + let directionCS = mul(_Global.matrixVStoCS, float4(directionVS, 0.0)); + + out.positionCS = float4(directionCS.xy, 0.0, directionCS.w); + out.texCoord = -vert.directionWS; + return out; +} + +[shader("fragment")] +float4 fragmentMain(Varyings frag) : SV_Target { + let texel = _Texture.Sample(_Sampler, frag.texCoord); + let outgoingRadiance = texel.rgb; + + let toneMappedLinearColor = toneMapAcesNarkowicz(outgoingRadiance); + let toneMappedSrgbColor = linearToSrgbColor(toneMappedLinearColor); + + return float4(toneMappedSrgbColor, 1.0); +} diff --git a/assets/shaders/skybox.vert b/assets/shaders/skybox.vert deleted file mode 100644 index b829a65..0000000 --- a/assets/shaders/skybox.vert +++ /dev/null @@ -1,18 +0,0 @@ -#version 460 -#extension GL_EXT_scalar_block_layout : require - -layout(location = 0) in vec3 directionWS; - -out Varyings { - layout(location = 0) vec3 texCoord; -} var; - -#include "includes/global_uniforms.glsl" - -void main() { - vec3 directionVS = (_Global.matrixWStoVS * vec4(directionWS, 0.0)).xyz; - vec4 directionCS = _Global.matrixVStoCS * vec4(directionVS, 0.0); - - gl_Position = vec4(directionCS.xy, 0.0, directionCS.w); - var.texCoord = -directionWS; -} diff --git a/build.zig b/build.zig index 9576739..14600ab 100644 --- a/build.zig +++ b/build.zig @@ -73,30 +73,36 @@ pub fn build(b: *std.Build) !void { run_step.dependOn(&run_cmd.step); } -fn isShaderExtension(extension: []const u8) bool { - return std.mem.eql(u8, extension, ".vert") or - std.mem.eql(u8, extension, ".frag") or - std.mem.eql(u8, extension, ".comp"); -} - fn addShaders(b: *std.Build, exe_mod: *std.Build.Module) !void { var dir = try b.build_root.handle.openDir(b.graph.io, "assets/shaders", .{ .iterate = true }); defer dir.close(b.graph.io); - var walker = try dir.walk(b.allocator); - defer walker.deinit(); + var it = dir.iterateAssumeFirstIteration(); - while (try walker.next(b.graph.io)) |entry| { + while (try it.next(b.graph.io)) |entry| { if (entry.kind != .file) continue; - if (!isShaderExtension(std.fs.path.extension(entry.path))) continue; - const glslc = b.addSystemCommand(&.{ "glslc", "-g", "--target-env=vulkan1.2" }); - glslc.addFileArg(b.path(b.pathJoin(&.{ "assets/shaders", entry.path }))); - glslc.addArg("-o"); - const spv = glslc.addOutputFileArg(b.fmt("{s}.spv", .{entry.basename})); - glslc.addArgs(&.{ "-MD", "-MF" }); - _ = glslc.addDepFileOutputArg(b.fmt("{s}.d", .{entry.basename})); + const stem = std.fs.path.stem(entry.name); + const extension = std.fs.path.extension(entry.name); + if (!std.mem.eql(u8, extension, ".slang")) continue; - exe_mod.addAnonymousImport(b.fmt("shaders/{s}", .{entry.path}), .{ .root_source_file = spv }); + const slangc = b.addSystemCommand(&.{ + "slangc", + "-g", + "-target", + "spirv", + "-fvk-use-scalar-layout", + "-matrix-layout-column-major", + }); + + slangc.addArg("-o"); + const spv = slangc.addOutputFileArg(b.fmt("{s}.spv", .{stem})); + + slangc.addArg("-depfile"); + _ = slangc.addDepFileOutputArg(b.fmt("{s}.d", .{stem})); + + slangc.addFileArg(b.path(b.pathJoin(&.{ "assets/shaders", entry.name }))); + + exe_mod.addAnonymousImport(b.fmt("shaders/{s}", .{stem}), .{ .root_source_file = spv }); } } diff --git a/src/Game.zig b/src/Game.zig index 396a10a..93d894e 100644 --- a/src/Game.zig +++ b/src/Game.zig @@ -55,6 +55,14 @@ const chunk_descriptor_pool = 1024; const camera_near_plane = 0.1; +const point_lights_data = [_]shaders.PointLight{}; +const directional_lights_data = [_]shaders.DirectionalLight{ + .{ + .directionWS = .unit_nz, + .color = .init(0.3, 0.3, 0.3), + }, +}; + pub fn init() !Game { const allocator_general = ctx.allocator_general; const allocator_frame = ctx.allocator_frame; @@ -158,13 +166,9 @@ pub fn init() !Game { errdefer engine.destroyPipelineLayout(pipeline_layout); engine.setObjectName(pipeline_layout, "PL Main", .{}); - const vertex_shader = try engine.createShaderModule(.{ .code = &shaders.main_vert_spv }); - defer engine.destroyShaderModule(vertex_shader); - engine.setObjectName(vertex_shader, "SM main_vert", .{}); - - const fragment_shader = try engine.createShaderModule(.{ .code = &shaders.main_frag_spv }); - defer engine.destroyShaderModule(fragment_shader); - engine.setObjectName(fragment_shader, "SM main_frag", .{}); + const shader = try engine.createShaderModule(.{ .code = &shaders.main_spv }); + defer engine.destroyShaderModule(shader); + engine.setObjectName(shader, "SM main", .{}); var vertex_buffer = try shaders.VertexBuffer.init(.{ .usage = .vertex, @@ -267,13 +271,13 @@ pub fn init() !Game { .stages = &.{ .{ .stage = .{ .vertex_bit = true }, - .module = vertex_shader, - .name = "main", + .module = shader, + .name = "vertexMain", }, .{ .stage = .{ .fragment_bit = true }, - .module = fragment_shader, - .name = "main", + .module = shader, + .name = "fragmentMain", }, }, .vertex_input_state = .{ @@ -609,22 +613,8 @@ pub fn init() !Game { }); } - const point_lights_data: []const shaders.PointLight = &.{}; - try point_lights.write(.{ - .header = @intCast(point_lights_data.len), - .elements = point_lights_data, - }); - - const directional_lights_data: []const shaders.DirectionalLight = &.{ - .{ - .directionWS = .unit_nz, - .color = .init(0.3, 0.3, 0.3), - }, - }; - try directional_lights.write(.{ - .header = @intCast(directional_lights_data.len), - .elements = directional_lights_data, - }); + try point_lights.write(.{ .elements = &point_lights_data }); + try directional_lights.write(.{ .elements = &directional_lights_data }); var skybox = try Skybox.load("skybox.hdr", 512, global_uniforms.buffer); errdefer skybox.deinit(); @@ -857,6 +847,9 @@ fn render(self: *Game) !void { .matrixWStoVS = matrix_ws_to_vs, .matrixVStoCS = matrix_vs_to_cs, .matrixSSPXtoCS = matrix_sspx_to_cs, + + .pointLightCount = point_lights_data.len, + .directionalLightCount = directional_lights_data.len, .ambientLight = ambient_light, }; diff --git a/src/engine/Gui.zig b/src/engine/Gui.zig index 4e3b790..aaf0ca2 100644 --- a/src/engine/Gui.zig +++ b/src/engine/Gui.zig @@ -156,13 +156,9 @@ pub fn init() !*Gui { errdefer engine.destroyPipelineLayout(box_pipeline_layout); engine.setObjectName(box_pipeline_layout, "PL GUI Box", .{}); - const box_vertex_shader = try engine.createShaderModule(.{ .code = &shaders.gui_box_vert_spv }); - defer engine.destroyShaderModule(box_vertex_shader); - engine.setObjectName(box_vertex_shader, "SM gui_box_vert", .{}); - - const box_fragment_shader = try engine.createShaderModule(.{ .code = &shaders.gui_box_frag_spv }); - defer engine.destroyShaderModule(box_fragment_shader); - engine.setObjectName(box_fragment_shader, "SM gui_box_frag", .{}); + const box_shader = try engine.createShaderModule(.{ .code = &shaders.gui_box_spv }); + defer engine.destroyShaderModule(box_shader); + engine.setObjectName(box_shader, "SM gui_box", .{}); var index_buffer = try shaders.IndexBuffer.init(.{ .usage = .index, @@ -179,13 +175,13 @@ pub fn init() !*Gui { .stages = &.{ .{ .stage = .{ .vertex_bit = true }, - .module = box_vertex_shader, - .name = "main", + .module = box_shader, + .name = "vertexMain", }, .{ .stage = .{ .fragment_bit = true }, - .module = box_fragment_shader, - .name = "main", + .module = box_shader, + .name = "fragmentMain", }, }, .vertex_input_state = .{}, diff --git a/src/engine/Skybox.zig b/src/engine/Skybox.zig index f0df16d..efbb026 100644 --- a/src/engine/Skybox.zig +++ b/src/engine/Skybox.zig @@ -261,6 +261,26 @@ pub fn load( }); errdefer engine.destroyImageView(cubemap_image_view); + // NOTE Because slang cannot conceive of such a thing as RWTextureCube + // (equivalent to imageCube in GLSL, which docs claim exists, but it + // doesn't), we have to resort to RWTexture2DArray (WTexture2DArray to be + // specific) in the compute shader. We have to make another image view with + // appropriate view_type to pass validation. + + const cubemap_image_view_2d_array = try engine.createImageView(.{ + .image = cubemap_image, + .view_type = .@"2d_array", + .format = .r16g16b16a16_sfloat, + .subresource_range = .{ + .aspect_mask = .{ .color_bit = true }, + .base_mip_level = 0, + .level_count = 1, + .base_array_layer = 0, + .layer_count = 6, + }, + }); + defer engine.destroyImageView(cubemap_image_view_2d_array); + // --- PIPELINE AND DESCRIPTORS -------------------------------------------- const sampler = try engine.createSampler(.{ @@ -312,7 +332,7 @@ pub fn load( }); defer engine.destroyPipelineLayout(compute_pipeline_layout); - const compute_shader = try engine.createShaderModule(.{ .code = &shaders.equirect_to_cube_comp_spv }); + const compute_shader = try engine.createShaderModule(.{ .code = &shaders.equirect_to_cube_spv }); defer engine.destroyShaderModule(compute_shader); var compute_pipeline: vk.Pipeline = undefined; @@ -380,7 +400,7 @@ pub fn load( .image = &.{ .{ .sampler = .null_handle, - .image_view = cubemap_image_view, + .image_view = cubemap_image_view_2d_array, .image_layout = .general, }, }, @@ -622,23 +642,20 @@ pub fn load( }, }); - const vertex_shader = try engine.createShaderModule(.{ .code = &shaders.skybox_vert_spv }); - defer engine.destroyShaderModule(vertex_shader); - - const fragment_shader = try engine.createShaderModule(.{ .code = &shaders.skybox_frag_spv }); - defer engine.destroyShaderModule(fragment_shader); + const shader = try engine.createShaderModule(.{ .code = &shaders.skybox_spv }); + defer engine.destroyShaderModule(shader); const pipeline = try engine.createGraphicsPipeline(.{ .stages = &.{ .{ .stage = .{ .vertex_bit = true }, - .module = vertex_shader, - .name = "main", + .module = shader, + .name = "vertexMain", }, .{ .stage = .{ .fragment_bit = true }, - .module = fragment_shader, - .name = "main", + .module = shader, + .name = "fragmentMain", }, }, .vertex_input_state = .{ diff --git a/src/shaders.zig b/src/shaders.zig index fe39ae7..f84468a 100644 --- a/src/shaders.zig +++ b/src/shaders.zig @@ -8,8 +8,8 @@ const Textures = @import("engine/Textures.zig"); pub const VertexBuffer = GenericBuffer(void, Vertex); pub const IndexBuffer = GenericBuffer(void, Index); pub const GlobalUniformsBuffer = GenericBuffer(GlobalUniforms, void); -pub const PointLightBuffer = GenericBuffer(u32, PointLight); -pub const DirectionalLightBuffer = GenericBuffer(u32, DirectionalLight); +pub const PointLightBuffer = GenericBuffer(void, PointLight); +pub const DirectionalLightBuffer = GenericBuffer(void, DirectionalLight); pub const MaterialBuffer = GenericBuffer(void, Material); pub const ObjectUniformsBuffer = GenericBuffer(void, ObjectUniforms); @@ -47,6 +47,9 @@ pub const GlobalUniforms = extern struct { matrixWStoVS: vm.Matrix4x4, matrixVStoCS: vm.Matrix4x4, matrixSSPXtoCS: vm.Matrix3x2, + + pointLightCount: u32, + directionalLightCount: u32, ambientLight: vm.Vector3, }; @@ -96,10 +99,7 @@ pub const ObjectUniforms = extern struct { material: Materials.Id, }; -pub const equirect_to_cube_comp_spv align(4) = @embedFile("shaders/equirect_to_cube.comp").*; -pub const gui_box_vert_spv align(4) = @embedFile("shaders/gui_box.vert").*; -pub const gui_box_frag_spv align(4) = @embedFile("shaders/gui_box.frag").*; -pub const main_vert_spv align(4) = @embedFile("shaders/main.vert").*; -pub const main_frag_spv align(4) = @embedFile("shaders/main.frag").*; -pub const skybox_vert_spv align(4) = @embedFile("shaders/skybox.vert").*; -pub const skybox_frag_spv align(4) = @embedFile("shaders/skybox.frag").*; +pub const equirect_to_cube_spv align(4) = @embedFile("shaders/equirect_to_cube").*; +pub const gui_box_spv align(4) = @embedFile("shaders/gui_box").*; +pub const main_spv align(4) = @embedFile("shaders/main").*; +pub const skybox_spv align(4) = @embedFile("shaders/skybox").*;