diff --git a/assets/materials/Bricks.json b/assets/materials/Bricks.json new file mode 100644 index 0000000..d8f4aad --- /dev/null +++ b/assets/materials/Bricks.json @@ -0,0 +1,6 @@ +{ + "baseColorTexture": "Bricks_BaseColor.png", + "metallic": 0, + "normalTexture": "Bricks_Normal.png", + "roughness": 1 +} diff --git a/assets/materials/Dirt.json b/assets/materials/Dirt.json new file mode 100644 index 0000000..3ff81e5 --- /dev/null +++ b/assets/materials/Dirt.json @@ -0,0 +1,6 @@ +{ + "baseColorTexture": "Dirt_BaseColor.png", + "metallic": 0, + "normalTexture": "Dirt_Normal.png", + "roughness": 1 +} diff --git a/assets/materials/Gold.json b/assets/materials/Gold.json new file mode 100644 index 0000000..a071cf2 --- /dev/null +++ b/assets/materials/Gold.json @@ -0,0 +1,6 @@ +{ + "baseColorTexture": "Gold_BaseColor.png", + "metallic": 1, + "normalTexture": "Gold_Normal.png", + "roughness": 0 +} diff --git a/assets/materials/Stone.json b/assets/materials/Stone.json new file mode 100644 index 0000000..a84773f --- /dev/null +++ b/assets/materials/Stone.json @@ -0,0 +1,6 @@ +{ + "baseColorTexture": "Stone_BaseColor.png", + "metallic": 0, + "normalTexture": "Stone_Normal.png", + "roughness": 1 +} diff --git a/assets/shaders.glsl b/assets/shaders.glsl deleted file mode 100644 index 62c4b43..0000000 --- a/assets/shaders.glsl +++ /dev/null @@ -1,276 +0,0 @@ -@vs vs_main - -layout(location = 0) in vec3 positionOS; -layout(location = 1) in vec2 texCoord; -layout(location = 2) in vec3 normalOS; -layout(location = 3) in vec4 tangentOS; - -layout(location = 0) out vec3 var_positionVS; -layout(location = 1) out vec2 var_texCoord; -layout(location = 2) out vec3 var_normalVS; -layout(location = 3) out vec3 var_tangentVS; -layout(location = 4) out vec3 var_bitangentVS; - -layout(binding = 0) uniform Global_Uniforms_Vertex { - mat4 _MatrixWStoVS; - mat4 _MatrixVStoCS; -}; - -layout(binding = 1) uniform Object_Uniforms { - mat4 _MatrixOStoWS; - mat4 _MatrixOStoWSNormal; -}; - -void main() { - vec3 positionWS = (_MatrixOStoWS * vec4(positionOS, 1.0)).xyz; - vec3 positionVS = (_MatrixWStoVS * vec4(positionWS, 1.0)).xyz; - vec4 positionCS = _MatrixVStoCS * vec4(positionVS, 1.0); - - vec3 normalWS = normalize((_MatrixOStoWSNormal * vec4(normalOS, 0.0)).xyz); - vec3 normalVS = normalize((_MatrixWStoVS * vec4(normalWS, 0.0)).xyz); - - vec3 tangentWS = normalize((_MatrixOStoWS * vec4(tangentOS.xyz, 0.0)).xyz); - vec3 tangentVS = normalize((_MatrixWStoVS * vec4(tangentWS, 0.0)).xyz); - - vec3 bitangentVS = tangentOS.w * normalize(cross(normalVS, tangentVS)); - - gl_Position = positionCS; - var_positionVS = positionVS; - var_texCoord = texCoord; - var_normalVS = normalVS; - var_tangentVS = tangentVS; - var_bitangentVS = bitangentVS; -} -@end - -@fs fs_main - -struct Point_Light { - vec3 positionWS; - vec3 color; -}; - -struct Directional_Light { - vec3 directionWS; - vec3 color; -}; - -layout(location = 0) in vec3 var_positionVS; -layout(location = 1) in vec2 var_texCoord; -layout(location = 2) in vec3 var_normalVS; -layout(location = 3) in vec3 var_tangentVS; -layout(location = 4) in vec3 var_bitangentVS; - -out vec4 fragColor; - -layout(binding = 2) uniform Global_Uniforms_Fragment { - mat4 _MatrixWStoVS; - vec3 _AmbientLight; - int _PointLightCount; - int _DirectionalLightCount; -}; - -layout(binding = 3) uniform Material_Uniforms { - int _TextureIndex; -}; - -layout(binding = 0) readonly buffer Point_Lights { - Point_Light _PointLights[]; -}; - -layout(binding = 1) readonly buffer Directional_Lights { - Directional_Light _DirectionalLights[]; -}; - -layout(binding = 2) uniform texture2DArray _BaseColorTexture; -layout(binding = 3) uniform texture2DArray _OcclusionRoughnessMetallicTexture; -layout(binding = 4) uniform texture2DArray _NormalTexture; - -layout(binding = 0) uniform sampler _Sampler; - -const float INV_PI = 0.31830987; -const float IOR = 1.45; -const vec3 DIELECTRIC_F0 = vec3(pow((IOR - 1.0) / (IOR + 1.0), 2.0)); -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 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); -} - -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 texture2DArrayAA(texture2DArray tex, vec2 texCoord) { - vec2 size = vec2(textureSize(sampler2DArray(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); - - vec3 texCoord3 = vec3(texCoordPX / size, float(_TextureIndex)); - return texture(sampler2DArray(tex, _Sampler), texCoord3); -} - -void main() { - vec4 baseColorTexel = texture2DArrayAA(_BaseColorTexture, var_texCoord); - vec4 occlusionRoughnessMetallicTexel = texture2DArrayAA(_OcclusionRoughnessMetallicTexture, var_texCoord); - vec4 normalTextureTexel = texture2DArrayAA(_NormalTexture, var_texCoord); - - vec3 baseColor = baseColorTexel.rgb; - float occlusion = occlusionRoughnessMetallicTexel.r; - float roughness = occlusionRoughnessMetallicTexel.g; - float metallic = occlusionRoughnessMetallicTexel.b; - - vec3 tangentVS = normalize(var_tangentVS); - vec3 bitangentVS = normalize(var_bitangentVS); - mat3 matrixTStoVS = mat3(tangentVS, bitangentVS, var_normalVS); - vec3 normalTS = normalTextureTexel.xyz * 2.0 - 1.0; - 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 = mix(DIELECTRIC_F0, baseColor, metallic); - - vec3 outgoingRadiance = vec3(0.0); - - for (int i = 0; i < _PointLightCount; i++) { - Point_Light light = _PointLights[i]; - - vec3 lightPositionVS = (_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 < _DirectionalLightCount; i++) { - Directional_Light light = _DirectionalLights[i]; - - vec3 lightDirectionVS = normalize((_MatrixWStoVS * vec4(-light.directionWS, 0.0)).xyz); - vec3 incomingRadiance = light.color; - - outgoingRadiance += lightOutgoingRadiance( - viewDirectionVS, normalVS, dotNV, - baseColor, alpha, metallic, f0, - incomingRadiance, lightDirectionVS - ); - } - - outgoingRadiance += _AmbientLight * baseColor * occlusion; - - vec3 toneMappedLinearColor = toneMapAcesNarkowicz(outgoingRadiance); - vec3 toneMappedSrgbColor = pow(toneMappedLinearColor, vec3(1.0 / 2.2)); - - fragColor = vec4(toneMappedSrgbColor, 1.0); -} - -@end - -@cs cs_equirectangular_to_cubemap - -layout(binding = 0) uniform Layer_Index { - int _LayerIndex; -}; - -layout(binding = 0) uniform texture2D _EquirectangularTexture; -layout(binding = 1, rgba16f) uniform writeonly image2DArray _CubemapImage; - -layout(binding = 0) uniform sampler _EquirectangularSampler; - -const float INV_PI = 0.31830987; -const float HALF_INV_PI = 0.15915494; - -layout(local_size_x = 8, local_size_y = 8) in; -void main() { - vec2 size = vec2(imageSize(_CubemapImage).xy); - vec2 texCoord = (vec2(gl_GlobalInvocationID.xy) + vec2(0.5)) / size; - - texCoord = texCoord * vec2(2.0) - vec2(1.0); // Map to range [-1, 1] - - vec3 cubeCoord; - if (_LayerIndex == 0) { - // Positive X - cubeCoord = vec3(1, -texCoord.y, -texCoord.x); - } else if (_LayerIndex == 1) { - // Negative X - cubeCoord = vec3(-1, -texCoord.y, texCoord.x); - } else if (_LayerIndex == 2) { - // Positive Y - cubeCoord = vec3(texCoord.x, 1, texCoord.y); - } else if (_LayerIndex == 3) { - // Negative Y - cubeCoord = vec3(texCoord.x, -1, -texCoord.y); - } else if (_LayerIndex == 4) { - // Positive Z - cubeCoord = vec3(texCoord.x, -texCoord.y, 1); - } else if (_LayerIndex == 5) { - // Negative Z - cubeCoord = vec3(-texCoord.x, -texCoord.y, -1); - } - - 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(ivec2(gl_GlobalInvocationID.xy), _LayerIndex), irradiance); -} - -@end - -@program main vs_main fs_main -@program equirectangular_to_cubemap cs_equirectangular_to_cubemap diff --git a/assets/shaders/equirect_to_cube.comp b/assets/shaders/equirect_to_cube.comp new file mode 100644 index 0000000..ec16d03 --- /dev/null +++ b/assets/shaders/equirect_to_cube.comp @@ -0,0 +1,49 @@ +#version 460 + +layout(set = 0, binding = 0) uniform sampler _EquirectangularSampler; + +layout(set = 1, binding = 0) uniform texture2D _EquirectangularTexture; +layout(set = 2, binding = 1, rgba16f) uniform writeonly restrict imageCube _CubemapImage; + +const float INV_PI = 0.31830987; +const float HALF_INV_PI = 0.15915494; + +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; + if (layerIndex == 0) { + // Positive X + cubeCoord = vec3(1, -texCoord.y, -texCoord.x); + } else if (layerIndex == 1) { + // Negative X + cubeCoord = vec3(-1, -texCoord.y, texCoord.x); + } else if (layerIndex == 2) { + // Positive Y + cubeCoord = vec3(texCoord.x, 1, texCoord.y); + } else if (layerIndex == 3) { + // Negative Y + cubeCoord = vec3(texCoord.x, -1, -texCoord.y); + } else if (layerIndex == 4) { + // Positive Z + cubeCoord = vec3(texCoord.x, -texCoord.y, 1); + } else if (layerIndex == 5) { + // Negative Z + cubeCoord = vec3(-texCoord.x, -texCoord.y, -1); + } + + 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(ivec2(gl_GlobalInvocationID.xy), layerIndex), irradiance); +} diff --git a/assets/shaders/main.frag b/assets/shaders/main.frag new file mode 100644 index 0000000..7f2e68a --- /dev/null +++ b/assets/shaders/main.frag @@ -0,0 +1,147 @@ +#version 460 +#extension GL_EXT_nonuniform_qualifier : require +#extension GL_EXT_scalar_block_layout : require + +in Varyings { + layout(location = 0) vec3 positionVS; + layout(location = 1) vec2 texCoord; + layout(location = 2) vec3 normalVS; + layout(location = 3) vec3 tangentVS; + layout(location = 4) vec3 bitangentVS; +} var; + +#include "main_common.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 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); +} + +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 MATERIAL _Materials[_Object.material] + +void main() { + vec4 baseColorTexel = texture2DAA(_Textures[MATERIAL.baseColorTexture], var.texCoord); + vec4 occlusionRoughnessMetallicTexel = texture2DAA(_Textures[MATERIAL.occlusionRoughnessMetallicTexture], var.texCoord); + vec4 normalTexel = texture2DAA(_Textures[MATERIAL.normalTexture], var.texCoord); + vec4 emissiveTexel = texture2DAA(_Textures[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.vert b/assets/shaders/main.vert new file mode 100644 index 0000000..cc6bb41 --- /dev/null +++ b/assets/shaders/main.vert @@ -0,0 +1,39 @@ +#version 460 +#extension GL_EXT_nonuniform_qualifier : require +#extension GL_EXT_scalar_block_layout : 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) vec3 positionVS; + layout(location = 1) vec2 texCoord; + layout(location = 2) vec3 normalVS; + layout(location = 3) vec3 tangentVS; + layout(location = 4) vec3 bitangentVS; +} var; + +#include "main_common.glsl" + +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.positionVS = positionVS; + var.texCoord = texCoord; + var.normalVS = normalVS; + var.tangentVS = tangentVS; + var.bitangentVS = bitangentVS; +} diff --git a/assets/shaders/main_common.glsl b/assets/shaders/main_common.glsl new file mode 100644 index 0000000..3717059 --- /dev/null +++ b/assets/shaders/main_common.glsl @@ -0,0 +1,57 @@ +// --- SET 0 --- GLOBAL -------------------------------------------------------- + +struct PointLight { + vec3 positionWS; + vec3 color; +}; + +struct DirectionalLight { + vec3 directionWS; + vec3 color; +}; + +struct Material { + vec3 baseColor; + uint baseColorTexture; + vec3 emissive; + uint emissiveTexture; + float ior; + float metallic; + float normalScale; + uint normalTexture; + uint occlusionRoughnessMetallicTexture; + float occlusionTextureStrength; + float roughness; +}; + +layout(set = 0, binding = 0, scalar) uniform GlobalUniforms { + mat4 matrixWStoVS; + mat4 matrixVStoCS; + vec3 ambientLight; +} _Global; + +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) uniform sampler _Sampler; +layout(set = 0, binding = 4) uniform texture2D _Textures[]; + +layout(set = 0, binding = 5, scalar) readonly buffer Materials { + Material _Materials[]; +}; + +// --- SET 1 --- PER OBJECT ---------------------------------------------------- + +layout(set = 1, binding = 0, scalar) uniform ObjectUniforms { + mat4 matrixOStoWS; + mat4 matrixOStoWSNormal; + + uint material; +} _Object; diff --git a/assets/textures/BaseColor.png b/assets/textures/BaseColor.png deleted file mode 100644 index d3b8742..0000000 --- a/assets/textures/BaseColor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a93379cd6b8ca483163bd3d059e6270b9f863b63f72dab7baabf2d1521412e64 -size 2779 diff --git a/assets/textures/Bricks_BaseColor.png b/assets/textures/Bricks_BaseColor.png new file mode 100644 index 0000000..c11867d --- /dev/null +++ b/assets/textures/Bricks_BaseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95b3ec12069aaac729e25355a68664910c65cf61b0cb405e408d6a67dd217532 +size 498 diff --git a/assets/textures/Bricks_Normal.png b/assets/textures/Bricks_Normal.png new file mode 100644 index 0000000..31481e1 --- /dev/null +++ b/assets/textures/Bricks_Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7b1dc10fe620efdc035e4814e6f60b8154ea4f602861099776d2d7cfae45982 +size 2268 diff --git a/assets/textures/Dirt_BaseColor.png b/assets/textures/Dirt_BaseColor.png new file mode 100644 index 0000000..e1becfd --- /dev/null +++ b/assets/textures/Dirt_BaseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11b76f8a6f2bd32e734b41d030be8dc0a90fc7cc60930f081704108b82941110 +size 506 diff --git a/assets/textures/Dirt_Normal.png b/assets/textures/Dirt_Normal.png new file mode 100644 index 0000000..73e26c5 --- /dev/null +++ b/assets/textures/Dirt_Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3efdf4e5c3f71c7579589d05da08ecd78a00d997dfc15ce7ee9bdb5085562394 +size 3263 diff --git a/assets/textures/Gold_BaseColor.png b/assets/textures/Gold_BaseColor.png new file mode 100644 index 0000000..1b6b737 --- /dev/null +++ b/assets/textures/Gold_BaseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c158e6647b2978e3b4f777f72845ccd3c8ab74a2a6d444b5604dc98519046d1a +size 502 diff --git a/assets/textures/Gold_Normal.png b/assets/textures/Gold_Normal.png new file mode 100644 index 0000000..7338cd7 --- /dev/null +++ b/assets/textures/Gold_Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0419332c00de9212b7bc72a614c29df0f1c37076b0f71ef7e88b8d4a3c2e518 +size 1929 diff --git a/assets/textures/Normal.png b/assets/textures/Normal.png deleted file mode 100644 index ba9d01b..0000000 --- a/assets/textures/Normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ddf3fab756c50827096cbced25cbde34ca6b50b8c3da4d904b3ce9eabb782c76 -size 46935 diff --git a/assets/textures/OcclusionRoughnessMetallic.png b/assets/textures/OcclusionRoughnessMetallic.png deleted file mode 100644 index 9cc2b05..0000000 --- a/assets/textures/OcclusionRoughnessMetallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a2efc715c451e398ac820800040d0edd4a704dda68c0592583c1d839516fe4a5 -size 120 diff --git a/assets/textures/Stone_BaseColor.png b/assets/textures/Stone_BaseColor.png new file mode 100644 index 0000000..5190d8c --- /dev/null +++ b/assets/textures/Stone_BaseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb5b9494439e1f369d005324bd37b6e507ba9fbdc7519cdf5506794aac633716 +size 485 diff --git a/assets/textures/Stone_Normal.png b/assets/textures/Stone_Normal.png new file mode 100644 index 0000000..0432ea3 --- /dev/null +++ b/assets/textures/Stone_Normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b7d2ded276d27f4eba1dc53feac1df1be8860e5a9c766dd0e1a0171f75b380b +size 2431 diff --git a/src/Game.zig b/src/Game.zig new file mode 100644 index 0000000..93bf81b --- /dev/null +++ b/src/Game.zig @@ -0,0 +1,119 @@ +const Game = @This(); +const std = @import("std"); + +const glfw = @import("zglfw"); + +const math = @import("math.zig"); + +const Materials = @import("assets/Materials.zig"); +const Swapchain = @import("engine/Swapchain.zig"); +const Iterator3 = math.Iterator3; +const Matrix4x4 = math.Matrix4x4; +const Quaternion = math.Quaternion; +const Vector2 = math.Vector2; +const Vector3 = math.Vector3; + +allocator: std.mem.Allocator, +swapchain: *Swapchain, + +materials: Materials, + +camera_position: Vector3 = .init(0, 0, 1.62), +camera_pitch: f32 = 0, +camera_yaw: f32 = 0, + +input_forwards: bool = false, +input_backwards: bool = false, +input_left: bool = false, +input_right: bool = false, + +const camera_near_plane = 0.1; +const camera_vertical_fov_deg = 90.0; +const camera_half_vertical_fov_rad = 0.5 * camera_vertical_fov_deg * std.math.rad_per_deg; +const camera_mouse_sensitivity = 0.0015; + +const max_point_lights = 100; +const max_directional_lights = 4; + +const player_speed = 5.0; + +pub fn init(allocator: std.mem.Allocator, swapchain: *Swapchain) !Game { + var materials = try Materials.init(allocator); + errdefer materials.deinit(); + + var it = materials.map.iterator(); + while (it.next()) |entry| { + std.debug.print("Material: {s}\n", .{entry.key_ptr.*}); + std.debug.print("Value: {}\n", .{entry.value_ptr.*}); + } + + return .{ + .allocator = allocator, + .swapchain = swapchain, + .materials = materials, + }; +} + +pub fn deinit(self: *Game) void { + self.materials.deinit(); + self.* = undefined; +} + +pub fn update(self: *Game, dt: f32) void { + var camera_d = Vector2.init( + @as(f32, @floatFromInt(@intFromBool(self.input_right))) - @as(f32, @floatFromInt(@intFromBool(self.input_left))), + @as(f32, @floatFromInt(@intFromBool(self.input_forwards))) - @as(f32, @floatFromInt(@intFromBool(self.input_backwards))), + ).rotate(self.camera_yaw).mulScalar(player_speed * dt); + + self.camera_position = Vector3.add(self.camera_position, camera_d.asVector3(0)); +} + +pub fn onKeyDown(self: *Game, key_code: glfw.Key, mods: glfw.Mods) void { + const no_mods = @as(c_int, @bitCast(mods)) == 0; + + if (key_code == .escape and no_mods) { + self.swapchain.engine.mode.surface.window.setShouldClose(true); + } + + if (key_code == .w) { + self.input_forwards = true; + self.input_backwards = false; + } + if (key_code == .s) { + self.input_backwards = true; + self.input_forwards = false; + } + if (key_code == .a) { + self.input_left = true; + self.input_right = false; + } + if (key_code == .d) { + self.input_right = true; + self.input_left = false; + } +} + +pub fn onKeyUp(self: *Game, key_code: glfw.Key, mods: glfw.Mods) void { + _ = mods; + + if (key_code == .w) { + self.input_forwards = false; + } + if (key_code == .s) { + self.input_backwards = false; + } + if (key_code == .a) { + self.input_left = false; + } + if (key_code == .d) { + self.input_right = false; + } +} + +pub fn onMouseMove(self: *Game, dx: f32, dy: f32) void { + self.camera_pitch -= dy * camera_mouse_sensitivity; + self.camera_yaw -= dx * camera_mouse_sensitivity; + + self.camera_pitch = std.math.clamp(self.camera_pitch, -0.5 * std.math.pi, 0.5 * std.math.pi); + self.camera_yaw = @mod(self.camera_yaw, 2 * std.math.pi); +} diff --git a/src/Log.zig b/src/Log.zig deleted file mode 100644 index 3de5e8b..0000000 --- a/src/Log.zig +++ /dev/null @@ -1,11 +0,0 @@ -const std = @import("std"); - -level: std.log.Level, -message: []const u8, - -pub fn init(level: std.log.Level, message: []const u8) @This() { - return .{ - .level = level, - .message = message, - }; -} diff --git a/src/assets/Atoms.zig b/src/assets/Atoms.zig new file mode 100644 index 0000000..dcf007f --- /dev/null +++ b/src/assets/Atoms.zig @@ -0,0 +1,70 @@ +pub const Atoms = @This(); +const std = @import("std"); + +allocator: std.mem.Allocator, +string_arena_state: std.heap.ArenaAllocator.State, +map: Map, +array: Array, + +pub const Atom = extern struct { + atom: u32, +}; + +pub const Map = std.StringHashMapUnmanaged(Atom); +pub const Array = std.ArrayList([]const u16); + +pub fn init(allocator: std.mem.Allocator) Atoms { + return .{ + .allocator = allocator, + .string_arena_state = .{}, + .map = .empty, + .next_index = 0, + }; +} + +pub fn deinit(self: *Atoms) void { + self.string_arena_state.promote(std.heap.page_allocator).deinit(); + self.map.deinit(self.allocator); + self.array.deinit(self.allocator); + self.* = undefined; +} + +pub fn getString(self: *const Atoms, atom: Atom) ?[]const u8 { + const index: usize = atom.atom; + return if (index < self.array.items.len) self.array.items[index] else null; +} + +pub fn getAtom(self: *const Atoms, string: []const u8) ?Atom { + return self.map.get(string); +} + +pub fn getOrPutAtom(self: *Atoms, string: []const u8) !Atom { + const entry = try self.map.getOrPut(self.allocator, string); + + if (entry.found_existing) { + return entry.value_ptr.*; + } else { + errdefer self.map.remove(string); + try self.array.ensureUnusedCapacity(self.allocator, 1); + const owned_string = try self.toOwnedString(string); + + const index = self.array.items.len; + const atom: Atom = .{ .atom = @intCast(index) }; + + entry.key_ptr.* = owned_string; + entry.value_ptr.* = atom; + + self.array.appendAssumeCapacity(owned_string); + + return atom; + } +} + +fn toOwnedString(self: *const Atoms, string: []const u8) ![]const u8 { + var string_arena = self.string_arena_state.promote(std.heap.page_allocator); + defer self.string_arena_state = string_arena.state; + + const allocator = string_arena.allocator(); + const owned_string = try allocator.dupe(u8, string); + return owned_string; +} diff --git a/src/assets/Materials.zig b/src/assets/Materials.zig new file mode 100644 index 0000000..faf4ad5 --- /dev/null +++ b/src/assets/Materials.zig @@ -0,0 +1,138 @@ +const Materials = @This(); +const std = @import("std"); + +const vk = @import("vulkan"); + +const Atoms = @import("Atoms.zig"); +const Engine = @import("../engine/Engine.zig"); +const Textures = @import("Textures.zig"); + +allocator: std.mem.Allocator, +map: Map, + +pub const Id = extern struct { + id: u32, +}; + +pub const Map = std.AutoHashMapUnmanaged(Atoms.Atom, Id); + +pub const Material = extern struct { + base_color: [3]f32, + base_color_texture: Textures.Id, + emissive: [3]f32, + emissive_texture: Textures.Id, + ior: f32, + metallic: f32, + normal_scale: f32, + normal_texture: Textures.Id, + occlusion_roughness_metallic_texture: Textures.Id, + occlusion_texture_strength: f32, + roughness: f32, +}; + +pub fn init(allocator: std.mem.Allocator) !Materials { + var map: Map = .empty; + errdefer map.deinit(allocator); + + return .{ + .allocator = allocator, + .map = map, + }; +} + +pub fn deinit(self: *Materials) void { + self.arena.deinit(); + self.* = undefined; +} + +fn loadMaterial(self: *Materials, textures: *Textures, engine: *Engine, atoms: *Atoms, key: Atoms.Atom) !Id { + const MaterialJson = struct { + baseColor: [3]f32 = .{ 1, 1, 1 }, + baseColorTexture: ?[]const u8 = null, + emissive: [3]f32 = .{ 0, 0, 0 }, + emissiveTexture: ?[]const u8 = null, + ior: f32 = 1.45, + metallic: f32 = 1, + normalScale: f32 = 1, + normalTexture: ?[]const u8 = null, + occlusionRoughnessMetallicTexture: ?[]const u8 = null, + occlusionTextureStrength: f32 = 1, + roughness: f32 = 1, + }; + + const filename = atoms.getString(key).?; + + const cwd = std.fs.cwd(); + + var dir = try cwd.openDir("assets/materials", .{}); + defer dir.close(); + + // NOTE Buffer size approximated based on expected JSON structure. + var buffer: [512]u8 = undefined; + + const file = try dir.openFile(filename, .{}); + defer file.close(); + + var file_reader = file.reader(&buffer); + var json_reader: std.json.Reader = .init(self.allocator, &file_reader.interface); + defer json_reader.deinit(); + + const parsed: std.json.Parsed(MaterialJson) = try std.json.parseFromTokenSource(MaterialJson, self.allocator, &json_reader, .{ + .duplicate_field_behavior = .@"error", + .ignore_unknown_fields = false, + .allocate = .alloc_if_needed, + }); + defer parsed.deinit(); + + const material_json = parsed.value; + + const base_color_texture = blk: { + if (material_json.baseColorTexture) |name| { + const atom = try atoms.getOrPutAtom(name); + break :blk try textures.getOrLoadId(engine, .{ .atom = atom, .usage = .base_color }); + } else { + break :blk textures.empty_base_color; + } + }; + + const emissive_texture = blk: { + if (material_json.emissiveTexture) |name| { + const atom = try atoms.getOrPutAtom(name); + break :blk try textures.getOrLoadId(engine, .{ .atom = atom, .usage = .emissive }); + } else { + break :blk textures.empty_emissive; + } + }; + + const normal_texture = blk: { + if (material_json.normalTexture) |name| { + const atom = try atoms.getOrPutAtom(name); + break :blk try textures.getOrLoadId(engine, .{ .atom = atom, .usage = .normal }); + } else { + break :blk textures.empty_normal; + } + }; + + const occlusion_roughness_metallic_texture = blk: { + if (material_json.occlusionRoughnessMetallicTexture) |name| { + const atom = try atoms.getOrPutAtom(name); + break :blk try textures.getOrLoadId(engine, .{ .atom = atom, .usage = .occlusion_roughness_metallic }); + } else { + break :blk textures.empty_occlusuion_roughness_metallic; + } + }; + + const material: Material = .{ + .base_color = material_json.baseColor, + .base_color_texture = base_color_texture, + .emissive = material_json.emissive, + .emissive_texture = emissive_texture, + .ior = material_json.ior, + .metallic = material_json.metallic, + .normal_scale = material_json.normalScale, + .normal_texture = normal_texture, + .occlusion_roughness_metallic_texture = occlusion_roughness_metallic_texture, + .occlusion_texture_strength = material_json.occlusionTextureStrength, + .roughness = material_json.roughness, + }; +} diff --git a/src/assets/Textures.zig b/src/assets/Textures.zig new file mode 100644 index 0000000..cf3377f --- /dev/null +++ b/src/assets/Textures.zig @@ -0,0 +1,139 @@ +const Textures = @This(); +const std = @import("std"); + +const stbi = @import("zstbi"); + +const Atoms = @import("Atoms.zig"); +const Engine = @import("../engine/Engine.zig"); +const Texture = @import("../engine/Texture.zig"); + +allocator: std.mem.Allocator, +map: Map, +array: Array, + +empty_base_color: Id, +empty_emissive: Id, +empty_normal: Id, +empty_occlusuion_roughness_metallic: Id, + +pub const Id = extern struct { + id: u32, +}; + +pub const Key = struct { + atom: Atoms.Atom, + usage: Texture.Usage, +}; + +pub const Map = std.AutoHashMapUnmanaged(Key, Id); +pub const Array = std.ArrayList(Texture); + +pub fn init(allocator: std.mem.Allocator, engine: *Engine) !Textures { + var map: Map = .empty; + errdefer map.deinit(allocator); + + var array: Array = try .initCapacity(allocator, 4); + errdefer { + for (array.items) |texture| { + texture.deinit(engine); + } + array.deinit(allocator); + } + + const empty_base_color: Id = .{ .id = @intCast(array.items.len) }; + const empty_base_color_texture: Texture = try .init(engine, 1, 1, .base_color); + array.appendAssumeCapacity(empty_base_color_texture); + + const empty_emissive: Id = .{ .id = @intCast(array.items.len) }; + const empty_emissive_texture: Texture = try .init(engine, 1, 1, .emissive); + array.appendAssumeCapacity(empty_emissive_texture); + + const empty_normal: Id = .{ .id = @intCast(array.items.len) }; + const empty_normal_texture: Texture = try .init(engine, 1, 1, .normal); + array.appendAssumeCapacity(empty_normal_texture); + + const empty_occlusuion_roughness_metallic: Id = .{ .id = @intCast(array.items.len) }; + const empty_occlusuion_roughness_metallic_texture: Texture = try .init(engine, 1, 1, .occlusion_roughness_metallic); + array.appendAssumeCapacity(empty_occlusuion_roughness_metallic_texture); + + empty_base_color_texture.write([3]u8, &.{.{ 255, 255, 255 }}); + empty_emissive_texture.write([3]f32, &.{.{ 1.0, 1.0, 1.0 }}); + empty_normal_texture.write([3]u8, &.{.{ 128, 128, 255 }}); + empty_occlusuion_roughness_metallic_texture.write([3]u8, &.{.{ 255, 255, 255 }}); + + return .{ + .map = map, + .array = array, + + .empty_base_color = empty_base_color, + .empty_emissive = empty_emissive, + .empty_normal = empty_normal, + .empty_occlusuion_roughness_metallic = empty_occlusuion_roughness_metallic, + }; +} + +pub fn deinit(self: *Textures, allocator: std.mem.Allocator, engine: *Engine) void { + for (self.array.items) |texture| { + texture.deinit(engine); + } + self.array.deinit(allocator); + + self.map.deinit(allocator); +} + +pub fn getTexture(self: *const Textures, id: Id) ?*Texture { + const index: usize = id.id; + return if (index < self.array.items.len) &self.array.items[index] else null; +} + +pub fn getId(self: *const Textures, key: Key) ?Id { + return self.map.get(key); +} + +pub fn getOrLoadId(self: *Textures, engine: *Engine, key: Key) !Id { + const entry = try self.map.getOrPut(self.allocator, key); + + if (entry.found_existing) { + return entry.value_ptr.*; + } else { + errdefer self.map.remove(key); + try self.array.ensureUnusedCapacity(self.allocator, 1); + const texture = try self.loadTexture(engine, key); + + const id = self.nextId(); + + entry.value_ptr.* = id; + self.array.appendAssumeCapacity(texture); + + return id; + } +} + +fn loadTexture(self: *Textures, engine: *Engine, key: Key) !Texture { + const cwd = std.fs.cwd(); + + var dir = try cwd.openDir("assets/textures", .{}); + defer dir.close(); + + const file = try dir.openFile(key.name, .{}); + defer file.close(); + + const file_buf = try file.readToEndAlloc(self.allocator, std.math.maxInt(usize)); + defer self.allocator.free(file_buf); + + var img = try stbi.Image.loadFromMemory(file_buf, key.usage.sampleCount()); + defer img.deinit(); + std.debug.assert(img.num_components == key.usage.sampleCount()); + + var texture: Texture = try .init(engine, img.width, img.height, key.usage); + errdefer texture.deinit(engine); + + texture.write(u8, img.data); + + return texture; +} + +fn nextId(self: *const Textures) Id { + const index = self.array.items.len; + return .{ .id = @intCast(index) }; +} diff --git a/src/const.zig b/src/const.zig index 5b96bba..37eadf6 100644 --- a/src/const.zig +++ b/src/const.zig @@ -7,7 +7,7 @@ pub const app_version_string = @import("config").version; pub const app_version = std.SemanticVersion.parse(app_version_string) catch unreachable; pub const min_framerate = 30.0; -pub const min_frametime = 1.0 / min_framerate; +pub const max_frametime = 1.0 / min_framerate; pub const default_window_width = 1280; pub const default_window_height = 720; diff --git a/src/engine/IndexBuffer.zig b/src/engine/IndexBuffer.zig new file mode 100644 index 0000000..c8c2ffb --- /dev/null +++ b/src/engine/IndexBuffer.zig @@ -0,0 +1,51 @@ +const IndexBuffer = @This(); +const std = @import("std"); + +const vk = @import("vulkan"); + +const Engine = @import("Engine.zig"); +const StagingBuffer = @import("StagingBuffer.zig"); + +buffer: vk.Buffer, +memory: vk.DeviceMemory, +index_count: usize, + +pub fn init(engine: *Engine, index_count: usize) !IndexBuffer { + const size = std.math.mul(usize, index_count, @sizeOf(u16)) catch return error.OutOfMemory; + + const buffer = try engine.device.createBuffer(&.{ + .size = size, + .usage = .{ + .transfer_dst_bit = true, + .index_buffer_bit = true, + }, + .sharing_mode = .exclusive, + }, null); + errdefer engine.device.destroyBuffer(buffer); + + const memory_requirements = engine.device.getBufferMemoryRequirements(buffer); + const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true }); + errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface); + + try engine.device.bindBufferMemory(buffer, memory, 0); + + return .{ + .buffer = buffer, + .memory = memory, + .index_count = index_count, + }; +} + +pub fn deinit(self: *IndexBuffer, engine: *Engine) void { + engine.device.freeMemory(self.memory, &engine.vk_allocator.interface); + engine.device.destroyBuffer(self.buffer); + + self.* = undefined; +} + +pub fn write(self: IndexBuffer, engine: *Engine, indices: []const u16) !void { + std.debug.assert(indices.len == self.index_count); + + const staging_buffer: StagingBuffer = .init(engine, std.mem.sliceAsBytes(indices), engine.graphics_queue.allocation.family); + defer staging_buffer.deinit(engine); +} diff --git a/src/engine/StagingBuffer.zig b/src/engine/StagingBuffer.zig new file mode 100644 index 0000000..bdf6067 --- /dev/null +++ b/src/engine/StagingBuffer.zig @@ -0,0 +1,54 @@ +const StagingBuffer = @This(); +const std = @import("std"); + +const vk = @import("vulkan"); + +const Engine = @import("Engine.zig"); + +buffer: vk.Buffer, +memory: vk.DeviceMemory, + +pub fn init(engine: *Engine, data: []const u8, destination_queue_family: u32) !StagingBuffer { + const transfer_queue_family = engine.transfer_queue.allocation.family; + + const single_queue_family = transfer_queue_family == destination_queue_family; + const queue_family_indices: []const u32 = if (single_queue_family) &.{} else &.{ transfer_queue_family, destination_queue_family }; + + const buffer = try engine.device.createBuffer(&.{ + .size = data.len, + .usage = .{ + .transfer_src_bit = true, + }, + .sharing_mode = if (single_queue_family) .exclusive else .concurrent, + .p_queue_family_indices = queue_family_indices.ptr, + }, null); + errdefer engine.device.destroyBuffer(buffer); + + const memory_requirements = engine.device.getBufferMemoryRequirements(buffer); + const memory = try engine.allocate( + memory_requirements, + .{ + .host_visible_bit = true, + .host_coherent_bit = true, + }, + ); + errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface); + + try engine.device.bindBufferMemory(buffer, memory, 0); + + const mapped_memory: [*]u8 = @ptrCast(try engine.device.mapMemory(memory, 0, data.len, .{}) orelse return error.OutOfMemory); + defer engine.device.unmapMemory(memory); + @memcpy(mapped_memory, data); + + return .{ + .buffer = buffer, + .memory = memory, + }; +} + +pub fn deinit(self: *StagingBuffer, engine: *Engine) void { + engine.device.freeMemory(self.memory, &engine.vk_allocator.interface); + engine.device.destroyBuffer(self.buffer); + + self.* = undefined; +} diff --git a/src/engine/StorageBuffer.zig b/src/engine/StorageBuffer.zig new file mode 100644 index 0000000..954db78 --- /dev/null +++ b/src/engine/StorageBuffer.zig @@ -0,0 +1,74 @@ +const StorageBuffer = @This(); +const std = @import("std"); + +const vk = @import("vulkan"); + +const Engine = @import("Engine.zig"); +const StagingBuffer = @import("StagingBuffer.zig"); + +buffer: vk.Buffer, +memory: vk.DeviceMemory, +prefix_size: usize, +element_size: usize, +array_offset: usize, +array_capacity: usize, + +pub fn init(engine: *Engine, comptime PrefixType: type, comptime ElementType: type, array_capacity: usize) !StorageBuffer { + const prefix_size = @sizeOf(PrefixType); + const array_offset = std.mem.alignForward(usize, prefix_size, @alignOf(ElementType)); + const element_size = @sizeOf(ElementType); + + const array_capacity_in_bytes = std.math.mul(usize, array_capacity, element_size) catch return error.OutOfMemory; + const size = std.math.add(array_offset, array_capacity_in_bytes) catch return error.OutOfMemory; + + const buffer = try engine.device.createBuffer(&.{ + .size = size, + .usage = .{ + .transfer_dst_bit = true, + .storage_buffer_bit = true, + }, + .sharing_mode = .exclusive, + }, null); + errdefer engine.device.destroyBuffer(buffer); + + const memory_requirements = engine.device.getBufferMemoryRequirements(buffer); + const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true }); + errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface); + + try engine.device.bindBufferMemory(buffer, memory, 0); + + return .{ + .buffer = buffer, + .memory = memory, + .prefix_size = prefix_size, + .element_size = element_size, + .array_offset = array_offset, + .array_capacity = array_capacity, + }; +} + +pub fn deinit(self: *StorageBuffer, engine: *Engine) void { + engine.device.freeMemory(self.memory, &engine.vk_allocator.interface); + engine.device.destroyBuffer(self.buffer); + + self.* = undefined; +} + +pub fn write(self: StorageBuffer, comptime PrefixType: type, comptime ElementType: type, engine: *Engine, prefix: PrefixType, elements: []const ElementType) !void { + std.debug.assert(elements.len <= self.array_capacity); + std.debug.assert(@sizeOf(PrefixType) == self.prefix_size); + std.debug.assert(@sizeOf(ElementType) == self.element_size); + std.debug.assert(std.mem.isAligned(self.array_offset, @alignOf(ElementType))); + + const array_size_in_bytes = elements.len * elements.len; + const size = self.array_offset + array_size_in_bytes; + + const data = try engine.vk_allocator.allocator.alloc(u8, size); + defer engine.vk_allocator.allocator.free(data); + + @memcpy(data[0..@sizeOf(PrefixType)], std.mem.asBytes(&prefix)); + @memcpy(data[self.array_offset..], std.mem.sliceAsBytes(elements)); + + const staging_buffer: StagingBuffer = .init(engine, data, engine.graphics_queue.allocation.family); + defer staging_buffer.deinit(engine); +} diff --git a/src/engine/Texture.zig b/src/engine/Texture.zig new file mode 100644 index 0000000..265cda8 --- /dev/null +++ b/src/engine/Texture.zig @@ -0,0 +1,139 @@ +const Texture = @This(); +const std = @import("std"); + +const vk = @import("vulkan"); + +const Engine = @import("Engine.zig"); + +pub const Usage = enum { + base_color, + normal, + occlusion_roughness_metallic, + emissive, + + pub fn format(self: Usage) vk.Format { + return switch (self) { + .base_color => .r8g8b8_srgb, + .normal => .r8g8b8_snorm, + .occlusion_roughness_metallic => .r8g8b8_unorm, + .emissive => .r16g16b16_sfloat, + }; + } + + pub fn sampleCount(self: Usage) usize { + return switch (self) { + .base_color => 3, + .normal => 3, + .occlusion_roughness_metallic => 3, + .emissive => 3, + }; + } + + pub fn SampleType(comptime self: Usage) type { + return switch (self) { + .base_color => u8, + .normal => u8, + .occlusion_roughness_metallic => u8, + .emissive => f16, + }; + } + + pub fn sampleSize(self: Usage) usize { + return switch (self) { + inline else => |x| @sizeOf(SampleType(x)), + }; + } + + pub fn TexelType(comptime self: Usage) type { + return [self.sampleCount()]SampleType(self); + } + + pub fn texelSize(self: Usage) usize { + return switch (self) { + inline else => |x| @sizeOf(TexelType(x)), + }; + } +}; + +image: vk.Image, +image_view: vk.ImageView, +memory: vk.DeviceMemory, + +width: u32, +height: u32, +usage: Usage, + +pub fn init(engine: *Engine, width: u32, height: u32, usage: Usage) !Texture { + const format: vk.Format = usage.format(); + + const image = try engine.device.createImage(&.{ + .image_type = .@"2d", + .format = format, + .extent = .{ + .width = width, + .height = height, + .depth = 1, + }, + .mip_levels = 1, + .array_layers = 1, + .samples = .{ .@"1_bit" = true }, + .tiling = .optimal, + .usage = .{ + .transfer_src_bit = true, + .sampled_bit = true, + }, + .sharing_mode = .exclusive, + .initial_layout = .undefined, + }, &engine.vk_allocator.interface); + errdefer engine.device.destroyImage(image, &engine.vk_allocator.interface); + + const memory_requirements = engine.device.getImageMemoryRequirements(image); + const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true }); + errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface); + + try engine.device.bindImageMemory(image, memory, 0); + + const image_view = try engine.device.createImageView(&.{ + .image = image, + .view_type = .@"2d", + .format = format, + .components = .{ + .r = .identity, + .g = .identity, + .b = .identity, + .a = .identity, + }, + .subresource_range = .{ + .aspect_mask = .{ .color_bit = true }, + .base_mip_level = 0, + .level_count = 1, + .base_array_level = 0, + .layer_count = 1, + }, + }, &engine.vk_allocator.interface); + errdefer engine.device.destroyImageView(image_view, &engine.vk_allocator.interface); + + return .{ + .image = image, + .image_view = image_view, + .memory = memory, + .width = width, + .height = height, + }; +} + +pub fn deinit(self: *Texture, engine: *Engine) void { + engine.device.destroyImageView(self.image_view, &engine.vk_allocator.interface); + engine.device.freeMemory(self.memory, &engine.vk_allocator.interface); + engine.device.destroyImage(self.image, &engine.vk_allocator.interface); + + self.* = undefined; +} + +pub fn write(self: Texture, comptime T: type, data: []const T) void { + const bytes_per_texel = self.format.texelSize(); + const bytes_per_row = self.width * bytes_per_texel; + const byte_length = self.height * bytes_per_row; + + std.debug.assert(data.len * @sizeOf(T) == byte_length); +} diff --git a/src/engine/VertexBuffer.zig b/src/engine/VertexBuffer.zig new file mode 100644 index 0000000..8a83ff3 --- /dev/null +++ b/src/engine/VertexBuffer.zig @@ -0,0 +1,55 @@ +const VertexBuffer = @This(); +const std = @import("std"); + +const vk = @import("vulkan"); + +const Engine = @import("Engine.zig"); +const StagingBuffer = @import("StagingBuffer.zig"); + +buffer: vk.Buffer, +memory: vk.DeviceMemory, +vertex_size: usize, +vertex_count: usize, + +pub fn init(engine: *Engine, comptime VertexType: type, vertex_count: usize) !VertexBuffer { + const vertex_size = @sizeOf(VertexType); + const size = std.math.mul(usize, vertex_count, vertex_size) catch return error.OutOfMemory; + + const buffer = try engine.device.createBuffer(&.{ + .size = size, + .usage = .{ + .transfer_dst_bit = true, + .vertex_buffer_bit = true, + }, + .sharing_mode = .exclusive, + }, null); + errdefer engine.device.destroyBuffer(buffer); + + const memory_requirements = engine.device.getBufferMemoryRequirements(buffer); + const memory = try engine.allocate(memory_requirements, .{ .device_local_bit = true }); + errdefer engine.device.freeMemory(memory, &engine.vk_allocator.interface); + + try engine.device.bindBufferMemory(buffer, memory, 0); + + return .{ + .buffer = buffer, + .memory = memory, + .vertex_size = vertex_size, + .vertex_count = vertex_count, + }; +} + +pub fn deinit(self: *VertexBuffer, engine: *Engine) void { + engine.device.freeMemory(self.memory, &engine.vk_allocator.interface); + engine.device.destroyBuffer(self.buffer); + + self.* = undefined; +} + +pub fn write(self: VertexBuffer, comptime VertexType: type, engine: *Engine, vertices: []const VertexType) !void { + std.debug.assert(vertices.len == self.vertex_count); + std.debug.assert(@sizeOf(VertexType) == self.vertex_size); + + const staging_buffer: StagingBuffer = .init(engine, std.mem.sliceAsBytes(vertices), engine.graphics_queue.allocation.family); + defer staging_buffer.deinit(engine); +} diff --git a/src/game.zig b/src/game.zig deleted file mode 100644 index 1854a1d..0000000 --- a/src/game.zig +++ /dev/null @@ -1,285 +0,0 @@ -const std = @import("std"); - -const ig = @import("cimgui"); -const shaders = @import("shaders"); -const sokol = @import("sokol"); - -const sapp = sokol.app; -const sg = sokol.gfx; -const sglue = sokol.glue; - -const main = @import("main.zig"); -const math = @import("math.zig"); -const tiles = @import("tiles.zig"); -const samplers = @import("samplers.zig"); -const skybox = @import("skybox.zig"); - -const Iterator3 = math.Iterator3; -const Matrix4x4 = math.Matrix4x4; -const Quaternion = math.Quaternion; -const Vector2 = math.Vector2; -const Vector3 = math.Vector3; - -var show_console: bool = false; -var show_demo_window: bool = false; - -var point_light_buffer: sg.Buffer = undefined; -var directional_light_buffer: sg.Buffer = undefined; - -pub var point_light_buffer_view: sg.View = undefined; -pub var directional_light_buffer_view: sg.View = undefined; - -var camera_position: Vector3 = .init(0, 0, 1.62); -var camera_pitch: f32 = 0; -var camera_yaw: f32 = 0; - -const camera_near_plane = 0.1; -const camera_vertical_fov_deg = 90.0; -const camera_half_vertical_fov_rad = 0.5 * camera_vertical_fov_deg * std.math.rad_per_deg; -const camera_mouse_sensitivity = 0.0015; - -const max_point_lights = 100; -const max_directional_lights = 4; - -pub fn init() void { - point_light_buffer = sg.makeBuffer(.{ - .size = @sizeOf([max_point_lights]shaders.PointLight), - .usage = .{ - .stream_update = true, - .storage_buffer = true, - }, - .label = "PointLight Buffer", - }); - point_light_buffer_view = sg.makeView(.{ - .storage_buffer = .{ .buffer = point_light_buffer }, - .label = "PointLight BV", - }); - - directional_light_buffer = sg.makeBuffer(.{ - .size = @sizeOf([max_directional_lights]shaders.DirectionalLight), - .usage = .{ - .stream_update = true, - .storage_buffer = true, - }, - .label = "DirectionalLight Buffer", - }); - directional_light_buffer_view = sg.makeView(.{ - .storage_buffer = .{ .buffer = directional_light_buffer }, - .label = "DirectionalLight BV", - }); - - tiles.init(); - skybox.init(); -} - -pub fn deinit() void { - skybox.deinit(); - tiles.deinit(); - - sg.destroyView(directional_light_buffer_view); - sg.destroyBuffer(directional_light_buffer); - sg.destroyView(point_light_buffer_view); - sg.destroyBuffer(point_light_buffer); - - samplers.deinit(); -} - -pub fn update(dt: f32) void { - var camera_d = Vector2.init( - @as(f32, @floatFromInt(@intFromBool(input_right))) - @as(f32, @floatFromInt(@intFromBool(input_left))), - @as(f32, @floatFromInt(@intFromBool(input_forwards))) - @as(f32, @floatFromInt(@intFromBool(input_backwards))), - ).rotate(camera_yaw).mulScalar(player_speed * dt); - - camera_position = Vector3.add(camera_position, camera_d.asVector3(0)); - - const framebuffer_size = Vector2.init(sapp.widthf(), sapp.heightf()); - - if (show_console) { - showConsole(); - } - - if (show_demo_window) { - ig.igShowDemoWindow(&show_demo_window); - } - - sg.beginPass(.{ - .action = blk: { - var ret: sg.PassAction = .{}; - - ret.colors[0] = .{ - .load_action = .CLEAR, - .store_action = .STORE, - .clear_value = .{ .r = 0, .g = 0, .b = 0, .a = 1 }, - }; - - ret.depth = .{ - .load_action = .CLEAR, - .store_action = .STORE, - .clear_value = 0, - }; - - break :blk ret; - }, - .swapchain = sglue.swapchain(), - }); - - tiles.bind(); - - const camera_rotation = Quaternion.mulQuaternion( - .initRotationXY(camera_yaw), - .initRotationYZ(camera_pitch), - ); - const camera_aspect_ratio = framebuffer_size.getX() / framebuffer_size.getY(); - const camera_yscale = 1.0 / @tan(camera_half_vertical_fov_rad); - const camera_xscale = camera_yscale / camera_aspect_ratio; - - const matrix_ws_to_vs = Matrix4x4.mulMatrix( - Matrix4x4.initRotation(camera_rotation.conjugate()), - Matrix4x4.initTranslation(camera_position.negate()), - ); - // zig fmt: off - const matrix_vs_to_cs = Matrix4x4.init( - camera_xscale, 0, 0, 0, - 0, 0, camera_near_plane, 1, - 0, camera_yscale, 0, 0, - 0, 0, 0, 0, - ); - // zig fmt: on - const ambient_light = Vector3.init(0.3, 0.3, 0.3); - - sg.applyUniforms(shaders.UB_Global_Uniforms_Vertex, sg.asRange(&shaders.GlobalUniformsVertex{ - ._MatrixWStoVS = matrix_ws_to_vs.asArray(), - ._MatrixVStoCS = matrix_vs_to_cs.asArray(), - })); - - const point_lights: []const shaders.PointLight = &.{ - .{ - .positionWS = .{ 0, 0, 1 }, - .color = .{ 10, 10, 10 }, - }, - .{ - .positionWS = .{ -10, 10, 1 }, - .color = .{ 5, 0, 0 }, - }, - .{ - .positionWS = .{ 10, 10, 1 }, - .color = .{ 0, 0, 5 }, - }, - .{ - .positionWS = .{ -10, -10, 1 }, - .color = .{ 0, 5, 0 }, - }, - .{ - .positionWS = .{ 10, -10, 1 }, - .color = .{ 5, 5, 0 }, - }, - }; - - const directional_lights: []const shaders.DirectionalLight = &.{ - .{ - .directionWS = .{ 0, 0, -1 }, - .color = .{ 0.2, 0.2, 0.2 }, - }, - }; - - sg.updateBuffer(point_light_buffer, sg.asRange(point_lights)); - sg.updateBuffer(directional_light_buffer, sg.asRange(directional_lights)); - - sg.applyUniforms(shaders.UB_Global_Uniforms_Fragment, sg.asRange(&shaders.GlobalUniformsFragment{ - ._MatrixWStoVS = matrix_ws_to_vs.asArray(), - ._AmbientLight = ambient_light.asArray(), - ._PointLightCount = @intCast(point_lights.len), - ._DirectionalLightCount = @intCast(directional_lights.len), - })); - - var it = Iterator3.init(.init(-8, -8, 0), .init(8, 8, 0), .one); - var tile_index: u32 = 0; - while (it.next()) |pos| : (tile_index = (tile_index + 1) % 16) { - tiles.draw(pos, .identity, tile_index); - } - - sg.endPass(); -} - -var input_forwards: bool = false; -var input_backwards: bool = false; -var input_left: bool = false; -var input_right: bool = false; -const player_speed = 5.0; - -pub fn onKeyDown(key_code: sapp.Keycode, mods: u32) void { - const no_mods = mods == 0; - - if (key_code == .ESCAPE and no_mods) { - sapp.requestQuit(); - } - - if (key_code == .GRAVE_ACCENT and no_mods) { - show_console = !show_console; - } - - if (key_code == .F1 and no_mods) { - show_demo_window = true; - } - - if (key_code == .W) { - input_forwards = true; - input_backwards = false; - } - if (key_code == .S) { - input_backwards = true; - input_forwards = false; - } - if (key_code == .A) { - input_left = true; - input_right = false; - } - if (key_code == .D) { - input_right = true; - input_left = false; - } -} - -pub fn onKeyUp(key_code: sapp.Keycode, mods: u32) void { - _ = mods; - - if (key_code == .W) { - input_forwards = false; - } - if (key_code == .S) { - input_backwards = false; - } - if (key_code == .A) { - input_left = false; - } - if (key_code == .D) { - input_right = false; - } -} - -pub fn onMouseMove(dx: f32, dy: f32) void { - camera_pitch -= dy * camera_mouse_sensitivity; - camera_yaw -= dx * camera_mouse_sensitivity; - - camera_pitch = std.math.clamp(camera_pitch, -0.5 * std.math.pi, 0.5 * std.math.pi); - camera_yaw = @mod(camera_yaw, 2 * std.math.pi); -} - -fn showConsole() void { - const display_size = ig.igGetIO().*.DisplaySize; - ig.igSetNextWindowPos(.{ .x = 0, .y = 0 }, 0); - ig.igSetNextWindowSize(.{ .x = display_size.x, .y = main.min_window_height }, ig.ImGuiCond_Once); - if (ig.igBegin("Console", null, ig.ImGuiWindowFlags_NoTitleBar | ig.ImGuiWindowFlags_NoResize | ig.ImGuiWindowFlags_NoMove | ig.ImGuiWindowFlags_NoCollapse)) { - for (main.logs.items) |log| { - ig.igPushStyleColorImVec4(ig.ImGuiCol_Text, switch (log.level) { - .err => .{ .x = 1, .y = 0, .z = 0, .w = 1 }, - .warn => .{ .x = 1, .y = 1, .z = 0, .w = 1 }, - .info => .{ .x = 1, .y = 1, .z = 1, .w = 1 }, - .debug => .{ .x = 0.5, .y = 0.5, .z = 0.5, .w = 1 }, - }); - ig.igTextUnformattedEx(log.message.ptr, log.message.ptr + log.message.len); - ig.igPopStyleColor(); - } - ig.igEnd(); - } -} diff --git a/src/global.zig b/src/global.zig deleted file mode 100644 index e69de29..0000000 diff --git a/src/main.zig b/src/main.zig index 0524810..d337ead 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5,10 +5,10 @@ const stbi = @import("zstbi"); const vk = @import("vulkan"); const c = @import("const.zig"); -const game = @import("game.zig"); const Engine = @import("engine/Engine.zig"); const Swapchain = @import("engine/Swapchain.zig"); +const Game = @import("Game.zig"); pub var allocator: std.mem.Allocator = undefined; pub var temp_allocator: std.mem.Allocator = undefined; @@ -54,12 +54,18 @@ pub fn main() !void { var swapchain = try Swapchain.init(&engine); defer swapchain.deinit(); - //game.init(); - //defer game.deinit(); + var game = try Game.init(allocator, &swapchain); + defer game.deinit(); + var t1 = glfw.getTime(); while (!window.shouldClose()) { glfw.pollEvents(); - //game.update(dt); + + const t2 = glfw.getTime(); + const dt: f32 = @floatCast(t2 - t1); + t1 = t2; + + game.update(dt); std.Thread.sleep(1 * std.time.ns_per_ms); } }