diff --git a/res/shaders/BasePass.slang b/res/shaders/BasePass.slang new file mode 100644 index 0000000..93f98d7 --- /dev/null +++ b/res/shaders/BasePass.slang @@ -0,0 +1,7 @@ +import Common; + +[shader("pixel")] +void pixelMain(in VertexAttributes attribs, out float4 baseColor) +{ + BRDF brdf = gMaterial.prepare(); +} diff --git a/res/shaders/StaticMeshBasePass.slang b/res/shaders/StaticMeshBasePass.slang index 2b29501..98713b8 100644 --- a/res/shaders/StaticMeshBasePass.slang +++ b/res/shaders/StaticMeshBasePass.slang @@ -21,8 +21,8 @@ struct PrimitiveAttributes groupshared StaticMeshVertexAttributes gs_vertices[MAX_VERTICES]; groupshared uint3 gs_indices[MAX_PRIMITIVES]; -groupshared uint gs_numVertices = 0; -groupshared uint gs_numPrimitives = 0; +groupshared uint gs_numVertices; +groupshared uint gs_numPrimitives; [numthreads(GROUP_SIZE, 1, 1)] [shader("amplification")] @@ -39,8 +39,8 @@ void taskMain( void meshMain( uint3 threadID: SV_GroupIndex, uint3 groupID: SV_GroupID, - out vertices StaticMeshVertexAttributes vertices[MAX_VERTICES], - out indices uint3 indices[MAX_PRIMITIVES] + out Vertices vertices, + out Indices indices ){ MeshShaderPayload p; InstanceData inst = scene.instances[p.instanceId]; diff --git a/res/shaders/StaticMeshBasePass.spirv b/res/shaders/StaticMeshBasePass.spirv deleted file mode 100644 index db4c9b0..0000000 --- a/res/shaders/StaticMeshBasePass.spirv +++ /dev/null @@ -1,249 +0,0 @@ -#version 450 -#extension GL_EXT_mesh_shader : require -#extension GL_EXT_shader_8bit_storage : require -#extension GL_EXT_shader_explicit_arithmetic_types : require -layout(row_major) uniform; -layout(row_major) buffer; - -#line 1 0 -struct InstanceData_0 -{ - mat4x4 transformMatrix_0; -}; - - -#line 46 1 -layout(std430, binding = 0, set = 2) readonly buffer StructuredBuffer_InstanceData_t_0 { - InstanceData_0 _data[]; -} scene_instances_0; - -#line 1 2 -struct MeshletDescription_0 -{ - uint vertexCount_0; - uint primitiveCount_0; - uint vertexOffset_0; - uint primitiveOffset_0; -}; - - -#line 47 1 -layout(std430, binding = 0, set = 1) readonly buffer StructuredBuffer_MeshletDescription_t_0 { - MeshletDescription_0 _data[]; -} meshlets_meshletInfos_0; - -#line 9 2 -layout(std430, binding = 1, set = 1) readonly buffer StructuredBuffer_uint8_t_0 { - uint8_t _data[]; -} meshlets_primitiveIndices_0; - -#line 9 -layout(std430, binding = 2, set = 1) readonly buffer StructuredBuffer_uint_t_0 { - uint _data[]; -} meshlets_vertexIndices_0; - -#line 24 3 -layout(std430, binding = 0, set = 3) readonly buffer StructuredBuffer_float4_t_0 { - vec4 _data[]; -} vertexData_positions_0; - -#line 24 -layout(std430, binding = 1, set = 3) readonly buffer StructuredBuffer_float2_t_0 { - vec2 _data[]; -} vertexData_texCoords_0; - -#line 24 -layout(std430, binding = 2, set = 3) readonly buffer StructuredBuffer_float3_t_0 { - vec3 _data[]; -} vertexData_normals_0; - -#line 5 4 -struct ViewParameter_0 -{ - mat4x4 viewMatrix_0; - mat4x4 projectionMatrix_0; - vec4 cameraPos_WS_0; - vec2 screenDimensions_0; -}; - -layout(binding = 0) -layout(std140) uniform _S1 -{ - mat4x4 viewMatrix_0; - mat4x4 projectionMatrix_0; - vec4 cameraPos_WS_0; - vec2 screenDimensions_0; -}viewParams_0; - -#line 24 1 - -uint gs_numVertices_0_init() -{ - -#line 24 - return 0U; -} -shared uint gs_numVertices_0 = gs_numVertices_0_init(); - - -#line 5 3 -struct StaticMeshVertexAttributes_0 -{ - vec4 position_0; - vec2 texCoords_0; - vec3 normal_0; -}; - - -#line 22 1 -shared StaticMeshVertexAttributes_0 gs_vertices_0[64]; - - - -uint gs_numPrimitives_0_init() -{ - -#line 25 - return 0U; -} -shared uint gs_numPrimitives_0 = gs_numPrimitives_0_init(); - - -#line 23 -shared uvec3 gs_indices_0[126]; - - -#line 11 -struct MeshShaderPayload_0 -{ - uint instanceId_0; - uint meshletId_0; -}; - - -#line 20 3 -StaticMeshVertexAttributes_0 StaticMeshVertexData_getAttributes_0(uint _S2, InstanceData_0 _S3) -{ - -#line 28 - StaticMeshVertexAttributes_0 attr_0; - - - - attr_0.position_0 = (((((((((vertexData_positions_0._data[_S2]) * (_S3.transformMatrix_0)))) * (viewParams_0.viewMatrix_0)))) * (viewParams_0.projectionMatrix_0))); - attr_0.texCoords_0 = vertexData_texCoords_0._data[_S2]; - attr_0.normal_0 = vertexData_normals_0._data[_S2]; - return attr_0; -} - - -#line 39 1 -layout(local_size_x = 32, local_size_y = 1, local_size_z = 1) in; -layout(max_vertices = 64) out; -layout(max_primitives = 126) out; -layout(triangles) out; -void main() -{ - -#line 39 - MeshShaderPayload_0 p_0; - -#line 46 - InstanceData_0 _S4 = scene_instances_0._data[p_0.instanceId_0]; - MeshletDescription_0 _S5 = meshlets_meshletInfos_0._data[p_0.meshletId_0]; - - - - uint _S6 = uvec3(gl_LocalInvocationIndex).x; - uint _S7 = _S5.vertexCount_0 - 1U; - -#line 64 - uint _S8 = _S5.primitiveCount_0 - 1U; - -#line 64 - uint loop_0 = 0U; - -#line 64 - for(;;) - { - -#line 52 - uint v_0 = min(_S6 + loop_0 * 32U, _S7); - atomicMax((gs_numVertices_0), (v_0 + 1U)); - - - gs_vertices_0[v_0] = StaticMeshVertexData_getAttributes_0(uint(int(meshlets_vertexIndices_0._data[_S5.vertexOffset_0 + v_0])), _S4); - -#line 49 - uint loop_1 = loop_0 + 1U; - -#line 49 - if(loop_1 < 2U) - { - } - else - { - -#line 49 - break; - } - -#line 49 - loop_0 = loop_1; - -#line 49 - } - -#line 49 - loop_0 = 0U; - -#line 49 - for(;;) - { - -#line 64 - uint p_1 = min(_S6 + loop_0 * 32U, _S8); - atomicMax((gs_numPrimitives_0), (p_1 + 1U)); - - uint _S9 = p_1 * 3U; - -#line 67 - uint _S10 = _S5.primitiveOffset_0 + _S9; - - - - uint idx1_0 = meshlets_vertexIndices_0._data[_S5.vertexOffset_0 + uint(meshlets_primitiveIndices_0._data[_S10 + 1U])]; - uint idx2_0 = meshlets_vertexIndices_0._data[_S5.vertexOffset_0 + uint(meshlets_primitiveIndices_0._data[_S10 + 2U])]; - gs_indices_0[_S9] = uvec3(meshlets_vertexIndices_0._data[_S5.vertexOffset_0 + uint(meshlets_primitiveIndices_0._data[_S10])]); - gs_indices_0[_S9 + 1U] = uvec3(idx1_0); - gs_indices_0[_S9 + 2U] = uvec3(idx2_0); - -#line 61 - uint loop_2 = loop_0 + 1U; - -#line 61 - if(loop_2 < 4U) - { - } - else - { - -#line 61 - break; - } - -#line 61 - loop_0 = loop_2; - -#line 61 - } - -#line 78 - barrier(); - SetMeshOutputsEXT(gs_numVertices_0, gs_numPrimitives_0); - barrier(); - -#line 93 - return; -} - diff --git a/res/shaders/lib/Material.slang b/res/shaders/lib/Material.slang index 4c7b878..77c2c18 100644 --- a/res/shaders/lib/Material.slang +++ b/res/shaders/lib/Material.slang @@ -1,11 +1,11 @@ import Common; import BRDF; -import MaterialParameter; interface IMaterial { associatedtype BRDF : IBRDF; BRDF prepare(MaterialFragmentParameter input); + }; ParameterBlock gMaterial; diff --git a/res/shaders/lib/MaterialParameter.slang b/res/shaders/lib/MaterialParameter.slang new file mode 100644 index 0000000..51ae33a --- /dev/null +++ b/res/shaders/lib/MaterialParameter.slang @@ -0,0 +1,22 @@ +import VertexData; + +struct MaterialParameter +{ + float3 worldPosition; + float2 texCoords; + float3 normal; + float3 tangent; + float3 biTangent; + + static MaterialParameter create(VertexAttributes attrib) + { + MaterialParameter result; + result.worldPosition = attrib.getWorldPosition().xyz; + result.texCoords = attrib.getTexCoords(); + result.normal = attrib.getNormal(); + result.tangent = attrib.getTangent(); + result.biTangent = attrib.getBiTangent(); + return result; + } +} + diff --git a/res/shaders/lib/StaticMeshVertexData.slang b/res/shaders/lib/StaticMeshVertexData.slang index 28afd2a..627ca7e 100644 --- a/res/shaders/lib/StaticMeshVertexData.slang +++ b/res/shaders/lib/StaticMeshVertexData.slang @@ -4,14 +4,17 @@ import Scene; struct StaticMeshVertexAttributes : VertexAttributes { - float4 position: SV_Position; + float4 clipPosition: SV_Position; + float3 worldPosition: POSITION0; float2 texCoords: TEXCOORD0; float3 normal: NORMAL0; - float4 getPosition() + float3 tangent: TANGENT0; + float3 biTangent: BITANGENT0; + float3 getWorldPosition() { - return position; + return worldPosition; } - float2 getTexCoord() + float2 getTexCoords() { return texCoords; } @@ -19,6 +22,14 @@ struct StaticMeshVertexAttributes : VertexAttributes { return normal; } + float3 getTangent() + { + return tangent; + } + float3 getBiTangent() + { + return biTangent; + } } struct StaticMeshVertexData : VertexData @@ -26,16 +37,22 @@ struct StaticMeshVertexData : VertexData StaticMeshVertexAttributes getAttributes(uint index, InstanceData inst) { StaticMeshVertexAttributes attr; - float4 worldPos = mul(inst.transformMatrix, positions[index]); + float4 localPos = float4(positions[index], 1); + float4 worldPos = mul(inst.transformMatrix, localPos); float4 viewPos = mul(viewParams.viewMatrix, worldPos); float4 clipPos = mul(viewParams.projectionMatrix, viewPos); - attr.position = clipPos; + attr.clipPosition = clipPos; + attr.worldPosition = worldPos.xyz; attr.texCoords = texCoords[index]; attr.normal = normals[index]; + attr.tangent = tangents[index]; + attr.biTangent = biTangents[index]; return attr; } - StructuredBuffer positions; + StructuredBuffer positions; StructuredBuffer texCoords; StructuredBuffer normals; + StructuredBuffer tangents; + StructuredBuffer biTangents; } ParameterBlock vertexData; \ No newline at end of file diff --git a/res/shaders/lib/VertexData.slang b/res/shaders/lib/VertexData.slang index 9fcd04f..12c7978 100644 --- a/res/shaders/lib/VertexData.slang +++ b/res/shaders/lib/VertexData.slang @@ -2,9 +2,11 @@ import Scene; interface VertexAttributes { - float4 getPosition(); - float2 getTexCoord(); + float3 getWorldPosition(); + float2 getTexCoords(); float3 getNormal(); + float3 getTangent(); + float3 getBiTangent(); } interface VertexData diff --git a/res/shaders/slang-glslang.dll b/res/shaders/slang-glslang.dll deleted file mode 100644 index 54cd2da..0000000 Binary files a/res/shaders/slang-glslang.dll and /dev/null differ diff --git a/res/shaders/slang-llvm.dll b/res/shaders/slang-llvm.dll deleted file mode 100644 index ed17c5e..0000000 Binary files a/res/shaders/slang-llvm.dll and /dev/null differ diff --git a/res/shaders/slang.dll b/res/shaders/slang.dll deleted file mode 100644 index 8a5cc7a..0000000 Binary files a/res/shaders/slang.dll and /dev/null differ diff --git a/res/shaders/slangc.exe b/res/shaders/slangc.exe deleted file mode 100755 index 025824b..0000000 Binary files a/res/shaders/slangc.exe and /dev/null differ diff --git a/res/shaders/test.glsl b/res/shaders/test.glsl new file mode 100755 index 0000000..c851c7e --- /dev/null +++ b/res/shaders/test.glsl @@ -0,0 +1,226 @@ +#pragma pack_matrix(column_major) +#ifdef SLANG_HLSL_ENABLE_NVAPI +#include "nvHLSLExtns.h" +#endif +#pragma warning(disable: 3557) + + +#line 1 "lib/Scene.slang" +struct InstanceData_0 +{ + matrix transformMatrix_0; +}; + + +#line 45 "test.slang" +StructuredBuffer scene_instances_0 : register(t0, space3); + + +#line 1 "lib/Meshlet.slang" +struct MeshletDescription_0 +{ + uint vertexCount_0; + uint primitiveCount_0; + uint vertexOffset_0; + uint primitiveOffset_0; +}; + + +#line 46 "test.slang" +StructuredBuffer meshlets_meshletInfos_0 : register(t0, space2); + + +#line 9 "lib/Meshlet.slang" +StructuredBuffer meshlets_primitiveIndices_0 : register(t1, space2); + + +#line 9 +StructuredBuffer meshlets_vertexIndices_0 : register(t2, space2); + + +#line 35 "lib/StaticMeshVertexData.slang" +StructuredBuffer vertexData_positions_0 : register(t0, space4); + + +#line 35 +StructuredBuffer vertexData_texCoords_0 : register(t1, space4); + + +#line 35 +StructuredBuffer vertexData_normals_0 : register(t2, space4); + + +#line 35 +StructuredBuffer vertexData_tangents_0 : register(t3, space4); + + +#line 35 +StructuredBuffer vertexData_biTangents_0 : register(t4, space4); + + +#line 5 "lib/Common.slang" +struct ViewParameter_0 +{ + matrix viewMatrix_0; + matrix projectionMatrix_0; + float4 cameraPos_WS_0; + float2 screenDimensions_0; +}; + +cbuffer viewParams_0 : register(b0, space1) +{ + ViewParameter_0 viewParams_0; +} + +#line 24 "test.slang" +static groupshared uint gs_numVertices_0; + + +#line 5 "lib/StaticMeshVertexData.slang" +struct StaticMeshVertexAttributes_0 +{ + float4 clipPosition_0 : SV_Position; + float3 worldPosition_0 : POSITION0; + float2 texCoords_0 : TEXCOORD0; + float3 normal_0 : NORMAL0; + float3 tangent_0 : TANGENT0; + float3 biTangent_0 : BITANGENT0; +}; + + +#line 22 "test.slang" +static groupshared StaticMeshVertexAttributes_0 gs_vertices_0[int(64)]; + + +#line 37 "lib/StaticMeshVertexData.slang" +StaticMeshVertexAttributes_0 StaticMeshVertexData_getAttributes_0(StructuredBuffer this_positions_0, StructuredBuffer this_texCoords_0, StructuredBuffer this_normals_0, StructuredBuffer this_tangents_0, StructuredBuffer this_biTangents_0, uint index_0, InstanceData_0 inst_0) +{ + + + float4 worldPos_0 = mul(inst_0.transformMatrix_0, float4(this_positions_0.Load(index_0), 1.0)); + +#line 39 + StaticMeshVertexAttributes_0 attr_0; + +#line 44 + attr_0.clipPosition_0 = mul(viewParams_0.projectionMatrix_0, mul(viewParams_0.viewMatrix_0, worldPos_0)); + attr_0.worldPosition_0 = worldPos_0.xyz; + attr_0.texCoords_0 = this_texCoords_0.Load(index_0); + attr_0.normal_0 = this_normals_0.Load(index_0); + attr_0.tangent_0 = this_tangents_0.Load(index_0); + attr_0.biTangent_0 = this_biTangents_0.Load(index_0); + return attr_0; +} + + +#line 25 "test.slang" +static groupshared uint gs_numPrimitives_0; + + +#line 23 +static groupshared uint3 gs_indices_0[int(126)]; + + +#line 39 +[shader("mesh")][numthreads(32, 1, 1)] +[outputtopology("triangle")] +void meshMain(uint threadID_0 : SV_GROUPINDEX, uint3 groupID_0 : SV_GROUPID, vertices out StaticMeshVertexAttributes_0 vertices_0[int(64)], indices out uint3 indices_0[int(126)]) +{ + + + InstanceData_0 _S1 = scene_instances_0.Load(threadID_0); + MeshletDescription_0 _S2 = meshlets_meshletInfos_0.Load(groupID_0.x); + +#line 51 + uint _S3 = _S2.vertexCount_0 - 1U; + +#line 63 + uint _S4 = _S2.primitiveCount_0 - 1U; + +#line 63 + uint loop_0 = 0U; + +#line 63 + for(;;) + { + +#line 51 + uint v_0 = min(threadID_0 + loop_0 * 32U, _S3); + InterlockedMax(gs_numVertices_0, v_0 + 1U); + + + gs_vertices_0[v_0] = StaticMeshVertexData_getAttributes_0(vertexData_positions_0, vertexData_texCoords_0, vertexData_normals_0, vertexData_tangents_0, vertexData_biTangents_0, uint(int(meshlets_vertexIndices_0.Load(_S2.vertexOffset_0 + v_0))), _S1); + +#line 48 + uint loop_1 = loop_0 + 1U; + +#line 48 + if(loop_1 < 2U) + { + } + else + { + +#line 48 + break; + } + +#line 48 + loop_0 = loop_1; + +#line 48 + } + +#line 48 + loop_0 = 0U; + +#line 48 + for(;;) + { + +#line 63 + uint p_0 = min(threadID_0 + loop_0 * 32U, _S4); + InterlockedMax(gs_numPrimitives_0, p_0 + 1U); + + uint _S5 = p_0 * 3U; + +#line 66 + uint _S6 = _S2.primitiveOffset_0 + _S5; + + + + uint idx1_0 = meshlets_vertexIndices_0.Load(_S2.vertexOffset_0 + uint(meshlets_primitiveIndices_0.Load(_S6 + 1U))); + uint idx2_0 = meshlets_vertexIndices_0.Load(_S2.vertexOffset_0 + uint(meshlets_primitiveIndices_0.Load(_S6 + 2U))); + gs_indices_0[_S5] = (uint3)meshlets_vertexIndices_0.Load(_S2.vertexOffset_0 + uint(meshlets_primitiveIndices_0.Load(_S6))); + gs_indices_0[_S5 + 1U] = (uint3)idx1_0; + gs_indices_0[_S5 + 2U] = (uint3)idx2_0; + +#line 60 + uint loop_2 = loop_0 + 1U; + +#line 60 + if(loop_2 < 4U) + { + } + else + { + +#line 60 + break; + } + +#line 60 + loop_0 = loop_2; + +#line 60 + } + +#line 77 + GroupMemoryBarrierWithGroupSync(); + SetMeshOutputCounts(gs_numVertices_0, gs_numPrimitives_0); + GroupMemoryBarrierWithGroupSync(); + vertices_0[threadID_0] = gs_vertices_0[threadID_0]; + indices_0[threadID_0] = gs_indices_0[threadID_0]; + return; +} + diff --git a/res/shaders/test.slang b/res/shaders/test.slang index 9c233b1..776abaf 100644 --- a/res/shaders/test.slang +++ b/res/shaders/test.slang @@ -1,12 +1,90 @@ -struct Payload +import Common; +import BRDF; +import Meshlet; +import Scene; +import VertexData; +import StaticMeshVertexData; + +layout(push_constant) +ConstantBuffer meshId; + +struct MeshShaderPayload { - uint num; + uint instanceId; + uint meshletId; }; -[shader("mesh")] -[numthreads(32, 1, 1)] -[outputtopology("triangle")] -void taskMain(uint threadID: SV_DispatchThreadID) +struct PrimitiveAttributes { - SetOutputMeshCounts(64, 126); + uint cull: SV_CullPrimitive; +}; + +groupshared StaticMeshVertexAttributes gs_vertices[MAX_VERTICES]; +groupshared uint3 gs_indices[MAX_PRIMITIVES]; +groupshared uint gs_numVertices; +groupshared uint gs_numPrimitives; + +[numthreads(GROUP_SIZE, 1, 1)] +[shader("amplification")] +void taskMain( + uint3 threadID: SV_GroupIndex, + uint3 groupID: SV_GroupID +){ + +} + +[numthreads(GROUP_SIZE, 1, 1)] +[outputtopology("triangle")] +[shader("mesh")] +void meshMain( + uint threadID: SV_GroupIndex, + uint3 groupID: SV_GroupID, + out Vertices vertices, + out Indices indices +){ + InstanceData inst = scene.instances[threadID]; + MeshletDescription m = meshlets.meshletInfos[groupID.x]; + const uint vertexLoops = (MAX_VERTICES + GROUP_SIZE - 1) / GROUP_SIZE; + for(uint loop = 0; loop < vertexLoops; ++loop) + { + uint v = threadID + loop * GROUP_SIZE; + v = min(v, m.vertexCount - 1); + InterlockedMax(gs_numVertices, v + 1); + { + int vertexIndex = meshlets.vertexIndices[m.vertexOffset + v]; + gs_vertices[v] = vertexData.getAttributes(vertexIndex, inst); + } + } + + const uint primitiveLoops = (MAX_PRIMITIVES + GROUP_SIZE - 1) / GROUP_SIZE; + for(uint loop = 0; loop < primitiveLoops; ++loop) + { + uint p = threadID + loop * GROUP_SIZE; + p = min(p, m.primitiveCount - 1); + InterlockedMax(gs_numPrimitives, p + 1); + { + uint8_t local_idx0 = meshlets.primitiveIndices[m.primitiveOffset + (p * 3) + 0]; + uint8_t local_idx1 = meshlets.primitiveIndices[m.primitiveOffset + (p * 3) + 1]; + uint8_t local_idx2 = meshlets.primitiveIndices[m.primitiveOffset + (p * 3) + 2]; + uint32_t idx0 = meshlets.vertexIndices[m.vertexOffset + local_idx0]; + uint32_t idx1 = meshlets.vertexIndices[m.vertexOffset + local_idx1]; + uint32_t idx2 = meshlets.vertexIndices[m.vertexOffset + local_idx2]; + gs_indices[p * 3 + 0] = idx0; + gs_indices[p * 3 + 1] = idx1; + gs_indices[p * 3 + 2] = idx2; + } + } + GroupMemoryBarrierWithGroupSync(); + SetMeshOutputCounts(gs_numVertices, gs_numPrimitives); + GroupMemoryBarrierWithGroupSync(); + for(uint loop1 = 0; loop1 < vertexLoops; ++loop1) + { + uint v = threadID + loop1 * GROUP_SIZE; + vertices[v] = gs_vertices[v]; + } + for(uint loop2 = 0; loop2 < primitiveLoops; ++loop2) + { + uint p = threadID + loop2 * GROUP_SIZE; + indices[p] = gs_indices[p]; + } } \ No newline at end of file diff --git a/src/Engine/Graphics/IndexBufferTopologyData.h b/src/Engine/Graphics/IndexBufferTopologyData.h index 5212bc5..1d850a1 100644 --- a/src/Engine/Graphics/IndexBufferTopologyData.h +++ b/src/Engine/Graphics/IndexBufferTopologyData.h @@ -3,9 +3,11 @@ namespace Seele { -class IndexBufferTopologyData +class IndexBufferTopologyData : public TopologyData { public: + IndexBufferTopologyData(); + virtual ~IndexBufferTopologyData(); private: Gfx::PIndexBuffer indices; }; diff --git a/src/Engine/Graphics/TopologyData.h b/src/Engine/Graphics/TopologyData.h index 8803ada..a35944d 100644 --- a/src/Engine/Graphics/TopologyData.h +++ b/src/Engine/Graphics/TopologyData.h @@ -6,6 +6,9 @@ namespace Seele class TopologyData { public: + TopologyData(); + virtual ~TopologyData(); + virtual void bind() = 0; private: }; } // namespace Seele \ No newline at end of file