├── data ├── models │ ├── README.md │ └── simple_scene.glb ├── textures │ ├── froge.png │ ├── bluenoise16.png │ ├── bluenoise256.png │ ├── bluenoise32.png │ ├── fa-solid-900.ttf │ ├── icons │ │ ├── curve.png │ │ ├── lattice.png │ │ ├── scene.png │ │ ├── histogram.png │ │ ├── icon_rgb.png │ │ ├── icon_sun.png │ │ ├── lamp_spot.png │ │ ├── particles.png │ │ ├── ease_in_out.png │ │ ├── icon_camera.png │ │ ├── icon_object.png │ │ ├── lamp_point.png │ │ ├── icon_mesh_cube.png │ │ ├── icon_camera_flash.png │ │ └── icon_chroma_scope.png │ ├── fa-regular-400.ttf │ ├── tony_mcmapface │ │ └── lut.png │ ├── MaterialIcons-Regular.ttf │ └── RobotoCondensed-Regular.ttf ├── shaders │ ├── FullScreenTri.vert.glsl │ ├── hzb │ │ ├── HZBCommon.h.glsl │ │ ├── HZBCopy.comp.glsl │ │ └── HZBReduce.comp.glsl │ ├── visbuffer │ │ ├── VisbufferResolve.vert.glsl │ │ ├── CullMeshlets.h.glsl │ │ ├── Visbuffer.vert.glsl │ │ └── Visbuffer.frag.glsl │ ├── Texture.frag.glsl │ ├── debug │ │ ├── VertexColor.frag.glsl │ │ ├── viewer │ │ │ ├── VsmBitmaskHzb.frag.glsl │ │ │ ├── VsmPhysicalPages.frag.glsl │ │ │ ├── VsmOverdrawHeatmap.frag.glsl │ │ │ └── VsmDebugPageTable.frag.glsl │ │ ├── DebugAabb.vert.glsl │ │ ├── DebugRect.vert.glsl │ │ ├── Forward.vert.glsl │ │ ├── Forward.frag.glsl │ │ ├── Debug.vert.glsl │ │ └── DebugCommon.h.glsl │ ├── raytracing │ │ └── Test.rgen.glsl │ ├── shadows │ │ ├── vsm │ │ │ ├── VsmClearDirtyPages.comp.glsl │ │ │ ├── VsmResetPageVisibility.comp.glsl │ │ │ ├── VsmFreeNonVisiblePages.comp.glsl │ │ │ ├── VsmInitStencil.frag.glsl │ │ │ ├── VsmListDirtyPages.comp.glsl │ │ │ ├── VsmAllocRequest.h.glsl │ │ │ ├── VsmReduceBitmaskHzb.comp.glsl │ │ │ ├── VsmAllocatePages.comp.glsl │ │ │ ├── VsmMarkVisiblePages.comp.glsl │ │ │ └── VsmShadow.frag.glsl │ │ └── ShadowMain.vert.glsl │ ├── volumetric │ │ ├── Depth2exponential.comp.glsl │ │ ├── GaussianBlur.comp.glsl │ │ ├── MarchVolume.comp.glsl │ │ ├── Common.h │ │ ├── Frog.h │ │ └── ApplyVolumetricsDeferred.comp.glsl │ ├── bloom │ │ ├── BloomCommon.h.glsl │ │ ├── BloomUpsample.comp.glsl │ │ ├── BloomDownsample.comp.glsl │ │ ├── BloomDownsampleLowPass.comp.glsl │ │ └── BloomDownsampleCommon.h.glsl │ ├── GlobalUniforms.h.glsl │ ├── imgui │ │ ├── imgui.vert.glsl │ │ └── imgui.frag.glsl │ ├── BasicTypes.h.glsl │ ├── auto_exposure │ │ ├── AutoExposureCommon.h.glsl │ │ ├── GenerateLuminanceHistogram.comp.glsl │ │ └── ResolveLuminanceHistogram.comp.glsl │ ├── CalibrateHdr.comp.glsl │ ├── Config.shared.h │ ├── Hash.h.glsl │ ├── Utility.h.glsl │ ├── post │ │ └── TonemapAndDither.shared.h │ ├── ao │ │ └── rtao │ │ │ └── RayTracedAO.comp.glsl │ ├── Pbr.h.glsl │ ├── ShadeDeferredPbr.h.glsl │ └── Math.h.glsl └── config │ └── defaultLayout.ini ├── vendor └── stb_image.cpp ├── media ├── bistro_0.png ├── bistro_1.png ├── sponza_0.png └── sponza_1.png ├── src ├── RendererUtilities.h ├── Fvog │ ├── detail │ │ ├── Common.cpp │ │ ├── Common.h │ │ ├── ApiToEnum2.h │ │ ├── SamplerCache2.h │ │ ├── Hash2.h │ │ └── SamplerCache2.cpp │ ├── TriviallyCopyableByteSpan.h │ ├── Shader2.h │ ├── Timer2.h │ ├── Timer2.cpp │ └── AccelerationStructure.h ├── PCG.h ├── techniques │ ├── ao │ │ ├── RayTracedAO.h │ │ └── RayTracedAO.cpp │ ├── Bloom.h │ ├── AutoExposure.h │ ├── AutoExposure.cpp │ └── Bloom.cpp ├── debug │ ├── ForwardRenderer.h │ └── ForwardRenderer.cpp ├── Scene.h ├── RendererUtilities.cpp ├── SceneLoader.h ├── main.cpp ├── MathUtilities.h ├── Renderables.h ├── Application.h └── PipelineManager.h ├── .gitignore ├── LICENSE ├── .github └── workflows │ └── cmake.yml ├── .clang-format ├── CMakeSettings.json ├── README.md ├── CMakeLists.txt └── external └── CMakeLists.txt /data/models/README.md: -------------------------------------------------------------------------------- 1 | # Put models here 2 | -------------------------------------------------------------------------------- /vendor/stb_image.cpp: -------------------------------------------------------------------------------- 1 | #define STB_IMAGE_IMPLEMENTATION 2 | #include -------------------------------------------------------------------------------- /media/bistro_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/media/bistro_0.png -------------------------------------------------------------------------------- /media/bistro_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/media/bistro_1.png -------------------------------------------------------------------------------- /media/sponza_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/media/sponza_0.png -------------------------------------------------------------------------------- /media/sponza_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/media/sponza_1.png -------------------------------------------------------------------------------- /data/textures/froge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/froge.png -------------------------------------------------------------------------------- /data/models/simple_scene.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/models/simple_scene.glb -------------------------------------------------------------------------------- /data/textures/bluenoise16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/bluenoise16.png -------------------------------------------------------------------------------- /data/textures/bluenoise256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/bluenoise256.png -------------------------------------------------------------------------------- /data/textures/bluenoise32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/bluenoise32.png -------------------------------------------------------------------------------- /data/textures/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/fa-solid-900.ttf -------------------------------------------------------------------------------- /data/textures/icons/curve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/icons/curve.png -------------------------------------------------------------------------------- /data/textures/icons/lattice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/icons/lattice.png -------------------------------------------------------------------------------- /data/textures/icons/scene.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/icons/scene.png -------------------------------------------------------------------------------- /data/textures/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/fa-regular-400.ttf -------------------------------------------------------------------------------- /data/textures/icons/histogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/icons/histogram.png -------------------------------------------------------------------------------- /data/textures/icons/icon_rgb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/icons/icon_rgb.png -------------------------------------------------------------------------------- /data/textures/icons/icon_sun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/icons/icon_sun.png -------------------------------------------------------------------------------- /data/textures/icons/lamp_spot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/icons/lamp_spot.png -------------------------------------------------------------------------------- /data/textures/icons/particles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/icons/particles.png -------------------------------------------------------------------------------- /data/textures/icons/ease_in_out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/icons/ease_in_out.png -------------------------------------------------------------------------------- /data/textures/icons/icon_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/icons/icon_camera.png -------------------------------------------------------------------------------- /data/textures/icons/icon_object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/icons/icon_object.png -------------------------------------------------------------------------------- /data/textures/icons/lamp_point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/icons/lamp_point.png -------------------------------------------------------------------------------- /data/textures/tony_mcmapface/lut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/tony_mcmapface/lut.png -------------------------------------------------------------------------------- /data/textures/icons/icon_mesh_cube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/icons/icon_mesh_cube.png -------------------------------------------------------------------------------- /data/textures/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /data/textures/RobotoCondensed-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/RobotoCondensed-Regular.ttf -------------------------------------------------------------------------------- /data/textures/icons/icon_camera_flash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/icons/icon_camera_flash.png -------------------------------------------------------------------------------- /data/textures/icons/icon_chroma_scope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Frogfood/HEAD/data/textures/icons/icon_chroma_scope.png -------------------------------------------------------------------------------- /data/shaders/FullScreenTri.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(location = 0) out vec2 v_uv; 4 | 5 | void main() 6 | { 7 | vec2 pos = vec2(gl_VertexIndex == 0, gl_VertexIndex == 2); 8 | v_uv = pos.xy * 2.0; 9 | gl_Position = vec4(pos * 4.0 - 1.0, 0.0, 1.0); 10 | } -------------------------------------------------------------------------------- /data/shaders/hzb/HZBCommon.h.glsl: -------------------------------------------------------------------------------- 1 | #ifndef HZB_COMMON_H 2 | #define HZB_COMMON_H 3 | 4 | #include "../Config.shared.h" 5 | 6 | #ifdef REVERSE_Z 7 | #define REDUCE_NEAR max 8 | #define REDUCE_FAR min 9 | #else 10 | #define REDUCE_NEAR min 11 | #define REDUCE_FAR max 12 | #endif 13 | 14 | #endif // HZB_COMMON_H -------------------------------------------------------------------------------- /data/shaders/visbuffer/VisbufferResolve.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #extension GL_GOOGLE_include_directive : enable 3 | #include "VisbufferCommon.h.glsl" 4 | 5 | layout (location = 0) out vec2 v_uv; 6 | 7 | void main() 8 | { 9 | vec2 pos = vec2(gl_VertexIndex == 0, gl_VertexIndex == 2); 10 | v_uv = pos.xy * 2.0; 11 | gl_Position = vec4(pos * 4.0 - 1.0, 0.0, 1.0); 12 | } 13 | -------------------------------------------------------------------------------- /data/shaders/Texture.frag.glsl: -------------------------------------------------------------------------------- 1 | #include "Resources.h.glsl" 2 | 3 | FVOG_DECLARE_ARGUMENTS(DebugTextureArguments) 4 | { 5 | FVOG_UINT32 textureIndex; 6 | FVOG_UINT32 samplerIndex; 7 | }; 8 | 9 | layout(location = 0) in vec2 v_uv; 10 | 11 | layout(location = 0) out vec4 o_color; 12 | 13 | void main() 14 | { 15 | o_color = texture(Fvog_sampler2D(textureIndex, samplerIndex), v_uv); 16 | } -------------------------------------------------------------------------------- /src/RendererUtilities.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Fvog/Device.h" 3 | #include "Fvog/Shader2.h" 4 | 5 | #include 6 | 7 | namespace Fvog 8 | { 9 | class Device; 10 | class Texture; 11 | } 12 | 13 | Fvog::Shader LoadShaderWithIncludes2(Fvog::PipelineStage stage, const std::filesystem::path& path); 14 | 15 | Fvog::Texture LoadTextureShrimple(const std::filesystem::path& path); -------------------------------------------------------------------------------- /src/Fvog/detail/Common.cpp: -------------------------------------------------------------------------------- 1 | #include "Common.h" 2 | #include 3 | 4 | namespace Fvog::detail 5 | { 6 | void CheckVkResult(VkResult result) 7 | { 8 | // TODO: don't throw for certain non-success codes (since they aren't always errors) 9 | if (result != VK_SUCCESS) 10 | { 11 | throw std::runtime_error("result was not VK_SUCCESS"); 12 | } 13 | } 14 | } // namespace Fvog::detail 15 | -------------------------------------------------------------------------------- /src/Fvog/detail/Common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace Fvog::detail 8 | { 9 | template 10 | [[nodiscard]] T* Address(T&& v) 11 | { 12 | return std::addressof(v); 13 | } 14 | 15 | constexpr size_t AlignUp(size_t value, size_t alignment) 16 | { 17 | return (value + alignment - 1) & ~(alignment - 1); 18 | } 19 | 20 | void CheckVkResult(VkResult result); 21 | } -------------------------------------------------------------------------------- /data/shaders/debug/VertexColor.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | 3 | layout(location = 0) in vec4 v_color; 4 | layout(location = 0) out vec4 o_color; 5 | 6 | // FSR 2 reactive mask. Unused when FSR 2 is disabled 7 | layout(location = 1) out float o_reactiveMask; 8 | 9 | void main() 10 | { 11 | o_color = v_color; 12 | 13 | // Values above 0.9 are not recommended for use, as they are "unlikely [...] to ever produce good results" 14 | o_reactiveMask = min(o_color.a, 0.9); 15 | } -------------------------------------------------------------------------------- /data/shaders/raytracing/Test.rgen.glsl: -------------------------------------------------------------------------------- 1 | #extension GL_EXT_ray_tracing : require 2 | 3 | #include "../Resources.h.glsl" 4 | 5 | FVOG_DECLARE_STORAGE_IMAGES(image2D); 6 | 7 | FVOG_DECLARE_ARGUMENTS(PushConstant) 8 | { 9 | uint imageIndex; 10 | }; 11 | 12 | #define g_image FvogGetStorageImage(image2D, imageIndex) 13 | 14 | void main() 15 | { 16 | const vec2 uv = vec2(gl_LaunchIDEXT.xy) / vec2(gl_LaunchSizeEXT.xy); 17 | imageStore(g_image, ivec2(gl_LaunchIDEXT.xy), vec4(uv, 0.0, 1.0)); 18 | } 19 | -------------------------------------------------------------------------------- /data/shaders/debug/viewer/VsmBitmaskHzb.frag.glsl: -------------------------------------------------------------------------------- 1 | #include "../../Resources.h.glsl" 2 | 3 | FVOG_DECLARE_ARGUMENTS(ViewerUniforms) 4 | { 5 | uint textureIndex; 6 | uint samplerIndex; 7 | int texLayer; 8 | int texLevel; 9 | }pc; 10 | 11 | layout(location = 0) in vec2 v_uv; 12 | 13 | layout(location = 0) out vec4 o_color; 14 | 15 | void main() 16 | { 17 | const float val = (textureLod(Fvog_usampler2DArray(pc.textureIndex, pc.samplerIndex), vec3(v_uv, pc.texLayer), float(pc.texLevel)).x); 18 | 19 | o_color = vec4(vec3(val), 1); 20 | } -------------------------------------------------------------------------------- /data/shaders/debug/viewer/VsmPhysicalPages.frag.glsl: -------------------------------------------------------------------------------- 1 | #include "../../Math.h.glsl" 2 | #include "../../GlobalUniforms.h.glsl" 3 | #include "../../Resources.h.glsl" 4 | 5 | FVOG_DECLARE_ARGUMENTS(ViewerUniforms) 6 | { 7 | uint textureIndex; 8 | uint samplerIndex; 9 | int texLayer; 10 | int texLevel; 11 | }pc; 12 | 13 | layout(location = 0) in vec2 v_uv; 14 | 15 | layout(location = 0) out vec4 o_color; 16 | 17 | void main() 18 | { 19 | const float depth = textureLod(Fvog_sampler2D(pc.textureIndex, pc.samplerIndex), v_uv, float(pc.texLevel)).x; 20 | 21 | o_color = vec4(vec3(depth), 1); 22 | } -------------------------------------------------------------------------------- /data/shaders/shadows/vsm/VsmClearDirtyPages.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | 3 | #extension GL_GOOGLE_include_directive : enable 4 | 5 | #include "../../Math.h.glsl" 6 | #include "../../GlobalUniforms.h.glsl" 7 | #include "VsmCommon.h.glsl" 8 | 9 | layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; 10 | void main() 11 | { 12 | const ivec3 gid = ivec3(gl_GlobalInvocationID.xyz); 13 | 14 | if (any(greaterThanEqual(gid.xy, ivec2(PAGE_SIZE)))) 15 | { 16 | return; 17 | } 18 | 19 | const uint pageAddress = dirtyPageList.data[gid.z]; 20 | StorePageTexel(gid.xy, pageAddress, 1.0); 21 | } -------------------------------------------------------------------------------- /data/shaders/debug/viewer/VsmOverdrawHeatmap.frag.glsl: -------------------------------------------------------------------------------- 1 | #include "../../Config.shared.h" 2 | #include "../../Math.h.glsl" 3 | #include "../../Resources.h.glsl" 4 | 5 | FVOG_DECLARE_ARGUMENTS(ViewerUniforms) 6 | { 7 | uint textureIndex; 8 | uint samplerIndex; 9 | int texLayer; 10 | int texLevel; 11 | }pc; 12 | 13 | layout(location = 0) in vec2 v_uv; 14 | 15 | layout(location = 0) out vec4 o_color; 16 | 17 | void main() 18 | { 19 | const uint overdraw = textureLod(Fvog_usampler2D(pc.textureIndex, pc.samplerIndex), v_uv, float(pc.texLevel)).x; 20 | 21 | o_color = vec4(vec3(TurboColormap(overdraw / VSM_MAX_OVERDRAW)), 1); 22 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # CMake 35 | CMakeFiles/ 36 | *.cmake 37 | 38 | # Build artifacts 39 | build/ 40 | cmake-build-debug/ 41 | cmake-build-release/ 42 | /.vs 43 | 44 | # Large/test assets 45 | /example/models 46 | 47 | # Clang cache 48 | .cache/ 49 | 50 | # IntelliJ 51 | .idea/ 52 | /bin 53 | -------------------------------------------------------------------------------- /data/shaders/shadows/vsm/VsmResetPageVisibility.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | 3 | #extension GL_GOOGLE_include_directive : enable 4 | 5 | #include "../../Math.h.glsl" 6 | #include "../../GlobalUniforms.h.glsl" 7 | #include "VsmCommon.h.glsl" 8 | 9 | layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; 10 | void main() 11 | { 12 | const ivec3 gid = ivec3(gl_GlobalInvocationID.xyz); 13 | 14 | if (any(greaterThanEqual(gid, imageSize(i_pageTables)))) 15 | { 16 | return; 17 | } 18 | 19 | uint pageData = imageLoad(i_pageTables, gid).x; 20 | pageData = SetIsPageVisible(pageData, false); 21 | pageData = SetIsPageDirty(pageData, false); 22 | imageStore(i_pageTables, gid, uvec4(pageData, 0, 0, 0)); 23 | } -------------------------------------------------------------------------------- /data/shaders/volumetric/Depth2exponential.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | 3 | layout(binding = 0) uniform sampler2D s_depth; 4 | layout(binding = 0) uniform writeonly image2D i_depthExp; 5 | 6 | layout(binding = 0, std140) uniform UNIFORMS 7 | { 8 | float depthExponent; 9 | }uniforms; 10 | 11 | layout(local_size_x = 8, local_size_y = 8) in; 12 | void main() 13 | { 14 | ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 15 | ivec2 targetDim = imageSize(i_depthExp); 16 | if (any(greaterThanEqual(gid, targetDim))) 17 | return; 18 | vec2 uv = (vec2(gid) + 0.5) / targetDim.xy; 19 | 20 | float depth = textureLod(s_depth, uv, 0).x; 21 | float depthExp = exp(depth * uniforms.depthExponent); 22 | imageStore(i_depthExp, gid, depthExp.xxxx); 23 | } -------------------------------------------------------------------------------- /data/shaders/shadows/vsm/VsmFreeNonVisiblePages.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | 3 | //#extension GL_GOOGLE_include_directive : enable 4 | 5 | #include "../../Math.h.glsl" 6 | #include "../../GlobalUniforms.h.glsl" 7 | #include "VsmCommon.h.glsl" 8 | 9 | layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; 10 | void main() 11 | { 12 | const ivec3 gid = ivec3(gl_GlobalInvocationID.xyz); 13 | 14 | if (any(greaterThanEqual(gid, imageSize(i_pageTables)))) 15 | { 16 | return; 17 | } 18 | 19 | // Preserve allocations for pages that are visible 20 | uint pageData = imageLoad(i_pageTables, gid).x; 21 | if (!GetIsPageVisible(pageData) && GetIsPageBacked(pageData)) 22 | { 23 | pageData = SetIsPageBacked(pageData, false); 24 | imageStore(i_pageTables, gid, uvec4(pageData, 0, 0, 0)); 25 | } 26 | } -------------------------------------------------------------------------------- /data/shaders/debug/DebugAabb.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | #extension GL_GOOGLE_include_directive : enable 4 | 5 | #include "../GlobalUniforms.h.glsl" 6 | #include "../Utility.h.glsl" 7 | #include "DebugCommon.h.glsl" 8 | 9 | FVOG_DECLARE_ARGUMENTS(DebugAabbArguments) 10 | { 11 | FVOG_UINT32 globalUniformsIndex; 12 | FVOG_UINT32 debugAabbBufferIndex; 13 | }; 14 | 15 | layout(location = 0) out vec4 v_color; 16 | 17 | void main() 18 | { 19 | DebugAabb box = debugAabbBuffers[debugAabbBufferIndex].aabbs[gl_InstanceIndex + gl_BaseInstance]; 20 | v_color = PackedToVec4(box.color); 21 | 22 | vec3 a_pos = CreateCube(gl_VertexIndex) - 0.5; 23 | vec3 worldPos = a_pos * PackedToVec3(box.extent) + PackedToVec3(box.center); 24 | gl_Position = perFrameUniformsBuffers[globalUniformsIndex].viewProj * vec4(worldPos, 1.0); 25 | } -------------------------------------------------------------------------------- /data/shaders/bloom/BloomCommon.h.glsl: -------------------------------------------------------------------------------- 1 | #ifndef BLOOM_COMMON_H 2 | #define BLOOM_COMMON_H 3 | 4 | #include "../Resources.h.glsl" 5 | 6 | FVOG_DECLARE_ARGUMENTS(BloomUniforms) 7 | { 8 | FVOG_UINT32 sourceSampledImageIdx; 9 | FVOG_UINT32 targetSampledImageIdx; // For upsample pass 10 | FVOG_UINT32 targetStorageImageIdx; 11 | FVOG_UINT32 linearSamplerIdx; 12 | 13 | FVOG_IVEC2 sourceDim; 14 | FVOG_IVEC2 targetDim; 15 | FVOG_FLOAT width; 16 | FVOG_FLOAT strength; 17 | FVOG_FLOAT sourceLod; 18 | FVOG_FLOAT targetLod; 19 | FVOG_UINT32 numPasses; 20 | FVOG_UINT32 isFinalPass; 21 | }uniforms; 22 | 23 | #ifndef __cplusplus 24 | 25 | #define s_source Fvog_sampler2D(uniforms.sourceSampledImageIdx, uniforms.linearSamplerIdx) 26 | #define s_target Fvog_sampler2D(uniforms.targetSampledImageIdx, uniforms.linearSamplerIdx) 27 | #define i_target Fvog_image2D(uniforms.targetStorageImageIdx) 28 | 29 | #endif // __cplusplus 30 | 31 | #endif // BLOOM_COMMON_H -------------------------------------------------------------------------------- /src/Fvog/detail/ApiToEnum2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../BasicTypes2.h" 4 | 5 | #include 6 | 7 | namespace Fvog::detail 8 | { 9 | VkImageType ViewTypeToImageType(VkImageViewType viewType); 10 | 11 | bool FormatIsDepth(Format format); 12 | 13 | bool FormatIsStencil(Format format); 14 | 15 | bool FormatIsColor(Format format); 16 | 17 | bool FormatIsSrgb(Format format); 18 | 19 | VkFormat FormatToVk(Format format); 20 | 21 | Format VkToFormat(VkFormat format); 22 | 23 | bool FormatIsBlockCompressed(Format format); 24 | 25 | uint64_t BlockCompressedImageSize(Format bcFormat, uint32_t width, uint32_t height, uint32_t depth); 26 | 27 | // Returns the size, in bytes, of a single pixel or block (for compressed formats) of the input format 28 | uint32_t FormatStorageSize(Format format); 29 | 30 | const char* FormatToString(Format format); 31 | 32 | const char* VkColorSpaceToString(VkColorSpaceKHR colorSpace); 33 | } 34 | -------------------------------------------------------------------------------- /src/Fvog/TriviallyCopyableByteSpan.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace Fvog 7 | { 8 | /// @brief Used to constrain the types accpeted by Buffer 9 | class TriviallyCopyableByteSpan : public std::span 10 | { 11 | public: 12 | template 13 | requires std::is_trivially_copyable_v 14 | TriviallyCopyableByteSpan(const T& t) : std::span(std::as_bytes(std::span{&t, static_cast(1)})) 15 | { 16 | } 17 | 18 | template 19 | requires std::is_trivially_copyable_v 20 | TriviallyCopyableByteSpan(std::span t) : std::span(std::as_bytes(t)) 21 | { 22 | } 23 | 24 | template 25 | requires std::is_trivially_copyable_v 26 | TriviallyCopyableByteSpan(std::span t) : std::span(std::as_bytes(t)) 27 | { 28 | } 29 | }; 30 | } // namespace Fvog -------------------------------------------------------------------------------- /data/shaders/debug/DebugRect.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | #extension GL_GOOGLE_include_directive : enable 4 | 5 | #include "../GlobalUniforms.h.glsl" 6 | #include "../Utility.h.glsl" 7 | #include "DebugCommon.h.glsl" 8 | 9 | layout(location = 0) out vec4 v_color; 10 | 11 | FVOG_DECLARE_ARGUMENTS(DebugRectArguments) 12 | { 13 | FVOG_UINT32 debugRectBufferIndex; 14 | }; 15 | 16 | void main() 17 | { 18 | DebugRect rect = debugRectBuffers[debugRectBufferIndex].rects[gl_InstanceIndex + gl_BaseInstance]; 19 | v_color = PackedToVec4(rect.color); 20 | 21 | PackedVec2 uvPos; 22 | if (gl_VertexIndex == 0) 23 | uvPos = rect.minOffset; 24 | else if (gl_VertexIndex == 1) 25 | uvPos = PackedVec2(rect.maxOffset.x, rect.minOffset.y); 26 | else if (gl_VertexIndex == 2) 27 | uvPos = rect.maxOffset; 28 | else 29 | uvPos = PackedVec2(rect.minOffset.x, rect.maxOffset.y); 30 | 31 | vec2 clip = PackedToVec2(uvPos) * 2.0 - 1.0; 32 | 33 | gl_Position = vec4(clip, rect.depth, 1.0); 34 | } -------------------------------------------------------------------------------- /src/PCG.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace PCG 6 | { 7 | constexpr std::uint32_t Hash(std::uint32_t seed) 8 | { 9 | std::uint32_t state = seed * 747796405u + 2891336453u; 10 | std::uint32_t word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; 11 | return (word >> 22u) ^ word; 12 | } 13 | 14 | // Used to advance the PCG state. 15 | constexpr std::uint32_t RandU32(std::uint32_t& rng_state) 16 | { 17 | std::uint32_t state = rng_state; 18 | rng_state = rng_state * 747796405u + 2891336453u; 19 | std::uint32_t word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; 20 | return (word >> 22u) ^ word; 21 | } 22 | 23 | // Advances the prng state and returns the corresponding random float. 24 | constexpr float RandFloat(std::uint32_t& state, float min = 0, float max = 1) 25 | { 26 | state = RandU32(state); 27 | float f = float(state) * std::bit_cast(0x2f800004u); 28 | return f * (max - min) + min; 29 | } 30 | } -------------------------------------------------------------------------------- /data/shaders/GlobalUniforms.h.glsl: -------------------------------------------------------------------------------- 1 | #ifndef GLOBAL_UNIFORMS_H 2 | #define GLOBAL_UNIFORMS_H 3 | 4 | #extension GL_GOOGLE_include_directive : enable 5 | 6 | #include "Resources.h.glsl" 7 | 8 | #define CULL_MESHLET_FRUSTUM (1 << 0) 9 | #define CULL_MESHLET_HIZ (1 << 1) 10 | #define CULL_PRIMITIVE_BACKFACE (1 << 2) 11 | #define CULL_PRIMITIVE_FRUSTUM (1 << 3) 12 | #define CULL_PRIMITIVE_SMALL (1 << 4) 13 | #define CULL_PRIMITIVE_VSM (1 << 5) 14 | #define USE_HASHED_TRANSPARENCY (1 << 6) 15 | 16 | //layout (binding = 0, std140) uniform PerFrameUniformsBuffer 17 | FVOG_DECLARE_STORAGE_BUFFERS(restrict readonly PerFrameUniformsBuffer) 18 | { 19 | mat4 viewProj; 20 | mat4 oldViewProjUnjittered; 21 | mat4 viewProjUnjittered; 22 | mat4 invViewProj; 23 | mat4 proj; 24 | mat4 invProj; 25 | vec4 cameraPos; 26 | uint meshletCount; 27 | uint maxIndices; 28 | float bindlessSamplerLodBias; 29 | uint flags; 30 | float alphaHashScale; 31 | } perFrameUniformsBuffers[]; 32 | 33 | #endif // GLOBAL_UNIFORMS_H -------------------------------------------------------------------------------- /data/shaders/shadows/vsm/VsmInitStencil.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #extension GL_GOOGLE_include_directive : enable 3 | 4 | #include "../../Config.shared.h" 5 | #include "VsmCommon.h.glsl" 6 | 7 | layout(binding = 1, r32ui) uniform restrict uimage2D i_physicalPagesUint; 8 | 9 | layout(binding = 1, std140) uniform VsmShadowUniforms 10 | { 11 | uint clipmapLod; 12 | }; 13 | 14 | layout(location = 0) in vec2 v_uv; 15 | 16 | void main() 17 | { 18 | const uint clipmapIndex = clipmapUniforms.clipmapTableIndices[clipmapLod]; 19 | const ivec2 pageOffset = clipmapUniforms.clipmapPageOffsets[clipmapLod]; 20 | const ivec2 pageAddressXy = ivec2(mod(vec2(ivec2(gl_FragCoord.xy) / PAGE_SIZE + pageOffset), vec2(imageSize(i_pageTables).xy))); 21 | const uint pageData = imageLoad(i_pageTables, ivec3(pageAddressXy, clipmapIndex)).x; 22 | const bool pageIsActive = GetIsPageBacked(pageData) && GetIsPageDirty(pageData); 23 | 24 | if (!pageIsActive) // write stencil ref to active pages only 25 | { 26 | discard; 27 | } 28 | } -------------------------------------------------------------------------------- /data/shaders/debug/Forward.vert.glsl: -------------------------------------------------------------------------------- 1 | #include "../Resources.h.glsl" 2 | #include "../Utility.h.glsl" 3 | 4 | struct Vertex 5 | { 6 | vec3 position; 7 | uint normal; 8 | vec2 uv; 9 | }; 10 | 11 | FVOG_DECLARE_BUFFER_REFERENCE(VertexBuffer) 12 | { 13 | Vertex vertices[]; 14 | }; 15 | 16 | FVOG_DECLARE_STORAGE_BUFFERS(ArgsBuffers) 17 | { 18 | mat4 clipFromWorld; 19 | mat4 worldFromObject; 20 | VertexBuffer vertexBuffer; 21 | FVOG_UINT32 materialId; 22 | FVOG_UINT32 materialBufferIndex; 23 | FVOG_UINT32 samplerIndex; 24 | }argsBuffers[]; 25 | 26 | FVOG_DECLARE_ARGUMENTS(DebugForwardArgs) 27 | { 28 | uint argsBufferIndex; 29 | }; 30 | 31 | #define pc argsBuffers[argsBufferIndex] 32 | 33 | layout(location = 0) out vec2 o_uv; 34 | layout(location = 1) out vec3 o_normal; 35 | 36 | void main() 37 | { 38 | Vertex vertex = pc.vertexBuffer.vertices[gl_VertexIndex]; 39 | 40 | o_uv = vertex.uv; 41 | o_normal = OctToVec3(unpackSnorm2x16(vertex.normal)); 42 | 43 | gl_Position = pc.clipFromWorld * pc.worldFromObject * vec4(vertex.position, 1.0); 44 | } -------------------------------------------------------------------------------- /src/techniques/ao/RayTracedAO.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "Fvog/Texture2.h" 4 | #include "Fvog/Pipeline2.h" 5 | #include "PipelineManager.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | namespace Fvog 12 | { 13 | class Device; 14 | class Tlas; 15 | } 16 | 17 | namespace Techniques 18 | { 19 | class RayTracedAO 20 | { 21 | public: 22 | RayTracedAO(); 23 | 24 | struct ComputeParams 25 | { 26 | Fvog::Tlas* tlas; 27 | Fvog::Texture* inputDepth; 28 | Fvog::Texture* inputNormalAndFaceNormal; 29 | Fvog::Extent2D outputSize; 30 | uint32_t numRays{5}; 31 | float rayLength{1}; 32 | glm::mat4 world_from_clip; 33 | uint32_t frameNumber{}; 34 | 35 | // TODO: scale factor and denoising params 36 | }; 37 | 38 | [[nodiscard]] Fvog::Texture& ComputeAO(VkCommandBuffer commandBuffer, const ComputeParams& params); 39 | 40 | private: 41 | PipelineManager::ComputePipelineKey rtaoPipeline_; 42 | std::optional aoTexture_; 43 | }; 44 | } -------------------------------------------------------------------------------- /data/shaders/shadows/vsm/VsmListDirtyPages.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | 3 | #include "../../Math.h.glsl" 4 | #include "../../GlobalUniforms.h.glsl" 5 | #include "VsmCommon.h.glsl" 6 | 7 | // Indirect dispatch params for clearing dirty pages (these pages will then be rendered) 8 | FVOG_DECLARE_STORAGE_BUFFERS(VsmPageClearDispatchParamsBuffers) 9 | { 10 | uint groupCountX; 11 | uint groupCountY; 12 | uint groupCountZ; 13 | }pageClearDispatchBuffers[]; 14 | 15 | #define pageClearDispatch pageClearDispatchBuffers[pageClearDispatchIndex] 16 | 17 | layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; 18 | void main() 19 | { 20 | const ivec3 gid = ivec3(gl_GlobalInvocationID.xyz); 21 | 22 | if (any(greaterThanEqual(gid, imageSize(i_pageTables)))) 23 | { 24 | return; 25 | } 26 | 27 | const uint pageData = imageLoad(i_pageTables, gid).x; 28 | 29 | if (GetIsPageBacked(pageData) && GetIsPageDirty(pageData)) 30 | { 31 | if (TryPushPageClear(GetPagePhysicalAddress(pageData))) 32 | { 33 | atomicAdd(pageClearDispatch.groupCountZ, 1); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jake Ryan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Fvog/detail/SamplerCache2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../Texture2.h" 3 | #include 4 | 5 | template<> 6 | struct std::hash 7 | { 8 | std::size_t operator()(const Fvog::SamplerCreateInfo& k) const noexcept; 9 | }; 10 | 11 | namespace Fwog 12 | { 13 | class Device; 14 | } 15 | 16 | namespace Fvog::detail 17 | { 18 | class SamplerCache 19 | { 20 | public: 21 | SamplerCache(Device* device) : device_(device) {} 22 | SamplerCache(const SamplerCache&) = delete; 23 | SamplerCache& operator=(const SamplerCache&) = delete; 24 | SamplerCache(SamplerCache&&) noexcept = default; 25 | SamplerCache& operator=(SamplerCache&&) noexcept = default; 26 | ~SamplerCache() 27 | { 28 | Clear(); 29 | } 30 | 31 | [[nodiscard]] Sampler CreateOrGetCachedTextureSampler(const SamplerCreateInfo& samplerState, std::string name); 32 | [[nodiscard]] size_t Size() const; 33 | void Clear(); 34 | 35 | private: 36 | Device* device_; 37 | std::unordered_map samplerCache_; 38 | std::unordered_map descriptorCache_; 39 | }; 40 | } // namespace Fwog::detail -------------------------------------------------------------------------------- /data/shaders/hzb/HZBCopy.comp.glsl: -------------------------------------------------------------------------------- 1 | #include "../Resources.h.glsl" 2 | #include "HZBCommon.h.glsl" 3 | 4 | layout (local_size_x = 16, local_size_y = 16, local_size_z = 1) in; 5 | 6 | FVOG_DECLARE_ARGUMENTS(HzbCopyPushConstants) 7 | { 8 | FVOG_UINT32 hzbIndex; 9 | FVOG_UINT32 depthIndex; 10 | FVOG_UINT32 depthSamplerIndex; 11 | }; 12 | 13 | void main() { 14 | const vec2 position = vec2(gl_GlobalInvocationID.xy); 15 | const vec2 hzb_size = vec2(imageSize(FvogGetStorageImage(image2D, hzbIndex))); 16 | const vec2 uv = (position + 0.5) / hzb_size; 17 | const float[] depth = float[]( 18 | textureLodOffset(Fvog_sampler2D(depthIndex, depthSamplerIndex), uv, 0.0, ivec2(0.0, 0.0)).r, 19 | textureLodOffset(Fvog_sampler2D(depthIndex, depthSamplerIndex), uv, 0.0, ivec2(1.0, 0.0)).r, 20 | textureLodOffset(Fvog_sampler2D(depthIndex, depthSamplerIndex), uv, 0.0, ivec2(0.0, 1.0)).r, 21 | textureLodOffset(Fvog_sampler2D(depthIndex, depthSamplerIndex), uv, 0.0, ivec2(1.0, 1.0)).r 22 | ); 23 | const float depth_sample = REDUCE_FAR(REDUCE_FAR(REDUCE_FAR(depth[0], depth[1]), depth[2]), depth[3]); 24 | imageStore(FvogGetStorageImage(image2D, hzbIndex), ivec2(position), vec4(depth_sample)); 25 | } 26 | -------------------------------------------------------------------------------- /data/shaders/hzb/HZBReduce.comp.glsl: -------------------------------------------------------------------------------- 1 | #include "../Resources.h.glsl" 2 | #include "HZBCommon.h.glsl" 3 | 4 | layout (local_size_x = 16, local_size_y = 16, local_size_z = 1) in; 5 | 6 | FVOG_DECLARE_ARGUMENTS(HzbReducePushConstants) 7 | { 8 | FVOG_UINT32 prevHzbIndex; 9 | FVOG_UINT32 curHzbIndex; 10 | }; 11 | 12 | void main() { 13 | const vec2 position = vec2(gl_GlobalInvocationID.xy); 14 | const vec2 prev_hzb_size = vec2(imageSize(Fvog_image2D(prevHzbIndex))); 15 | const vec2 curr_hzb_size = vec2(imageSize(Fvog_image2D(curHzbIndex))); 16 | const vec2 scaled_pos = position * (prev_hzb_size / curr_hzb_size); 17 | const float[] depths = float[]( 18 | imageLoad(Fvog_image2D(prevHzbIndex), ivec2(scaled_pos + vec2(0.0, 0.0) + 0.5)).r, 19 | imageLoad(Fvog_image2D(prevHzbIndex), ivec2(scaled_pos + vec2(1.0, 0.0) + 0.5)).r, 20 | imageLoad(Fvog_image2D(prevHzbIndex), ivec2(scaled_pos + vec2(0.0, 1.0) + 0.5)).r, 21 | imageLoad(Fvog_image2D(prevHzbIndex), ivec2(scaled_pos + vec2(1.0, 1.0) + 0.5)).r 22 | ); 23 | const float depth = REDUCE_FAR(REDUCE_FAR(REDUCE_FAR(depths[0], depths[1]), depths[2]), depths[3]); 24 | imageStore(Fvog_image2D(curHzbIndex), ivec2(position), vec4(depth)); 25 | } 26 | -------------------------------------------------------------------------------- /data/shaders/imgui/imgui.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #extension GL_EXT_nonuniform_qualifier : require 3 | #extension GL_EXT_scalar_block_layout : require 4 | 5 | #define COLOR_SPACE_sRGB_NONLINEAR 0 6 | #define COLOR_SPACE_scRGB_LINEAR 1 7 | #define COLOR_SPACE_HDR10_ST2084 2 8 | #define COLOR_SPACE_BT2020_LINEAR 3 9 | 10 | layout(push_constant, scalar) uniform PushConstants 11 | { 12 | uint vertexBufferIndex; 13 | uint textureIndex; 14 | uint samplerIndex; 15 | uint textureColorSpace; 16 | uint displayColorSpace; 17 | float maxDisplayNits; 18 | vec2 scale; 19 | vec2 translation; 20 | } pc; 21 | 22 | struct Vertex 23 | { 24 | vec2 position; 25 | vec2 texcoord; 26 | uint color; 27 | }; 28 | 29 | layout(set = 0, binding = 0, scalar) readonly buffer VertexBuffer 30 | { 31 | Vertex vertices[]; 32 | }buffers[]; 33 | 34 | out gl_PerVertex { vec4 gl_Position; }; 35 | layout(location = 0) out struct { vec4 Color; vec2 UV; } Out; 36 | void main() 37 | { 38 | Vertex vertex = buffers[pc.vertexBufferIndex].vertices[gl_VertexIndex]; 39 | Out.Color = unpackUnorm4x8(vertex.color); 40 | Out.UV = vertex.texcoord; 41 | gl_Position = vec4(vertex.position * pc.scale + pc.translation, 0, 1); 42 | } -------------------------------------------------------------------------------- /data/shaders/debug/Forward.frag.glsl: -------------------------------------------------------------------------------- 1 | #include "../Resources.h.glsl" 2 | #define VISBUFFER_NO_PUSH_CONSTANTS 3 | #include "../visbuffer/VisbufferCommon.h.glsl" 4 | 5 | FVOG_DECLARE_SAMPLERS; 6 | FVOG_DECLARE_SAMPLED_IMAGES(texture2D); 7 | 8 | FVOG_DECLARE_ARGUMENTS(DebugForwardArgs) 9 | { 10 | uint argsBufferIndex; 11 | }; 12 | 13 | FVOG_DECLARE_STORAGE_BUFFERS(ArgsBuffers) 14 | { 15 | mat4 clipFromWorld; 16 | mat4 worldFromObject; 17 | VertexBuffer vertexBuffer; 18 | FVOG_UINT32 materialId; 19 | FVOG_UINT32 materialBufferIndex; 20 | FVOG_UINT32 samplerIndex; 21 | }argsBuffers[]; 22 | 23 | #define pc argsBuffers[argsBufferIndex] 24 | 25 | layout(location = 0) in vec2 i_uv; 26 | layout(location = 1) in vec3 i_normal; 27 | 28 | layout(location = 0) out vec4 o_color; 29 | 30 | void main() 31 | { 32 | o_color.a = 1.0; 33 | 34 | GpuMaterial material = MaterialBuffers[pc.materialBufferIndex].materials[pc.materialId]; 35 | o_color.rgb = material.baseColorFactor.rgb; 36 | 37 | if (bool(material.flags & MATERIAL_HAS_BASE_COLOR)) 38 | { 39 | o_color.rgb *= texture(Fvog_sampler2D(material.baseColorTextureIndex, pc.samplerIndex), i_uv).rgb; 40 | } 41 | 42 | //o_color.rgb = vec3(i_uv, 0); 43 | } -------------------------------------------------------------------------------- /data/shaders/BasicTypes.h.glsl: -------------------------------------------------------------------------------- 1 | #ifndef BASIC_TYPES_H 2 | #define BASIC_TYPES_H 3 | 4 | struct DrawElementsIndirectCommand 5 | { 6 | uint indexCount; 7 | uint instanceCount; 8 | uint firstIndex; 9 | int baseVertex; 10 | uint baseInstance; 11 | }; 12 | 13 | struct DrawIndirectCommand 14 | { 15 | uint vertexCount; 16 | uint instanceCount; 17 | uint firstVertex; 18 | uint firstInstance; 19 | }; 20 | 21 | // Packed vector types with scalar alignment 22 | struct PackedVec2 23 | { 24 | float x, y; 25 | }; 26 | 27 | struct PackedVec3 28 | { 29 | float x, y, z; 30 | }; 31 | 32 | struct PackedVec4 33 | { 34 | float x, y, z, w; 35 | }; 36 | 37 | vec2 PackedToVec2(in PackedVec2 v) 38 | { 39 | return vec2(v.x, v.y); 40 | } 41 | 42 | PackedVec2 Vec2ToPacked(in vec2 v) 43 | { 44 | return PackedVec2(v.x, v.y); 45 | } 46 | 47 | vec3 PackedToVec3(in PackedVec3 v) 48 | { 49 | return vec3(v.x, v.y, v.z); 50 | } 51 | 52 | PackedVec3 Vec3ToPacked(in vec3 v) 53 | { 54 | return PackedVec3(v.x, v.y, v.z); 55 | } 56 | 57 | vec4 PackedToVec4(in PackedVec4 v) 58 | { 59 | return vec4(v.x, v.y, v.z, v.w); 60 | } 61 | 62 | PackedVec4 Vec4ToPacked(in vec4 v) 63 | { 64 | return PackedVec4(v.x, v.y, v.z, v.w); 65 | } 66 | 67 | #endif // BASIC_TYPES_H -------------------------------------------------------------------------------- /data/shaders/visbuffer/CullMeshlets.h.glsl: -------------------------------------------------------------------------------- 1 | #ifndef CULL_MESHLETS_H 2 | #define CULL_MESHLETS_H 3 | 4 | #include "../Resources.h.glsl" 5 | 6 | FVOG_DECLARE_ARGUMENTS(CullMeshletsPushConstants) 7 | { 8 | FVOG_UINT32 globalUniformsIndex; 9 | FVOG_UINT32 meshletInstancesIndex; 10 | FVOG_UINT32 meshletDataIndex; 11 | FVOG_UINT32 meshletPrimitivesIndex; 12 | FVOG_UINT32 meshletVerticesIndex; 13 | FVOG_UINT32 meshletIndicesIndex; 14 | FVOG_UINT32 transformsIndex; 15 | FVOG_UINT32 indirectDrawIndex; 16 | FVOG_UINT32 materialsIndex; 17 | FVOG_UINT32 viewIndex; 18 | 19 | FVOG_UINT32 pageTablesIndex; 20 | FVOG_UINT32 physicalPagesIndex; 21 | FVOG_UINT32 vsmBitmaskHzbIndex; 22 | FVOG_UINT32 vsmUniformsBufferIndex; 23 | FVOG_UINT32 dirtyPageListBufferIndex; 24 | FVOG_UINT32 clipmapUniformsBufferIndex; 25 | FVOG_UINT32 nearestSamplerIndex; 26 | FVOG_UINT32 pageClearDispatchIndex; 27 | 28 | FVOG_UINT32 hzbIndex; 29 | FVOG_UINT32 hzbSamplerIndex; 30 | FVOG_UINT32 cullTrianglesDispatchIndex; 31 | 32 | FVOG_UINT32 visibleMeshletsIndex; 33 | 34 | // CullTriangles.comp 35 | FVOG_UINT32 indexBufferIndex; 36 | 37 | // Debug 38 | FVOG_UINT32 debugAabbBufferIndex; 39 | FVOG_UINT32 debugRectBufferIndex; 40 | }; 41 | 42 | #endif // CULL_MESHLETS_H -------------------------------------------------------------------------------- /src/Fvog/detail/Hash2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace Fvog::detail::hashing 6 | { 7 | template 8 | struct hash; 9 | 10 | template 11 | inline void hash_combine(std::size_t& seed, const T& v) 12 | { 13 | seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); 14 | } 15 | 16 | // Recursive template code derived from Matthieu M. 17 | template::value - 1> 18 | struct HashValueImpl 19 | { 20 | static void apply(size_t& seed, const Tuple& tuple) 21 | { 22 | HashValueImpl::apply(seed, tuple); 23 | hash_combine(seed, std::get(tuple)); 24 | } 25 | }; 26 | 27 | template 28 | struct HashValueImpl 29 | { 30 | static void apply(size_t& seed, const Tuple& tuple) 31 | { 32 | hash_combine(seed, std::get<0>(tuple)); 33 | } 34 | }; 35 | 36 | template 37 | struct hash> 38 | { 39 | size_t operator()(const std::tuple& tt) const 40 | { 41 | size_t seed = 0; 42 | HashValueImpl>::apply(seed, tt); 43 | return seed; 44 | } 45 | }; 46 | } // namespace Fvog::detail::hashing -------------------------------------------------------------------------------- /data/shaders/auto_exposure/AutoExposureCommon.h.glsl: -------------------------------------------------------------------------------- 1 | #ifndef AUTO_EXPOSURE_COMMON_H 2 | #define AUTO_EXPOSURE_COMMON_H 3 | 4 | #extension GL_GOOGLE_include_directive : enable 5 | 6 | #define NUM_BUCKETS 128 7 | 8 | #include "../Resources.h.glsl" 9 | 10 | FVOG_DECLARE_ARGUMENTS(AutoExposurePushConstants) 11 | { 12 | FVOG_UINT32 autoExposureBufferIndex; 13 | FVOG_UINT32 exposureBufferIndex; 14 | FVOG_UINT32 hdrBufferIndex; 15 | }; 16 | 17 | //layout(std430, binding = 0) restrict buffer AutoExposureBuffer 18 | FVOG_DECLARE_STORAGE_BUFFERS(AutoExposureBuffers) 19 | { 20 | readonly float deltaTime; 21 | readonly float adjustmentSpeed; 22 | readonly float logMinLuminance; 23 | readonly float logMaxLuminance; 24 | readonly float targetLuminance; // 0.184 = 50% perceived lightness (L*) 25 | readonly uint numPixels; // Number of pixels considered in the reduction 26 | coherent uint histogramBuckets[NUM_BUCKETS]; 27 | } autoExposureBuffers[]; 28 | 29 | #define d_autoExposure autoExposureBuffers[autoExposureBufferIndex] 30 | 31 | //layout (std430, binding = 1) buffer ExposureBuffer 32 | FVOG_DECLARE_STORAGE_BUFFERS(ExposureBuffers) 33 | { 34 | float exposure; 35 | } exposureBuffers[]; 36 | 37 | #define d_exposureBuffer exposureBuffers[exposureBufferIndex] 38 | 39 | #endif // AUTO_EXPOSURE_COMMON_H -------------------------------------------------------------------------------- /data/shaders/auto_exposure/GenerateLuminanceHistogram.comp.glsl: -------------------------------------------------------------------------------- 1 | #include "AutoExposureCommon.h.glsl" 2 | #include "../Math.h.glsl" 3 | #include "../Utility.h.glsl" 4 | 5 | uint ColorToBucket(vec3 color) 6 | { 7 | const float luminance = Luminance(color); 8 | if (luminance < 1e-5) 9 | return 0; 10 | float mapped = Remap(log2(luminance), d_autoExposure.logMinLuminance, d_autoExposure.logMaxLuminance, 1.0, float(NUM_BUCKETS - 1)); 11 | return clamp(int(mapped), 0, NUM_BUCKETS - 1); 12 | } 13 | 14 | shared int sh_buckets[NUM_BUCKETS]; 15 | 16 | layout(local_size_x = 32, local_size_y = 32) in; 17 | void main() 18 | { 19 | const uint localId = gl_LocalInvocationIndex; 20 | sh_buckets[localId] = 0; 21 | 22 | barrier(); 23 | 24 | const uvec2 texSize = textureSize(FvogGetSampledImage(texture2D, hdrBufferIndex), 0); 25 | const uvec2 coords = uvec2(gl_GlobalInvocationID.xy); 26 | if (all(lessThan(coords, texSize))) 27 | { 28 | vec3 color = texelFetch(FvogGetSampledImage(texture2D, hdrBufferIndex), ivec2(coords), 0).rgb; 29 | uint bucket = ColorToBucket(color); 30 | atomicAdd(sh_buckets[bucket], 1); 31 | } 32 | 33 | barrier(); 34 | 35 | // Only do one global atomic add per bucket 36 | if (localId < NUM_BUCKETS) 37 | { 38 | atomicAdd(d_autoExposure.histogramBuckets[localId], sh_buckets[localId]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /data/shaders/debug/viewer/VsmDebugPageTable.frag.glsl: -------------------------------------------------------------------------------- 1 | #include "../../Math.h.glsl" 2 | #include "../../GlobalUniforms.h.glsl" 3 | #include "../../Resources.h.glsl" 4 | 5 | FVOG_DECLARE_ARGUMENTS(ViewerUniforms) 6 | { 7 | uint textureIndex; 8 | uint samplerIndex; 9 | int texLayer; 10 | int texLevel; 11 | }pc; 12 | 13 | layout(location = 0) in vec2 v_uv; 14 | 15 | layout(location = 0) out vec4 o_color; 16 | 17 | // TODO: these helpers will break if they change in VsmCommon.h.glsl 18 | #define PAGE_VISIBLE_BIT (1u) 19 | #define PAGE_DIRTY_BIT (2u) 20 | #define PAGE_BACKED_BIT (4u) 21 | bool GetIsPageVisible(uint pageData) 22 | { 23 | return (pageData & PAGE_VISIBLE_BIT) != 0u; 24 | } 25 | 26 | bool GetIsPageDirty(uint pageData) 27 | { 28 | return (pageData & PAGE_DIRTY_BIT) != 0u; 29 | } 30 | 31 | bool GetIsPageBacked(uint pageData) 32 | { 33 | return (pageData & PAGE_BACKED_BIT) != 0u; 34 | } 35 | 36 | void main() 37 | { 38 | const uint pageData = textureLod(Fvog_usampler2DArray(pc.textureIndex, pc.samplerIndex), vec3(v_uv, pc.texLayer), float(pc.texLevel)).x; 39 | 40 | o_color = vec4(0, 0, 0, 1); 41 | 42 | if (GetIsPageVisible(pageData)) 43 | { 44 | o_color.r = 1; 45 | } 46 | 47 | if (GetIsPageDirty(pageData)) 48 | { 49 | o_color.g = 1; 50 | } 51 | 52 | if (GetIsPageBacked(pageData)) 53 | { 54 | o_color.b = 1; 55 | } 56 | } -------------------------------------------------------------------------------- /data/shaders/shadows/vsm/VsmAllocRequest.h.glsl: -------------------------------------------------------------------------------- 1 | // #version 450 core 2 | // #extension GL_GOOGLE_include_directive : enable 3 | // #include "../../Math.h.glsl" 4 | // #include "../../GlobalUniforms.h.glsl" 5 | #ifndef VSM_ALLOC_REQUEST_H 6 | #define VSM_ALLOC_REQUEST_H 7 | 8 | struct VsmPageAllocRequest 9 | { 10 | // Address of the requester 11 | ivec3 pageTableAddress; 12 | 13 | // Unused until local lights are supported 14 | uint pageTableLevel; 15 | }; 16 | 17 | FVOG_DECLARE_STORAGE_BUFFERS(VsmPageAllocRequests) 18 | { 19 | uint count; 20 | VsmPageAllocRequest data[]; 21 | }allocRequestsBuffers[]; 22 | 23 | #define allocRequests allocRequestsBuffers[allocRequestsIndex] 24 | 25 | FVOG_DECLARE_STORAGE_BUFFERS(VsmVisiblePagesBitmask) 26 | { 27 | uint data[]; 28 | }visiblePagesBitmaskBuffers[]; 29 | 30 | #define visiblePagesBitmask visiblePagesBitmaskBuffers[visiblePagesBitmaskIndex] 31 | 32 | // layout(binding = 2, std430) restrict buffer VsmVisibleTimeTree 33 | // { 34 | // uint time[]; 35 | // }lastTimeVisible; 36 | 37 | bool TryPushAllocRequest(VsmPageAllocRequest request) 38 | { 39 | uint index = atomicAdd(allocRequests.count, 1); 40 | 41 | if (index >= allocRequests.data.length()) 42 | { 43 | atomicAdd(allocRequests.count, -1); 44 | return false; 45 | } 46 | 47 | allocRequests.data[index] = request; 48 | return true; 49 | } 50 | 51 | #endif // VSM_ALLOC_REQUEST_H -------------------------------------------------------------------------------- /data/shaders/CalibrateHdr.comp.glsl: -------------------------------------------------------------------------------- 1 | #include "Color.h.glsl" 2 | #include "Resources.h.glsl" 3 | 4 | FVOG_DECLARE_ARGUMENTS(CalibrateHdrArguments) 5 | { 6 | FVOG_UINT32 outputImageIndex; 7 | FVOG_FLOAT displayTargetNits; 8 | }; 9 | 10 | #ifndef __cplusplus 11 | void main() 12 | { 13 | const ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 14 | 15 | if (any(greaterThanEqual(gid, imageSize(Fvog_image2D(outputImageIndex))))) 16 | { 17 | return; 18 | } 19 | 20 | // HDR: Emit 10000 nits for 50% checkerboard. The other 50% should be displayTargetNits. 21 | // If they approximately match to a viewer, the max display brightness has been found. 22 | // Note 1: while the peak brightness of most monitors is <<10000 nits, some smoothly approach that peak 23 | // as the input reaches 10000, so any calibration will be somewhat wrong on those monitors. 24 | // Note 2: the test image should take up a small percentage of the screen (2-10%) as the peak brightness 25 | // generally cannot be sustained on the whole display. Therefore, scene content should aim to have peak 26 | // brightness cover only a small portion of the screen as well. 27 | 28 | vec3 color = vec3(1); // 10k nits 29 | if ((gid.x + gid.y) % 2 == 0) 30 | { 31 | color = color_PQ_OETF(vec3(displayTargetNits / 10000.0)); 32 | } 33 | 34 | imageStore(Fvog_image2D(outputImageIndex), gid, vec4(color, 1.0)); 35 | } 36 | #endif 37 | -------------------------------------------------------------------------------- /data/shaders/debug/Debug.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | #extension GL_GOOGLE_include_directive : enable 4 | 5 | #include "../Resources.h.glsl" 6 | #include "../GlobalUniforms.h.glsl" 7 | #include "../BasicTypes.h.glsl" 8 | 9 | struct Vertex 10 | { 11 | PackedVec3 position; 12 | PackedVec4 color; 13 | }; 14 | 15 | FVOG_DECLARE_STORAGE_BUFFERS(VertexBuffers) 16 | { 17 | Vertex vertices[]; 18 | }vertexBuffers[]; 19 | 20 | // This is used when the vertex buffer contains an indirect command at the beginning. 21 | FVOG_DECLARE_STORAGE_BUFFERS(GpuVertexBuffers) 22 | { 23 | DrawIndirectCommand indirect; 24 | Vertex vertices[]; 25 | }gpuVertexBuffers[]; 26 | 27 | FVOG_DECLARE_ARGUMENTS(DebugLinesPushConstants) 28 | { 29 | FVOG_UINT32 vertexBufferIndex; 30 | FVOG_UINT32 globalUniformsIndex; 31 | FVOG_UINT32 useGpuVertexBuffer; 32 | }; 33 | 34 | layout(location = 0) out vec4 v_color; 35 | 36 | void main() 37 | { 38 | Vertex vertex; 39 | if (useGpuVertexBuffer != 0) 40 | { 41 | // Hardcoded for line instances (two vertices each) 42 | vertex = gpuVertexBuffers[vertexBufferIndex].vertices[gl_VertexIndex + 2 * gl_InstanceIndex]; 43 | } 44 | else 45 | { 46 | vertex = vertexBuffers[vertexBufferIndex].vertices[gl_VertexIndex]; 47 | } 48 | v_color = PackedToVec4(vertex.color); 49 | gl_Position = perFrameUniformsBuffers[globalUniformsIndex].viewProj * vec4(PackedToVec3(vertex.position), 1.0); 50 | } -------------------------------------------------------------------------------- /data/shaders/shadows/vsm/VsmReduceBitmaskHzb.comp.glsl: -------------------------------------------------------------------------------- 1 | #include "../../Math.h.glsl" 2 | #include "../../GlobalUniforms.h.glsl" 3 | #include "VsmCommon.h.glsl" 4 | 5 | #define i_srcVsmBitmaskHzb Fvog_uimage2DArray(srcVsmBitmaskHzbIndex) 6 | #define i_dstVsmBitmaskHzb Fvog_uimage2DArray(dstVsmBitmaskHzbIndex) 7 | 8 | layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; 9 | void main() 10 | { 11 | const ivec3 gid = ivec3(gl_GlobalInvocationID.xyz); 12 | 13 | if (any(greaterThanEqual(gid, imageSize(i_dstVsmBitmaskHzb)))) 14 | { 15 | return; 16 | } 17 | 18 | uint seen = 0; 19 | 20 | // Read from virtual pages if first pass 21 | if (currentPass == 0) 22 | { 23 | const uint pageData = imageLoad(i_pageTables, gid).x; 24 | 25 | if (GetIsPageBacked(pageData) && GetIsPageVisible(pageData) && GetIsPageDirty(pageData)) 26 | { 27 | seen = 1; 28 | } 29 | } 30 | // Read from previous level of virtual HZB 31 | else 32 | { 33 | const uint bl = imageLoad(i_srcVsmBitmaskHzb, ivec3(gid.xy * 2 + ivec2(0, 0), gid.z)).x; 34 | const uint br = imageLoad(i_srcVsmBitmaskHzb, ivec3(gid.xy * 2 + ivec2(0, 1), gid.z)).x; 35 | const uint tl = imageLoad(i_srcVsmBitmaskHzb, ivec3(gid.xy * 2 + ivec2(1, 0), gid.z)).x; 36 | const uint tr = imageLoad(i_srcVsmBitmaskHzb, ivec3(gid.xy * 2 + ivec2(1, 1), gid.z)).x; 37 | 38 | seen = bl | br | tl | tr; 39 | } 40 | 41 | imageStore(i_dstVsmBitmaskHzb, gid, uvec4(seen, 0, 0, 0)); 42 | } -------------------------------------------------------------------------------- /data/shaders/shadows/vsm/VsmAllocatePages.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | 3 | #extension GL_GOOGLE_include_directive : enable 4 | 5 | #include "VsmCommon.h.glsl" 6 | #include "VsmAllocRequest.h.glsl" 7 | 8 | void AllocatePageSimple(VsmPageAllocRequest request) 9 | { 10 | // Scan for available (not visible) page (we will ignore off-screen caching for now) 11 | for (uint i = 0; i < visiblePagesBitmask.data.length(); i++) 12 | { 13 | // Find least significant (rightmost) zero bit 14 | const int lsb = findLSB(~visiblePagesBitmask.data[i]); 15 | if (lsb != -1) 16 | { 17 | const uint pageIndex = i * 32 + lsb; 18 | 19 | // TODO: use inout var to indicate start pos instead of writing to global mem 20 | visiblePagesBitmask.data[i] |= 1 << lsb; 21 | 22 | uint pageData = imageLoad(i_pageTables, request.pageTableAddress).x; 23 | pageData = SetPagePhysicalAddress(pageData, pageIndex); 24 | pageData = SetIsPageDirty(pageData, true); 25 | pageData = SetIsPageBacked(pageData, true); 26 | imageStore(i_pageTables, request.pageTableAddress, uvec4(pageData, 0, 0, 0)); 27 | return; 28 | } 29 | } 30 | 31 | // Failed to find free page, should never happen 32 | } 33 | 34 | layout(local_size_x = 1, local_size_y = 1) in; 35 | void main() 36 | { 37 | for (uint i = 0; i < allocRequests.count; i++) 38 | { 39 | VsmPageAllocRequest request = allocRequests.data[i]; 40 | AllocatePageSimple(request); 41 | } 42 | 43 | allocRequests.count = 0; 44 | } -------------------------------------------------------------------------------- /data/shaders/shadows/ShadowMain.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | #define VISBUFFER_NO_PUSH_CONSTANTS 4 | #include "../visbuffer/VisbufferCommon.h.glsl" 5 | #include "vsm/VsmCommon.h.glsl" 6 | 7 | layout(location = 0) out vec2 v_uv; 8 | layout(location = 1) out uint v_materialId; 9 | layout(location = 2) out vec3 i_objectSpacePos; 10 | 11 | void main() 12 | { 13 | const uint visibleMeshletId = (uint(gl_VertexIndex) >> MESHLET_PRIMITIVE_BITS) & MESHLET_ID_MASK; 14 | const uint meshletInstanceId = d_visibleMeshlets.indices[visibleMeshletId]; 15 | const uint primitiveId = uint(gl_VertexIndex) & MESHLET_PRIMITIVE_MASK; 16 | const MeshletInstance meshletInstance = d_meshletInstances[meshletInstanceId]; 17 | const Meshlet meshlet = d_meshlets[meshletInstance.meshletId]; 18 | const uint vertexOffset = meshlet.vertexOffset; 19 | const uint indexOffset = meshlet.indexOffset; 20 | const uint primitiveOffset = meshlet.primitiveOffset; 21 | const uint instanceId = meshletInstance.instanceId; 22 | 23 | const uint primitive = uint(d_primitives[primitiveOffset + primitiveId]); 24 | const uint index = d_indices[indexOffset + primitive]; 25 | const Vertex vertex = d_vertices[vertexOffset + index]; 26 | const vec3 position = PackedToVec3(vertex.position); 27 | const mat4 transform = d_transforms[instanceId].modelCurrent; 28 | 29 | v_materialId = d_transforms[instanceId].materialId; 30 | v_uv = PackedToVec2(vertex.uv); 31 | i_objectSpacePos = position; 32 | gl_Position = d_currentView.viewProj * transform * vec4(position, 1.0); 33 | } -------------------------------------------------------------------------------- /data/shaders/Config.shared.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_SHARED_H 2 | #define CONFIG_SHARED_H 3 | 4 | #define DEPTH_ZERO_TO_ONE 1 5 | #define REVERSE_Z 1 6 | 7 | // Adds a depth buffer that is cleared before each VSM clipmap is drawn and forces early fragment tests. 8 | // Reduces overdraw and improves perf in certain situations. 9 | #define VSM_USE_TEMP_ZBUFFER 0 10 | 11 | // If true, VSM will properly support alpha masked geometry (e.g. foliage) that requires sampling and discarding. 12 | // WARNING: because the above option enables early fragment tests, rendering will be incorrect if used with it. 13 | #define VSM_SUPPORT_ALPHA_MASKED_GEOMETRY 1 14 | 15 | // When enabled VsmShadow.frag will increment a per-texel overdraw counter. 16 | // This allows for overdraw debug visualizations at the expense of some perf. 17 | #define VSM_RENDER_OVERDRAW 1 18 | 19 | // When VSM_RENDER_OVERDRAW is enabled, clamp the colormap to this amount of overdraw. 20 | // This number should be a float. 21 | #define VSM_MAX_OVERDRAW 32.0f 22 | 23 | #if REVERSE_Z 24 | #define NEAR_DEPTH 1.0f 25 | #define FAR_DEPTH 0.0f 26 | #define Z_COMPARE_OP_FARTHER < 27 | #else 28 | #define NEAR_DEPTH 0.0f 29 | #define FAR_DEPTH 1.0f 30 | #define Z_COMPARE_OP_FARTHER > 31 | #endif 32 | 33 | #ifdef __cplusplus 34 | #include 35 | #if REVERSE_Z 36 | inline constexpr auto FVOG_COMPARE_OP_NEARER = VK_COMPARE_OP_GREATER; // 37 | #else 38 | inline constexpr auto FVOG_COMPARE_OP_NEARER = VK_COMPARE_OP_LESS; 39 | #endif 40 | #endif 41 | 42 | #endif // CONFIG_SHARED_H 43 | -------------------------------------------------------------------------------- /data/shaders/Hash.h.glsl: -------------------------------------------------------------------------------- 1 | #ifndef HASH_H 2 | #define HASH_H 3 | 4 | /////////////////////////////////////////////////////////////////////////////// 5 | // PCG family of hashes/pRNGs 6 | /////////////////////////////////////////////////////////////////////////////// 7 | 8 | uint PCG_Hash(uint seed) 9 | { 10 | uint state = seed * 747796405u + 2891336453u; 11 | uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; 12 | return (word >> 22u) ^ word; 13 | } 14 | 15 | // Used to advance the PCG state. 16 | uint PCG_RandU32(inout uint rng_state) 17 | { 18 | uint state = rng_state; 19 | rng_state = rng_state * 747796405u + 2891336453u; 20 | uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; 21 | return (word >> 22u) ^ word; 22 | } 23 | 24 | // Advances the prng state and returns the corresponding random float. 25 | float PCG_RandFloat(inout uint state, float min_, float max_) 26 | { 27 | state = PCG_RandU32(state); 28 | float f = float(state) * uintBitsToFloat(0x2f800004u); 29 | return f * (max_ - min_) + min_; 30 | } 31 | 32 | /////////////////////////////////////////////////////////////////////////////// 33 | // Miscellany 34 | // mostly sin(fract(..)) stuff that I don't approve of but am too lazy to change 35 | /////////////////////////////////////////////////////////////////////////////// 36 | 37 | float MM_Hash2(vec2 v) 38 | { 39 | return fract(1e4 * sin(17.0 * v.x + 0.1 * v.y) * (0.1 + abs(sin(13.0 * v.y + v.x)))); 40 | } 41 | 42 | float MM_Hash3(vec3 v) 43 | { 44 | return MM_Hash2(vec2(MM_Hash2(v.xy), v.z)); 45 | } 46 | 47 | #endif // HASH_H 48 | -------------------------------------------------------------------------------- /data/shaders/volumetric/GaussianBlur.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #define KERNEL_RADIUS 3 3 | 4 | layout(binding = 0) uniform sampler2D s_in; 5 | layout(binding = 0) uniform writeonly restrict image2D i_out; 6 | 7 | layout(binding = 0, std140) uniform UNIFORMS 8 | { 9 | ivec2 direction; 10 | ivec2 targetDim; 11 | }uniforms; 12 | 13 | #if KERNEL_RADIUS == 6 14 | const float weights[] = { 0.22528, 0.192187, 0.119319, 0.053904, 0.017716, 0.004235 }; // 11x11 15 | #elif KERNEL_RADIUS == 5 16 | const float weights[] = { 0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216 }; // 9x9 17 | #elif KERNEL_RADIUS == 4 18 | const float weights[] = { 0.235624, 0.201012, 0.124798, 0.056379 }; // 7x7 19 | #elif KERNEL_RADIUS == 3 20 | const float weights[] = { 0.265569, 0.226558, 0.140658 }; // 5x5 21 | #elif KERNEL_RADIUS == 2 22 | const float weights[] = { 0.369521, 0.31524 }; // 3x3 23 | #elif KERNEL_RADIUS == 1 24 | const float weights[] = { 1.0 }; // 1x1 (lol) 25 | #endif 26 | 27 | layout (local_size_x = 8, local_size_y = 8) in; 28 | void main() 29 | { 30 | const ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 31 | if (any(greaterThanEqual(gid, uniforms.targetDim))) 32 | return; 33 | vec2 uv = (vec2(gid) + 0.5) / uniforms.targetDim.xy; 34 | 35 | vec2 texel = 1.0 / uniforms.targetDim; 36 | 37 | vec4 color = textureLod(s_in, uv, 0).rgba * weights[0]; 38 | 39 | for (int i = 1; i < KERNEL_RADIUS; i++) 40 | { 41 | color += textureLod(s_in, uv + i * texel * uniforms.direction, 0).rgba * weights[i]; 42 | color += textureLod(s_in, uv - i * texel * uniforms.direction, 0).rgba * weights[i]; 43 | } 44 | 45 | imageStore(i_out, gid, color); 46 | } -------------------------------------------------------------------------------- /data/shaders/Utility.h.glsl: -------------------------------------------------------------------------------- 1 | #ifndef UTILITY_H 2 | #define UTILITY_H 3 | 4 | vec3 OctToVec3(vec2 e) 5 | { 6 | vec3 v = vec3(e.xy, 1.0 - abs(e.x) - abs(e.y)); 7 | vec2 signNotZero = vec2((v.x >= 0.0) ? +1.0 : -1.0, (v.y >= 0.0) ? +1.0 : -1.0); 8 | if (v.z < 0.0) v.xy = (1.0 - abs(v.yx)) * signNotZero; 9 | return normalize(v); 10 | } 11 | 12 | vec2 Vec3ToOct(vec3 v) 13 | { 14 | vec2 p = vec2(v.x, v.y) * (1.0 / (abs(v.x) + abs(v.y) + abs(v.z))); 15 | vec2 signNotZero = vec2((p.x >= 0.0) ? 1.0 : -1.0, (p.y >= 0.0) ? 1.0 : -1.0); 16 | return (v.z <= 0.0) ? ((1.0 - abs(vec2(p.y, p.x))) * signNotZero) : p; 17 | } 18 | 19 | // 14-vertex CCW triangle strip in [0, 1] 20 | vec3 CreateCube(in uint vertexID) 21 | { 22 | uint b = 1u << vertexID; 23 | return vec3((0x287au & b) != 0u, (0x02afu & b) != 0u, (0x31e3u & b) != 0u); 24 | } 25 | 26 | // 4-vertex CCW triangle fan in [0, 1] 27 | vec2 CreateRect(in uint vertexID) 28 | { 29 | return vec2(vertexID == 1u || vertexID == 2u, vertexID > 1u); 30 | } 31 | 32 | vec3 hsv_to_rgb(in vec3 hsv) 33 | { 34 | vec3 rgb = clamp(abs(mod(hsv.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0); 35 | return hsv.z * mix(vec3(1.0), rgb, hsv.y); 36 | } 37 | 38 | // Gets the linear luminance, spectrally weighted for human perception, of a tristimulus value 39 | float Luminance(vec3 c) 40 | { 41 | return dot(c, vec3(0.2126, 0.7152, 0.0722)); 42 | } 43 | 44 | vec3 mix4(vec3 a, vec3 b, vec3 c, vec3 d, float alpha) 45 | { 46 | if (alpha < 0.33) 47 | return mix(a, b, alpha * 3.0); 48 | if (alpha < 0.67) 49 | return mix(b, c, (alpha - 0.33) * 3.0); 50 | return mix(c, d, (alpha - 0.67) * 3.0); 51 | } 52 | 53 | #endif // UTILITY_H -------------------------------------------------------------------------------- /src/techniques/Bloom.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "PipelineManager.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | namespace Fvog 12 | { 13 | class Device; 14 | } 15 | 16 | namespace Techniques 17 | { 18 | class Bloom 19 | { 20 | public: 21 | explicit Bloom(); 22 | 23 | struct ApplyParams 24 | { 25 | // The input and output texture. 26 | Fvog::Texture& target; 27 | 28 | // A scratch texture to be used for intermediate storage. 29 | // Its dimensions should be _half_ those of the target. 30 | Fvog::Texture& scratchTexture; 31 | 32 | // Maximum number of times to downsample before upsampling. 33 | // A larger value means a wider blur. 34 | // Cannot exceed the number of mip levels in the target image. 35 | uint32_t passes; 36 | 37 | // How noticeable the effect should be. 38 | // A reasonable value for an HDR renderer would be less than 1.0/16.0 39 | float strength; 40 | 41 | // Width of the bloom upscale kernel. 42 | float width; 43 | 44 | // If true, a low-pass filter will be used on the first downsampling pass 45 | // to reduce the dynamic range and minimize temporal aliasing. 46 | bool useLowPassFilterOnFirstPass; 47 | }; 48 | 49 | void Apply(VkCommandBuffer commandBuffer, const ApplyParams& params); 50 | 51 | private: 52 | PipelineManager::ComputePipelineKey downsampleLowPassPipeline; 53 | PipelineManager::ComputePipelineKey downsamplePipeline; 54 | PipelineManager::ComputePipelineKey upsamplePipeline; 55 | }; 56 | } // namespace Techniques -------------------------------------------------------------------------------- /data/shaders/visbuffer/Visbuffer.vert.glsl: -------------------------------------------------------------------------------- 1 | #include "VisbufferCommon.h.glsl" 2 | #include "../hzb/HZBCommon.h.glsl" 3 | 4 | layout (location = 0) out flat uint o_visibleMeshletId; 5 | layout (location = 1) out flat uint o_primitiveId; 6 | layout (location = 2) out vec2 o_uv; 7 | layout (location = 3) out vec3 o_objectSpacePos; 8 | layout (location = 4) out flat uint o_materialId; 9 | 10 | void main() 11 | { 12 | const uint visibleMeshletId = (uint(gl_VertexIndex) >> MESHLET_PRIMITIVE_BITS) & MESHLET_ID_MASK; 13 | const uint meshletInstanceId = d_visibleMeshlets.indices[visibleMeshletId]; 14 | const uint primitiveId = uint(gl_VertexIndex) & MESHLET_PRIMITIVE_MASK; 15 | const MeshletInstance meshletInstance = d_meshletInstances[meshletInstanceId]; 16 | const Meshlet meshlet = d_meshlets[meshletInstance.meshletId]; 17 | const uint vertexOffset = meshlet.vertexOffset; 18 | const uint indexOffset = meshlet.indexOffset; 19 | const uint primitiveOffset = meshlet.primitiveOffset; 20 | const uint instanceId = meshletInstance.instanceId; 21 | 22 | const uint primitive = uint(d_primitives[primitiveOffset + primitiveId]); 23 | const uint index = d_indices[indexOffset + primitive]; 24 | const Vertex vertex = d_vertices[vertexOffset + index]; 25 | const vec3 position = PackedToVec3(vertex.position); 26 | const mat4 transform = d_transforms[instanceId].modelCurrent; 27 | const vec2 uv = PackedToVec2(vertex.uv); 28 | 29 | o_visibleMeshletId = visibleMeshletId; 30 | o_primitiveId = primitiveId / 3; 31 | o_uv = uv; 32 | o_objectSpacePos = position; 33 | o_materialId = d_transforms[instanceId].materialId; 34 | 35 | gl_Position = d_perFrameUniforms.viewProj * transform * vec4(position, 1.0); 36 | } -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | BUILD_TYPE: Release 11 | 12 | jobs: 13 | build_gcc: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | gccversion: [13] 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Install Dependencies 23 | run: sudo apt-get update && sudo apt-get install libgl1-mesa-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev 24 | 25 | - name: Add Repository to find newer versions of gcc 26 | run: sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test 27 | 28 | - name: Add LunarG mirror 29 | run: | 30 | wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo tee /etc/apt/trusted.gpg.d/lunarg.asc 31 | sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-jammy.list http://packages.lunarg.com/vulkan/lunarg-vulkan-jammy.list 32 | 33 | - name: Update Repos 34 | run: sudo apt-get update 35 | 36 | - name: Install Vulkan SDK 37 | run: sudo apt-get install vulkan-sdk 38 | 39 | - name: Install gcc-${{ matrix.gccversion }} 40 | run: sudo apt-get install gcc-${{ matrix.gccversion }} g++-${{ matrix.gccversion }} 41 | 42 | - name: Configure CMake with GCC-${{ matrix.gccversion }} 43 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_COMPILER=g++-${{ matrix.gccversion }} -DGLFW_BUILD_WAYLAND:BOOL=OFF 44 | env: 45 | CC: gcc-${{ matrix.gccversion }} 46 | CXX: g++-${{ matrix.gccversion }} 47 | 48 | - name: Build with gcc-${{ matrix.gccversion }} 49 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 50 | -------------------------------------------------------------------------------- /data/shaders/bloom/BloomUpsample.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | #extension GL_GOOGLE_include_directive : enable 4 | 5 | #include "BloomCommon.h.glsl" 6 | 7 | layout(local_size_x = 16, local_size_y = 16) in; 8 | void main() 9 | { 10 | ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 11 | 12 | if (any(greaterThanEqual(gid, uniforms.targetDim))) 13 | return; 14 | 15 | vec2 texel = 1.0 / uniforms.sourceDim; 16 | 17 | // center of written pixel 18 | vec2 uv = (vec2(gid) + 0.5) / uniforms.targetDim; 19 | 20 | vec4 rgba = texelFetch(s_target, gid, int(uniforms.targetLod)); 21 | 22 | vec4 blurSum = vec4(0); 23 | blurSum += textureLod(s_source, uv + vec2(-1, -1) * texel * uniforms.width, uniforms.sourceLod) * 1.0 / 16.0; 24 | blurSum += textureLod(s_source, uv + vec2(0, -1) * texel * uniforms.width, uniforms.sourceLod) * 2.0 / 16.0; 25 | blurSum += textureLod(s_source, uv + vec2(1, -1) * texel * uniforms.width, uniforms.sourceLod) * 1.0 / 16.0; 26 | blurSum += textureLod(s_source, uv + vec2(-1, 0) * texel * uniforms.width, uniforms.sourceLod) * 2.0 / 16.0; 27 | blurSum += textureLod(s_source, uv + vec2(0, 0) * texel * uniforms.width, uniforms.sourceLod) * 4.0 / 16.0; 28 | blurSum += textureLod(s_source, uv + vec2(1, 0) * texel * uniforms.width, uniforms.sourceLod) * 2.0 / 16.0; 29 | blurSum += textureLod(s_source, uv + vec2(-1, 1) * texel * uniforms.width, uniforms.sourceLod) * 1.0 / 16.0; 30 | blurSum += textureLod(s_source, uv + vec2(0, 1) * texel * uniforms.width, uniforms.sourceLod) * 2.0 / 16.0; 31 | blurSum += textureLod(s_source, uv + vec2(1, 1) * texel * uniforms.width, uniforms.sourceLod) * 1.0 / 16.0; 32 | 33 | if (bool(uniforms.isFinalPass)) 34 | { 35 | // Conserve energy 36 | rgba = mix(rgba, blurSum / uniforms.numPasses, uniforms.strength); 37 | } 38 | else 39 | { 40 | // Accumulate 41 | rgba += blurSum; 42 | } 43 | 44 | imageStore(i_target, gid, rgba); 45 | } -------------------------------------------------------------------------------- /src/techniques/AutoExposure.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | //#include 3 | //#include 4 | //#include 5 | #include "Fvog/Texture2.h" 6 | #include "Fvog/Buffer2.h" 7 | #include "Fvog/Pipeline2.h" 8 | #include "PipelineManager.h" 9 | 10 | #include "shaders/Resources.h.glsl" 11 | 12 | namespace Fvog 13 | { 14 | class Device; 15 | } 16 | 17 | namespace Techniques 18 | { 19 | class AutoExposure 20 | { 21 | public: 22 | explicit AutoExposure(); 23 | 24 | struct ApplyParams 25 | { 26 | // Image whose average luminance is to be computed 27 | Fvog::Texture& image; 28 | 29 | // Buffer containing the output exposure value 30 | Fvog::Buffer& exposureBuffer; 31 | 32 | float deltaTime; 33 | 34 | float adjustmentSpeed; 35 | 36 | float targetLuminance; 37 | 38 | float logMinLuminance; 39 | 40 | float logMaxLuminance; 41 | }; 42 | 43 | void Apply(VkCommandBuffer cmd, const ApplyParams& params); 44 | 45 | private: 46 | static constexpr uint32_t numBuckets = 128; 47 | 48 | FVOG_DECLARE_ARGUMENTS(AutoExposurePushConstants) 49 | { 50 | FVOG_UINT32 autoExposureBufferIndex; 51 | FVOG_UINT32 exposureBufferIndex; 52 | FVOG_UINT32 hdrBufferIndex; 53 | }; 54 | 55 | // Read-only data 56 | struct AutoExposureUniforms 57 | { 58 | float deltaTime; 59 | float adjustmentSpeed; 60 | float logMinLuminance; 61 | float logMaxLuminance; 62 | float targetLuminance; 63 | uint32_t numPixels; 64 | }; 65 | 66 | struct AutoExposureBufferData 67 | { 68 | AutoExposureUniforms uniforms; 69 | uint32_t histogramBuckets[numBuckets]; 70 | }; 71 | 72 | Fvog::NDeviceBuffer dataBuffer_; 73 | 74 | PipelineManager::ComputePipelineKey generateLuminanceHistogramPipeline_; 75 | PipelineManager::ComputePipelineKey resolveLuminanceHistogramPipeline_; 76 | }; 77 | } -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AlignAfterOpenBracket: DontAlign 3 | AlignConsecutiveMacros: 'true' 4 | AlignConsecutiveAssignments: 'true' 5 | AlignEscapedNewlines: Left 6 | AlignOperands: 'true' 7 | AlignTrailingComments: 'true' 8 | AllowAllArgumentsOnNextLine: 'false' 9 | AllowAllParametersOfDeclarationOnNextLine: 'false' 10 | AllowShortBlocksOnASingleLine: 'true' 11 | AllowShortCaseLabelsOnASingleLine: 'true' 12 | AllowShortFunctionsOnASingleLine: Empty 13 | AllowShortIfStatementsOnASingleLine: Never 14 | AllowShortLambdasOnASingleLine: All 15 | AllowShortLoopsOnASingleLine: 'false' 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakTemplateDeclarations: 'Yes' 18 | BinPackArguments: 'false' 19 | BinPackParameters: 'false' 20 | BreakBeforeBraces: Allman 21 | BreakConstructorInitializers: BeforeColon 22 | BreakStringLiterals: 'false' 23 | ColumnLimit: '160' 24 | CompactNamespaces: 'false' 25 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' 26 | ConstructorInitializerIndentWidth: '2' 27 | ContinuationIndentWidth: '2' 28 | Cpp11BracedListStyle: 'true' 29 | FixNamespaceComments: 'true' 30 | IndentCaseLabels: 'false' 31 | IndentPPDirectives: BeforeHash 32 | IndentWidth: '2' 33 | Language: Cpp 34 | NamespaceIndentation: All 35 | PenaltyBreakBeforeFirstCallParameter: '1' 36 | PenaltyExcessCharacter: '5' 37 | PenaltyReturnTypeOnItsOwnLine: '10000' 38 | PointerAlignment: Left 39 | SpaceAfterCStyleCast: 'false' 40 | SpaceAfterLogicalNot: 'false' 41 | SpaceAfterTemplateKeyword: 'false' 42 | SpaceBeforeAssignmentOperators: 'true' 43 | SpaceBeforeCpp11BracedList: 'false' 44 | SpaceBeforeCtorInitializerColon: 'true' 45 | SpaceBeforeInheritanceColon: 'true' 46 | SpaceBeforeParens: ControlStatements 47 | SpaceBeforeRangeBasedForLoopColon: 'true' 48 | SpaceInEmptyParentheses: 'false' 49 | SpacesInAngles: 'false' 50 | SpacesInCStyleCastParentheses: 'false' 51 | SpacesInContainerLiterals: 'false' 52 | SpacesInParentheses: 'false' 53 | SpacesInSquareBrackets: 'false' 54 | TabWidth: '2' 55 | UseTab: Never 56 | 57 | ... 58 | -------------------------------------------------------------------------------- /data/shaders/post/TonemapAndDither.shared.h: -------------------------------------------------------------------------------- 1 | #ifndef TONEMAP_AND_DITHER_H 2 | #define TONEMAP_AND_DITHER_H 3 | 4 | #include "../Resources.h.glsl" 5 | #include "../Color.h.glsl" 6 | 7 | #ifdef __cplusplus 8 | namespace shared { 9 | #endif 10 | 11 | FVOG_DECLARE_ARGUMENTS(TonemapArguments) 12 | { 13 | Texture2D sceneColor; 14 | Texture2D noise; 15 | Sampler nearestSampler; 16 | Sampler linearClampSampler; 17 | 18 | Buffer exposure; 19 | Buffer tonemapUniforms; 20 | Image2D outputImage; 21 | 22 | Texture3D tonyMcMapface; 23 | }; 24 | 25 | struct AgXMapperSettings 26 | { 27 | float saturation; 28 | float linear; 29 | float compression; 30 | }; 31 | 32 | struct GTMapperSettings 33 | { 34 | float contrast; 35 | float startOfLinearSection; 36 | float lengthOfLinearSection; 37 | float toeCurviness; 38 | float toeFloor; 39 | }; 40 | 41 | struct TonemapUniforms 42 | { 43 | #ifdef __cplusplus 44 | TonemapUniforms() 45 | : tonemapper(0), 46 | enableDithering(true), 47 | quantizeBits(8), 48 | maxDisplayNits(200), 49 | tonemapOutputColorSpace(COLOR_SPACE_sRGB_NONLINEAR), 50 | agx(AgXMapperSettings{ 51 | .saturation = 1.0f, 52 | .linear = 0.10f, 53 | .compression = 0.15f, 54 | }), 55 | gt(GTMapperSettings{ 56 | .contrast = 1.00f, 57 | .startOfLinearSection = 0.22f, 58 | .lengthOfLinearSection = 0.40f, 59 | .toeCurviness = 1.33f, 60 | .toeFloor = 0.00f, 61 | }) 62 | { 63 | } 64 | #endif 65 | 66 | FVOG_UINT32 tonemapper; // 0 = AgX, 1 = Tony, 2 = Linear, 3 = GT 67 | FVOG_UINT32 enableDithering; 68 | FVOG_UINT32 quantizeBits; 69 | FVOG_FLOAT maxDisplayNits; 70 | FVOG_UINT32 shadingInternalColorSpace; // Tonemap input color space 71 | FVOG_UINT32 tonemapOutputColorSpace; 72 | AgXMapperSettings agx; 73 | GTMapperSettings gt; 74 | }; 75 | 76 | #ifdef __cplusplus 77 | } 78 | #endif 79 | 80 | #endif // TONEMAP_AND_DITHER_H -------------------------------------------------------------------------------- /src/debug/ForwardRenderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Fvog/Buffer2.h" 3 | #include "Fvog/Pipeline2.h" 4 | #include "Fvog/Shader2.h" 5 | #include "Fvog/Texture2.h" 6 | #include "PipelineManager.h" 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | 14 | namespace Fvog 15 | { 16 | class Device; 17 | } 18 | 19 | namespace Debug 20 | { 21 | class ForwardRenderer 22 | { 23 | public: 24 | explicit ForwardRenderer(); 25 | 26 | ForwardRenderer(const ForwardRenderer&) = delete; 27 | ForwardRenderer& operator=(const ForwardRenderer&) = delete; 28 | 29 | ForwardRenderer(ForwardRenderer&&) noexcept = default; 30 | ForwardRenderer& operator=(ForwardRenderer&&) noexcept = default; 31 | 32 | struct Drawable 33 | { 34 | VkDeviceAddress vertexBufferAddress{}; 35 | Fvog::Buffer* indexBuffer{}; 36 | VkDeviceSize indexBufferOffset{}; 37 | uint32_t indexCount; 38 | glm::mat4 worldFromObject{}; 39 | uint32_t materialId{}; 40 | }; 41 | 42 | struct ViewParams 43 | { 44 | glm::mat4 clipFromWorld; // viewProj 45 | }; 46 | 47 | void PushDraw(const Drawable& draw); 48 | void FlushAndRender(VkCommandBuffer commandBuffer, const ViewParams& view, const Fvog::TextureView& renderTarget, Fvog::Buffer& materialBuffer); 49 | 50 | private: 51 | struct Uniforms 52 | { 53 | glm::mat4 clipFromWorld; 54 | glm::mat4 worldFromObject; 55 | VkDeviceAddress vertexBufferAddress; 56 | uint32_t materialId; 57 | uint32_t materialBufferIndex; 58 | uint32_t samplerIndex; 59 | }; 60 | 61 | // Pipeline is recreated if last RT format doesn't match 62 | Fvog::Format lastRenderTargetFormat = Fvog::Format::UNDEFINED; 63 | PipelineManager::GraphicsPipelineKey pipeline_; 64 | std::optional depthTexture_; 65 | std::optional> uniformBuffer_; 66 | 67 | std::vector draws_; 68 | }; 69 | } // namespace Debug -------------------------------------------------------------------------------- /src/Scene.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Fvog/Texture2.h" 3 | #include "Fvog/Device.h" 4 | 5 | #include "Renderables.h" 6 | 7 | #include "shaders/ShadeDeferredPbr.h.glsl" 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | class FrogRenderer2; 17 | 18 | namespace Utility 19 | { 20 | struct LoadModelResultA; 21 | } 22 | 23 | namespace Scene 24 | { 25 | struct MeshIdAndMaterialId 26 | { 27 | Render::MeshID meshId; 28 | Render::MaterialID materialId; 29 | }; 30 | 31 | struct Node 32 | { 33 | std::string name; 34 | 35 | glm::vec3 translation; 36 | glm::quat rotation; 37 | glm::vec3 scale; 38 | 39 | [[nodiscard]] glm::mat4 CalcLocalTransform() const noexcept; 40 | 41 | // Horrible interface 42 | void DeleteLight(FrogRenderer2& renderer); 43 | 44 | glm::mat4 globalTransform; 45 | //glm::vec3 globalAabbMin; 46 | //glm::vec3 globalAabbMax; 47 | 48 | // Relationship 49 | Node* parent = nullptr; 50 | std::vector children; 51 | bool isDirty = false; // True if transform OR light data changed 52 | bool isDescendantDirty = false; 53 | 54 | // Also tells parents that a descendant is dirty. 55 | void MarkDirty(); 56 | 57 | std::vector meshes; 58 | Render::LightID lightId; 59 | GpuLight light; // Only contains valid data if lightId is not null 60 | }; 61 | 62 | struct SceneMeshlet 63 | { 64 | void Import(FrogRenderer2& renderer, Utility::LoadModelResultA loadModelResult); 65 | 66 | // Epic interface 67 | void CalcUpdatedData(FrogRenderer2& renderer) const; 68 | 69 | std::vector rootNodes; 70 | std::vector> nodes; 71 | 72 | std::vector images; 73 | std::vector meshGeometryIds; 74 | std::vector meshIds; 75 | std::vector lightIds; 76 | std::vector materialIds; 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-Debug", 5 | "generator": "Ninja", 6 | "configurationType": "Debug", 7 | "inheritEnvironments": [ "msvc_x64_x64" ], 8 | "buildRoot": "${projectDir}\\out\\build\\${name}", 9 | "installRoot": "${projectDir}\\out\\install\\${name}", 10 | "cmakeCommandArgs": "", 11 | "buildCommandArgs": "", 12 | "ctestCommandArgs": "", 13 | "variables": [ 14 | { 15 | "name": "FROGRENDER_FSR2_ENABLE", 16 | "value": "True", 17 | "type": "BOOL" 18 | } 19 | ] 20 | }, 21 | { 22 | "name": "x64-RelWithDebInfo", 23 | "generator": "Ninja", 24 | "configurationType": "RelWithDebInfo", 25 | "buildRoot": "${projectDir}\\out\\build\\${name}", 26 | "installRoot": "${projectDir}\\out\\install\\${name}", 27 | "cmakeCommandArgs": "", 28 | "buildCommandArgs": "", 29 | "ctestCommandArgs": "", 30 | "inheritEnvironments": [ "msvc_x64_x64" ], 31 | "variables": [ 32 | { 33 | "name": "FROGRENDER_FSR2_ENABLE", 34 | "value": "True", 35 | "type": "BOOL" 36 | }, 37 | { 38 | "name": "TRACY_ENABLE", 39 | "value": "True", 40 | "type": "BOOL" 41 | } 42 | ] 43 | }, 44 | { 45 | "name": "x64-Release", 46 | "generator": "Ninja", 47 | "configurationType": "Release", 48 | "buildRoot": "${projectDir}\\out\\build\\${name}", 49 | "installRoot": "${projectDir}\\out\\install\\${name}", 50 | "cmakeCommandArgs": "", 51 | "buildCommandArgs": "", 52 | "ctestCommandArgs": "", 53 | "inheritEnvironments": [ "msvc_x64_x64" ], 54 | "variables": [ 55 | { 56 | "name": "FROGRENDER_FSR2_ENABLE", 57 | "value": "True", 58 | "type": "BOOL" 59 | }, 60 | { 61 | "name": "TRACY_ENABLE", 62 | "value": "False", 63 | "type": "BOOL" 64 | } 65 | ] 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /src/techniques/ao/RayTracedAO.cpp: -------------------------------------------------------------------------------- 1 | #include "RayTracedAO.h" 2 | #include "Application.h" 3 | #include "Fvog/Rendering2.h" 4 | #include "../../RendererUtilities.h" 5 | #include "Fvog/AccelerationStructure.h" 6 | #include "shaders/ao/rtao/RayTracedAO.comp.glsl" 7 | 8 | #include 9 | #include 10 | 11 | namespace Techniques 12 | { 13 | RayTracedAO::RayTracedAO() 14 | { 15 | rtaoPipeline_ = GetPipelineManager().EnqueueCompileComputePipeline({ 16 | .name = "Ray Traced AO", 17 | .shaderModuleInfo = {.path = GetShaderDirectory() / "ao/rtao/RayTracedAO.comp.glsl"}, 18 | }); 19 | } 20 | 21 | Fvog::Texture& RayTracedAO::ComputeAO(VkCommandBuffer commandBuffer, const ComputeParams& params) 22 | { 23 | assert(params.tlas); 24 | assert(params.inputDepth); 25 | 26 | auto ctx = Fvog::Context(commandBuffer); 27 | 28 | if (!aoTexture_ || Fvog::Extent2D(aoTexture_->GetCreateInfo().extent) != params.outputSize) 29 | { 30 | aoTexture_ = Fvog::CreateTexture2D(params.outputSize, Fvog::Format::R16_UNORM, Fvog::TextureUsage::GENERAL, "AO Texture"); 31 | } 32 | 33 | ctx.ImageBarrierDiscard(aoTexture_.value(), VK_IMAGE_LAYOUT_GENERAL); 34 | auto marker = ctx.MakeScopedDebugMarker("Ray Traced AO"); 35 | ctx.BindComputePipeline(rtaoPipeline_.GetPipeline()); 36 | ctx.SetPushConstants(RtaoArguments{ 37 | .tlasAddress = params.tlas->GetAddress(), 38 | .gDepth = params.inputDepth->ImageView().GetTexture2D(), 39 | .gNormalAndFaceNormal = params.inputNormalAndFaceNormal->ImageView().GetTexture2D(), 40 | .outputAo = aoTexture_.value().ImageView().GetImage2D(), 41 | .world_from_clip = params.world_from_clip, 42 | .numRays = params.numRays, 43 | .rayLength = params.rayLength, 44 | .frameNumber = params.frameNumber, 45 | }); 46 | ctx.DispatchInvocations(params.outputSize.width, params.outputSize.height, 1); 47 | ctx.Barrier(); 48 | 49 | return aoTexture_.value(); 50 | } 51 | } // namespace Techniques 52 | -------------------------------------------------------------------------------- /data/shaders/volumetric/MarchVolume.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #extension GL_GOOGLE_include_directive : enable 3 | #include "Common.h" 4 | 5 | layout(binding = 0) uniform sampler3D s_colorDensityVolume; 6 | layout(binding = 0) uniform writeonly image3D i_inScatteringTransmittanceVolume; 7 | 8 | layout(local_size_x = 16, local_size_y = 16) in; 9 | void main() 10 | { 11 | ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 12 | ivec3 targetDim = imageSize(i_inScatteringTransmittanceVolume); 13 | if (any(greaterThanEqual(gid, targetDim.xy))) 14 | return; 15 | vec2 uv = (vec2(gid) + 0.5) / targetDim.xy; 16 | 17 | vec3 texel = 1.0 / targetDim; 18 | 19 | vec3 inScatteringAccum = vec3(0.0); 20 | float densityAccum = 0.0; 21 | vec3 pPrev = uniforms.viewPos; 22 | for (int i = 0; i < targetDim.z; i++) 23 | { 24 | // uvw is the current voxel in unorm (UV) space. One half is added to i to get the center of the voxel as usual. 25 | vec3 uvw = vec3(uv, (i + 0.5) * texel.z); 26 | 27 | // Starting with linear depth, square it to bias precision towards the viewer. 28 | // Then, invert the depth as though it were multiplied by the volume projection. 29 | float zInv = InvertDepthZO(uvw.z * uvw.z, uniforms.volumeNearPlane, uniforms.volumeFarPlane); 30 | 31 | // Unproject the inverted depth to get the world position of this froxel. 32 | vec3 pCur = UnprojectUVZO(zInv, uv, uniforms.invViewProjVolume); 33 | 34 | // The step size is not constant, so we calculate it here. 35 | float stepSize = distance(pPrev, pCur); 36 | pPrev = pCur; 37 | 38 | vec4 froxelInfo = textureLod(s_colorDensityVolume, uvw, 0); 39 | vec3 froxelLight = froxelInfo.rgb; 40 | float froxelDensity = froxelInfo.a; 41 | 42 | densityAccum += froxelDensity * stepSize; 43 | float transmittance = beer(densityAccum); 44 | 45 | // 10*stepSize makes the accumulation independent of volume size and depth distribution 46 | inScatteringAccum += 10 * stepSize * transmittance * froxelLight; 47 | 48 | imageStore(i_inScatteringTransmittanceVolume, ivec3(gid, i), vec4(inScatteringAccum, transmittance)); 49 | } 50 | } -------------------------------------------------------------------------------- /src/RendererUtilities.cpp: -------------------------------------------------------------------------------- 1 | #include "RendererUtilities.h" 2 | 3 | #include "Fvog/Device.h" 4 | #include "Fvog/Shader2.h" 5 | #include "Fvog/Texture2.h" 6 | 7 | #define STB_INCLUDE_IMPLEMENTATION 8 | #define STB_INCLUDE_LINE_GLSL 9 | #include "stb_include.h" 10 | #include "stb_image.h" 11 | 12 | Fvog::Texture LoadTextureShrimple(const std::filesystem::path& path) 13 | { 14 | int x{}; 15 | int y{}; 16 | auto* pixels = stbi_load(path.string().c_str(), &x, &y, nullptr, 4); 17 | if (!pixels) 18 | { 19 | throw std::runtime_error("Texture not found"); 20 | } 21 | auto texture = Fvog::CreateTexture2D({(uint32_t)x, (uint32_t)y}, Fvog::Format::R8G8B8A8_SRGB, Fvog::TextureUsage::READ_ONLY, path.string()); 22 | texture.UpdateImageSLOW({ 23 | .extent = texture.GetCreateInfo().extent, 24 | .data = pixels, 25 | }); 26 | stbi_image_free(pixels); 27 | return texture; 28 | } 29 | 30 | // Experimental includer that uses glslang includer 31 | //Fvog::Shader LoadShaderWithIncludes2(Fvog::Device& device, Fvog::PipelineStage stage, const std::filesystem::path& path) 32 | //{ 33 | // if (!std::filesystem::exists(path) || std::filesystem::is_directory(path)) 34 | // { 35 | // throw std::runtime_error("Path does not refer to a file"); 36 | // } 37 | // return Fvog::Shader(device.device_, stage, path, path.filename().string().c_str()); 38 | //} 39 | 40 | Fvog::Shader LoadShaderWithIncludes2(Fvog::PipelineStage stage, const std::filesystem::path& path) 41 | { 42 | if (!std::filesystem::exists(path) || std::filesystem::is_directory(path)) 43 | { 44 | throw std::runtime_error("Path does not refer to a file"); 45 | } 46 | 47 | auto pathStr = path.string(); 48 | auto parentPathStr = path.parent_path().string(); 49 | char error[256]{}; 50 | auto processedSource = std::unique_ptr(stb_include_file(pathStr.c_str(), nullptr, parentPathStr.c_str(), error)); 51 | if (!processedSource) 52 | { 53 | throw std::runtime_error("Failed to process includes"); 54 | } 55 | return Fvog::Shader(stage, std::string_view(processedSource.get()), path.filename().string().c_str()); 56 | } 57 | -------------------------------------------------------------------------------- /src/Fvog/Shader2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "BasicTypes2.h" 9 | 10 | namespace Fvog 11 | { 12 | namespace detail 13 | { 14 | struct ShaderCompileInfo 15 | { 16 | std::vector binarySpv; 17 | Extent3D workgroupSize_{}; 18 | }; 19 | } 20 | 21 | enum class PipelineStage 22 | { 23 | VERTEX_SHADER, 24 | FRAGMENT_SHADER, 25 | COMPUTE_SHADER, 26 | RAYGEN_SHADER, 27 | MISS_SHADER, 28 | CLOSEST_HIT_SHADER, 29 | ANY_HIT_SHADER, 30 | INTERSECTION_SHADER, 31 | }; 32 | 33 | /// @brief A shader object to be used in one or more GraphicsPipeline or ComputePipeline objects 34 | class Shader 35 | { 36 | public: 37 | /// @brief Constructs the shader 38 | /// @param stage A pipeline stage 39 | /// @param source A GLSL source string 40 | /// @throws ShaderCompilationException if the shader is malformed 41 | // Already-processed source constructor 42 | explicit Shader(PipelineStage stage, std::string_view source, std::string name = {}); 43 | 44 | // Path constructor (uses glslang include handling) 45 | explicit Shader(PipelineStage stage, const std::filesystem::path& path, std::string name = {}); 46 | Shader(const Shader&) = delete; 47 | Shader(Shader&& old) noexcept; 48 | Shader& operator=(const Shader&) = delete; 49 | Shader& operator=(Shader&& old) noexcept; 50 | ~Shader(); 51 | 52 | /// @brief Gets the handle of the underlying OpenGL shader object 53 | /// @return The shader 54 | [[nodiscard]] VkShaderModule Handle() const 55 | { 56 | return shaderModule_; 57 | } 58 | 59 | [[nodiscard]] Extent3D WorkgroupSize() const 60 | { 61 | return workgroupSize_; 62 | } 63 | 64 | [[nodiscard]] PipelineStage GetPipelineStage() const 65 | { 66 | return stage_; 67 | } 68 | 69 | private: 70 | void Initialize(const detail::ShaderCompileInfo& info); 71 | 72 | std::string name_; 73 | PipelineStage stage_{}; 74 | VkShaderModule shaderModule_; 75 | Extent3D workgroupSize_{}; 76 | }; 77 | } -------------------------------------------------------------------------------- /src/SceneLoader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Renderables.h" 3 | #include "shaders/ShadeDeferredPbr.h.glsl" 4 | 5 | #include "Fvog/Texture2.h" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace Utility 14 | { 15 | struct MeshGeometry 16 | { 17 | std::pmr::vector meshlets; 18 | std::pmr::vector vertices; 19 | std::pmr::vector remappedIndices; // meshletIndices 20 | std::pmr::vector primitives; 21 | std::pmr::vector originalIndices; 22 | }; 23 | 24 | struct LoadModelNode 25 | { 26 | std::string name; 27 | 28 | glm::vec3 translation; 29 | glm::quat rotation; 30 | glm::vec3 scale; 31 | 32 | [[nodiscard]] glm::mat4 CalcLocalTransform() const noexcept; 33 | 34 | // Relationship 35 | std::vector children; 36 | 37 | // A list of meshlets (minus their transform ID), which are stored in the scene 38 | struct MeshIndices 39 | { 40 | size_t meshIndex; 41 | std::optional materialIndex; 42 | }; 43 | std::vector meshes; 44 | std::optional light; // TODO: hold a light without position/direction type safety 45 | }; 46 | 47 | struct LoadModelResultA 48 | { 49 | // These nodes are a different type that refer to not-yet-uploaded 50 | // resources. These nodes contain indices into the various other 51 | // buffers this struct holds, and should be trivially convertible to 52 | // actual scene nodes. 53 | std::pmr::vector rootNodes; 54 | std::pmr::vector> nodes; 55 | 56 | std::pmr::vector meshGeometries; 57 | std::pmr::vector materials; 58 | std::vector images; 59 | }; 60 | 61 | // TODO: maybe customizeable (not recommended though) 62 | inline constexpr auto maxMeshletIndices = 64u; 63 | inline constexpr auto maxMeshletPrimitives = 64u; 64 | inline constexpr auto meshletConeWeight = 0.0f; 65 | 66 | [[nodiscard]] LoadModelResultA LoadModelFromFile( 67 | const std::filesystem::path& fileName, 68 | const glm::mat4& rootTransform, 69 | bool skipMaterials = false); 70 | } -------------------------------------------------------------------------------- /data/shaders/volumetric/Common.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_H 2 | #define COMMON_H 3 | 4 | // unproject with zero-origin convention [0, 1] 5 | vec3 UnprojectUVZO(float depth, vec2 uv, mat4 invXProj) 6 | { 7 | vec4 clipSpacePosition = vec4(uv * 2.0 - 1.0, depth, 1.0); // [0, 1] -> [-1, 1] 8 | 9 | vec4 worldSpacePosition = invXProj * clipSpacePosition; 10 | worldSpacePosition /= worldSpacePosition.w; 11 | 12 | return worldSpacePosition.xyz; 13 | } 14 | 15 | // unproject with GL convention [-1, 1] 16 | vec3 UnprojectUVGL(float depth, vec2 uv, mat4 invXProj) 17 | { 18 | depth = depth * 2.0 - 1.0; // [0, 1] -> [-1, 1] 19 | return UnprojectUVZO(depth, uv, invXProj); 20 | } 21 | 22 | float LinearizeDepthZO(float nonlinearZ, float zn, float zf) 23 | { 24 | return zn / (zf + nonlinearZ * (zn - zf)); 25 | } 26 | 27 | // the inverse of LinearizeDepthZO 28 | float InvertDepthZO(float linearZ, float zn, float zf) 29 | { 30 | return (zn - zf * linearZ) / (linearZ * (zn - zf)); 31 | } 32 | 33 | layout(binding = 0, std140) uniform UNIFORMS 34 | { 35 | vec3 viewPos; 36 | float time; 37 | mat4 invViewProjScene; 38 | mat4 viewProjVolume; 39 | mat4 invViewProjVolume; 40 | mat4 sunViewProj; 41 | vec3 sunDir; 42 | float volumeNearPlane; 43 | float volumeFarPlane; 44 | uint useScatteringTexture; 45 | float anisotropyG; 46 | float noiseOffsetScale; 47 | uint frog; 48 | float groundFogDensity; 49 | vec3 sunColor; 50 | }uniforms; 51 | 52 | #define M_PI 3.1415926 53 | 54 | // Henyey-Greenstein phase function for anisotropic in-scattering 55 | float phaseHG(float g, float cosTheta) 56 | { 57 | return (1.0 - g * g) / (4.0 * M_PI * pow(1.0 + g * g - 2.0 * g * cosTheta, 1.5)); 58 | } 59 | 60 | // Schlick's efficient approximation of HG 61 | float phaseSchlick(float k, float cosTheta) 62 | { 63 | float denom = 1.0 - k * cosTheta; 64 | return (1.0 - k * k) / (4.0 * M_PI * denom * denom); 65 | } 66 | 67 | // Conversion of HG's G parameter to Schlick's K parameter 68 | float gToK(float g) 69 | { 70 | return clamp(1.55 * g - 0.55 * g * g * g, -0.999, 0.999); 71 | } 72 | 73 | // Beer-Lambert law 74 | float beer(float d) 75 | { 76 | return exp(-d); 77 | } 78 | 79 | // Powder scattering effect for large volumes (darkens edges, used with beer) 80 | float powder(float d) 81 | { 82 | return 1.0 - exp(-d * 2.0); 83 | } 84 | 85 | #endif -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * TODO rendering: 4 | * Core: 5 | * [X] glTF loader 6 | * [X] FSR 2 7 | * [X] PBR punctual lights 8 | * [ ] Skinned animation 9 | * [ ] Transparency 10 | * 11 | * Low-level 12 | * [X] Visibility buffer 13 | * [X] Frustum culling 14 | * [X] Hi-z occlusion culling 15 | * [ ] Clustered light culling 16 | * [-] Raster occlusion culling 17 | * [X] Multi-view 18 | * [X] Triangle culling: https://www.slideshare.net/gwihlidal/optimizing-the-graphics-pipeline-with-compute-gdc-2016 19 | * [X] Back-facing 20 | * [X] Zero-area 21 | * [X] Small primitive (doesn't overlap pixel) 22 | * [X] Frustum 23 | * 24 | * Atmosphere 25 | * [ ] Sky 26 | * [ ] Volumetric fog 27 | * [ ] Clouds 28 | * 29 | * Effects: 30 | * [X] Bloom 31 | * 32 | * Resolve: 33 | * [X] Auto exposure 34 | * [ ] Auto whitepoint 35 | * [ ] Local tonemapping 36 | * [ ] Purkinje shift 37 | * 38 | * Tryhard: 39 | * [X] BVH builder 40 | * [X] Path tracer 41 | * [ ] DDGI 42 | * [ ] Surfel GI 43 | * 44 | * TODO UI: 45 | * [X] Render into an ImGui window 46 | * [X] Install a font + header from here: https://github.com/juliettef/IconFontCppHeaders 47 | * [ ] Figure out an epic layout 48 | * [X] Config file so stuff doesn't reset every time 49 | * [ ] Model browser 50 | * [ ] Command console 51 | * 52 | * Debugging: 53 | * [ ] Meshlet color view 54 | * [ ] Meshlet HZB LoD view 55 | * [ ] Frustum visualization for other views 56 | * [/] Texture viewer (basically the one from RenderDoc): 57 | * [ ] Register textures to list of viewable textures in GUI (perhaps just a vector of Texture*) 58 | * [ ] Selector for range of displayable values 59 | * [ ] Toggles for visible channels 60 | * [ ] Selectors for mip level and array layer, if applicable 61 | * [ ] Option to tint the window/image alpha to see the window behind it (i.e., overlay the image onto the main viewport) 62 | * [ ] Toggle to scale the image to the viewport (would be helpful to view square textures like the hzb properly overlaid on the scene) 63 | * [ ] Standard scroll zoom and drag controls 64 | */ 65 | 66 | #include 67 | #include 68 | #include 69 | 70 | #define VMA_IMPLEMENTATION 71 | #include 72 | 73 | #include "FrogRenderer2.h" 74 | 75 | int main() 76 | { 77 | auto app = FrogRenderer2({ 78 | .name = "FrogRender", 79 | }); 80 | app.Run(); 81 | 82 | return 0; 83 | } 84 | -------------------------------------------------------------------------------- /data/shaders/shadows/vsm/VsmMarkVisiblePages.comp.glsl: -------------------------------------------------------------------------------- 1 | #extension GL_KHR_shader_subgroup_ballot : require 2 | #extension GL_KHR_shader_subgroup_basic : require 3 | #extension GL_KHR_shader_subgroup_vote : require 4 | 5 | #include "../../Config.shared.h" 6 | #include "VsmCommon.h.glsl" 7 | #include "VsmAllocRequest.h.glsl" 8 | 9 | #define s_gDepth FvogGetSampledImage(texture2D, gDepthIndex) 10 | 11 | layout(local_size_x = 8, local_size_y = 8) in; 12 | void main() 13 | { 14 | const ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 15 | 16 | if (any(greaterThanEqual(gid, textureSize(s_gDepth, 0)))) 17 | { 18 | return; 19 | } 20 | 21 | const float depthSample = texelFetch(s_gDepth, gid, 0).x; 22 | 23 | if (depthSample == FAR_DEPTH) 24 | { 25 | return; 26 | } 27 | 28 | PageAddressInfo addr = GetClipmapPageFromDepth(depthSample, gid, textureSize(s_gDepth, 0)); 29 | 30 | // Waterfall- only do the imageAtomicOr once per unique addr.pageAddress in the subgroup. 31 | // This provides a ~10x speedup for tested scenes on NV. 32 | 33 | // This boolean is used to avoid hitting an apparent driver bug on NV. When break is used instead, 34 | // pages will be occasionally missed, creating a flickering artifact. 35 | bool loop = true; 36 | while (loop) 37 | { 38 | ivec3 firstLanePageAddress = subgroupBroadcastFirst(addr.pageAddress); 39 | if (addr.pageAddress == firstLanePageAddress) 40 | { 41 | if (subgroupElect()) 42 | { 43 | const uint pageData = imageAtomicOr(i_pageTables, addr.pageAddress, PAGE_VISIBLE_BIT); 44 | 45 | if ((vsmUniforms.debugFlags & VSM_FORCE_DIRTY_VISIBLE_PAGES) != 0) 46 | { 47 | imageAtomicOr(i_pageTables, addr.pageAddress, PAGE_DIRTY_BIT); 48 | } 49 | 50 | if (!GetIsPageVisible(pageData)) 51 | { 52 | if (GetIsPageBacked(pageData)) 53 | { 54 | // Mark visible in bitmask so allocator doesn't overwrite 55 | const uint physicalAddress = GetPagePhysicalAddress(pageData); 56 | atomicOr(visiblePagesBitmask.data[physicalAddress / 32], 1 << (physicalAddress % 32)); 57 | } 58 | else // Page fault 59 | { 60 | VsmPageAllocRequest request; 61 | request.pageTableAddress = addr.pageAddress; 62 | request.pageTableLevel = 0; // TODO: change for lower-res mipmaps 63 | TryPushAllocRequest(request); 64 | } 65 | } 66 | } 67 | //break; 68 | loop = false; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /data/shaders/auto_exposure/ResolveLuminanceHistogram.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #extension GL_GOOGLE_include_directive : enable 3 | #extension GL_KHR_shader_subgroup_basic : require 4 | #extension GL_KHR_shader_subgroup_arithmetic : require 5 | 6 | #include "AutoExposureCommon.h.glsl" 7 | #include "../Math.h.glsl" 8 | 9 | // gl_SubgroupSize is not a constant, so assume subgroup size is 1 (worst case) 10 | // Most likely, we will only use 1/32 of this storage 11 | shared uint sh_scratch[NUM_BUCKETS]; 12 | 13 | layout (local_size_x = NUM_BUCKETS) in; 14 | void main() 15 | { 16 | const uint i = gl_GlobalInvocationID.x; 17 | 18 | const uint currentBucketCount = d_autoExposure.histogramBuckets[i]; 19 | 20 | { 21 | // Weighted by the bucket index 22 | const uint firstReduce = subgroupAdd(currentBucketCount * i); 23 | 24 | // Do a cheeky reduction here to skip an iteration of the loop 25 | if (subgroupElect()) 26 | { 27 | sh_scratch[gl_SubgroupID] = firstReduce; 28 | } 29 | } 30 | 31 | // Reset bucket for next frame 32 | d_autoExposure.histogramBuckets[i] = 0; 33 | 34 | barrier(); 35 | 36 | // Most likely, just one iteration of this loop will run. 37 | // E.g., 128 buckets / 32 lanes in a subgroup = 4 work items left 38 | for (uint work = NUM_BUCKETS / gl_SubgroupSize; work > 0; work /= gl_SubgroupSize) 39 | { 40 | uint localSum = 0; 41 | if (i < work) 42 | { 43 | localSum = subgroupAdd(sh_scratch[i]); 44 | } 45 | 46 | barrier(); 47 | 48 | if (subgroupElect()) 49 | { 50 | sh_scratch[gl_SubgroupID] = localSum; 51 | } 52 | 53 | barrier(); 54 | } 55 | 56 | if (gl_GlobalInvocationID.x == 0) 57 | { 58 | // Since our global ID is zero, we know that this is the number of black (or nearly black) pixels on the screen 59 | const uint numBlackPixels = currentBucketCount; 60 | const float log2MeanLuminance = Remap(float(sh_scratch[0]) / max(float(d_autoExposure.numPixels) - numBlackPixels, 1.0), 1.0, NUM_BUCKETS, d_autoExposure.logMinLuminance, d_autoExposure.logMaxLuminance); 61 | // Lerp in linear space (lerping in log space seems to cause visually nonuniform adaptation in different lighting conditions) 62 | const float exposureTarget = log2(d_autoExposure.targetLuminance / exp2(log2MeanLuminance)); 63 | // Framerate-independent exponential decay 64 | const float alpha = clamp(1 - exp(-d_autoExposure.deltaTime * d_autoExposure.adjustmentSpeed), 0.0, 1.0); 65 | d_exposureBuffer.exposure = mix(d_exposureBuffer.exposure, exposureTarget, alpha); 66 | } 67 | } -------------------------------------------------------------------------------- /data/shaders/debug/DebugCommon.h.glsl: -------------------------------------------------------------------------------- 1 | //? #version 450 2 | #ifndef DEBUG_COMMON_H 3 | #define DEBUG_COMMON_H 4 | 5 | #include "../Resources.h.glsl" 6 | #include "../BasicTypes.h.glsl" 7 | 8 | struct DebugAabb 9 | { 10 | PackedVec3 center; 11 | PackedVec3 extent; 12 | PackedVec4 color; 13 | }; 14 | 15 | struct DebugRect 16 | { 17 | PackedVec2 minOffset; 18 | PackedVec2 maxOffset; 19 | PackedVec4 color; 20 | float depth; 21 | }; 22 | 23 | struct DebugLine 24 | { 25 | PackedVec3 aPosition; 26 | PackedVec4 aColor; 27 | PackedVec3 bPosition; 28 | PackedVec4 bColor; 29 | }; 30 | 31 | FVOG_DECLARE_STORAGE_BUFFERS(restrict DebugAabbBuffer) 32 | { 33 | DrawIndirectCommand drawCommand; 34 | DebugAabb aabbs[]; 35 | } debugAabbBuffers[]; 36 | 37 | FVOG_DECLARE_STORAGE_BUFFERS(restrict DebugRectBuffer) 38 | { 39 | DrawIndirectCommand drawCommand; 40 | DebugRect rects[]; 41 | } debugRectBuffers[]; 42 | 43 | FVOG_DECLARE_STORAGE_BUFFERS(restrict DebugLineBuffer) 44 | { 45 | DrawIndirectCommand drawCommand; 46 | DebugLine lines[]; 47 | } debugLineBuffers[]; 48 | 49 | // World-space box 50 | bool TryPushDebugAabb(uint bufferIndex, DebugAabb box) 51 | { 52 | uint index = atomicAdd(debugAabbBuffers[bufferIndex].drawCommand.instanceCount, 1); 53 | 54 | // Check if buffer is full 55 | if (index >= debugAabbBuffers[bufferIndex].aabbs.length()) 56 | { 57 | atomicAdd(debugAabbBuffers[bufferIndex].drawCommand.instanceCount, -1); 58 | return false; 59 | } 60 | 61 | debugAabbBuffers[bufferIndex].aabbs[index] = box; 62 | return true; 63 | } 64 | 65 | // UV-space rect 66 | bool TryPushDebugRect(uint bufferIndex, DebugRect rect) 67 | { 68 | uint index = atomicAdd(debugRectBuffers[bufferIndex].drawCommand.instanceCount, 1); 69 | 70 | // Check if buffer is full 71 | if (index >= debugRectBuffers[bufferIndex].rects.length()) 72 | { 73 | atomicAdd(debugRectBuffers[bufferIndex].drawCommand.instanceCount, -1); 74 | return false; 75 | } 76 | 77 | debugRectBuffers[bufferIndex].rects[index] = rect; 78 | return true; 79 | } 80 | 81 | // World-space line 82 | bool TryPushDebugLine(Buffer lineBuffer, DebugLine line) 83 | { 84 | uint index = atomicAdd(debugLineBuffers[lineBuffer.bufIdx].drawCommand.instanceCount, 1); 85 | 86 | // Check if buffer is full 87 | if (index >= debugLineBuffers[lineBuffer.bufIdx].lines.length()) 88 | { 89 | atomicAdd(debugLineBuffers[lineBuffer.bufIdx].drawCommand.instanceCount, -1); 90 | return false; 91 | } 92 | 93 | debugLineBuffers[lineBuffer.bufIdx].lines[index] = line; 94 | return true; 95 | } 96 | #endif // DEBUG_COMMON_H -------------------------------------------------------------------------------- /data/shaders/ao/rtao/RayTracedAO.comp.glsl: -------------------------------------------------------------------------------- 1 | #include "../../Resources.h.glsl" 2 | 3 | #ifndef __cplusplus 4 | #include "../../Math.h.glsl" 5 | #include "../../Utility.h.glsl" 6 | #include "../../Config.shared.h" 7 | #include "../../Hash.h.glsl" 8 | #else 9 | using namespace shared; 10 | #endif 11 | 12 | FVOG_DECLARE_ARGUMENTS(RtaoArguments) 13 | { 14 | FVOG_UINT64 tlasAddress; 15 | Texture2D gDepth; 16 | Texture2D gNormalAndFaceNormal; 17 | Image2D outputAo; 18 | FVOG_MAT4 world_from_clip; 19 | FVOG_UINT32 numRays; 20 | FVOG_FLOAT rayLength; 21 | FVOG_UINT32 frameNumber; 22 | }; 23 | 24 | #ifndef __cplusplus 25 | 26 | layout(local_size_x = 8, local_size_y = 8) in; 27 | 28 | void main() 29 | { 30 | const ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 31 | 32 | if (any(greaterThanEqual(gid, imageSize(outputAo)))) 33 | { 34 | return; 35 | } 36 | 37 | const vec2 uv = (vec2(gid) + 0.5) / imageSize(outputAo); 38 | 39 | const float depth = texelFetch(gDepth, gid, 0).x; 40 | 41 | if (depth == FAR_DEPTH) 42 | { 43 | return; 44 | } 45 | 46 | const vec4 normalOctAndFlatNormalOct = texelFetch(gNormalAndFaceNormal, gid, 0); 47 | const vec3 flatNormal = OctToVec3(normalOctAndFlatNormalOct.zw); 48 | 49 | const vec3 rayOrigin = UnprojectUV_ZO(depth, uv, world_from_clip); 50 | 51 | uint randState = PCG_Hash(frameNumber + PCG_Hash(gid.y + PCG_Hash(gid.x))); 52 | vec2 noise = vec2(PCG_RandFloat(randState, 0, 1), PCG_RandFloat(randState, 0, 1)); 53 | 54 | float visibility = 0; 55 | for (uint i = 0; i < numRays; i++) 56 | { 57 | const vec2 xi = fract(noise + Hammersley(i, numRays)); 58 | const vec3 rayDir = map_to_unit_hemisphere_cosine_weighted(xi, flatNormal); 59 | 60 | rayQueryEXT rayQuery; 61 | rayQueryInitializeEXT(rayQuery, accelerationStructureEXT(tlasAddress), 62 | gl_RayFlagsOpaqueEXT, 63 | 0xFF, rayOrigin + flatNormal * .001, 0.001, rayDir, rayLength); 64 | 65 | while (rayQueryProceedEXT(rayQuery)) 66 | { 67 | if (rayQueryGetIntersectionTypeEXT(rayQuery, false) == gl_RayQueryCandidateIntersectionTriangleEXT) 68 | { 69 | rayQueryConfirmIntersectionEXT(rayQuery); 70 | } 71 | } 72 | 73 | // Miss, increase visibility 74 | if (!(rayQueryGetIntersectionTypeEXT(rayQuery, true) == gl_RayQueryCommittedIntersectionTriangleEXT)) 75 | { 76 | // cos_theta * lambertian(1) / pdf 77 | // cosine weighted sampling pdf = cos_theta / pi 78 | // lambertian = c / pi 79 | // The terms cancel out so we're left with only visibility, which is handled by this branch. 80 | visibility += 1.0; 81 | } 82 | } 83 | 84 | if (numRays == 0) 85 | { 86 | imageStore(outputAo, gid, vec4(1)); 87 | return; 88 | } 89 | 90 | const float ao = visibility / numRays; 91 | imageStore(outputAo, gid, vec4(ao)); 92 | } 93 | 94 | #endif // !__cplusplus 95 | -------------------------------------------------------------------------------- /data/shaders/visbuffer/Visbuffer.frag.glsl: -------------------------------------------------------------------------------- 1 | #include "VisbufferCommon.h.glsl" 2 | #include "../Math.h.glsl" 3 | #include "../Hash.h.glsl" 4 | 5 | layout (location = 0) in flat uint i_visibleMeshletId; 6 | layout (location = 1) in flat uint i_primitiveId; 7 | layout (location = 2) in vec2 i_uv; 8 | layout (location = 3) in vec3 i_objectSpacePos; 9 | layout (location = 4) in flat uint i_materialId; 10 | 11 | layout (location = 0) out uint o_pixel; 12 | 13 | void main() 14 | { 15 | const GpuMaterial material = d_materials[i_materialId]; 16 | 17 | vec2 dxuv = dFdx(i_uv); 18 | vec2 dyuv = dFdy(i_uv); 19 | 20 | const float maxObjSpaceDerivLen = max(length(dFdx(i_objectSpacePos)), length(dFdy(i_objectSpacePos))); 21 | 22 | if (material.baseColorTextureIndex != 0) 23 | { 24 | // Apply a mip/lod bias to the sampled value 25 | if (d_perFrameUniforms.bindlessSamplerLodBias != 0) 26 | { 27 | ApplyLodBiasToGradient(dxuv, dyuv, d_perFrameUniforms.bindlessSamplerLodBias); 28 | } 29 | 30 | float alpha = material.baseColorFactor.a; 31 | float lod = 0; 32 | if (bool(material.flags & MATERIAL_HAS_BASE_COLOR)) 33 | { 34 | alpha *= textureGrad(Fvog_sampler2D(material.baseColorTextureIndex, materialSamplerIndex), i_uv, dxuv, dyuv).a; 35 | 36 | // textureQueryLod at home 37 | const vec2 texSize = textureSize(FvogGetSampledImage(texture2D, material.baseColorTextureIndex), 0); 38 | const float maxSize = max(texSize.x, texSize.y); 39 | const float maxLod = floor(log2(maxSize)); 40 | const float p = max(length(dxuv), length(dyuv)); 41 | lod = clamp(log2(p * maxSize), 0, maxLod); 42 | } 43 | 44 | float threshold = material.alphaCutoff; 45 | if (bool(d_perFrameUniforms.flags & USE_HASHED_TRANSPARENCY)) 46 | { 47 | threshold = material.alphaCutoff + min(1.0 - material.alphaCutoff, material.alphaCutoff) + (ComputeHashedAlphaThreshold(i_objectSpacePos, maxObjSpaceDerivLen, d_perFrameUniforms.alphaHashScale) * 2.0 - 1.0); 48 | //threshold = material.alphaCutoff - 0.5 + ComputeHashedAlphaThreshold(i_objectSpacePos, maxObjSpaceDerivLen, perFrameUniforms.alphaHashScale); 49 | //threshold = material.alphaCutoff + min(1.0 - material.alphaCutoff, material.alphaCutoff) + (MM_Hash3(i_objectSpacePos) * 2.0 - 1.0); 50 | //threshold = material.alphaCutoff - 0.5 + MM_Hash3(i_objectSpacePos); 51 | //threshold = MM_Hash3(i_objectSpacePos); 52 | threshold = ComputeHashedAlphaThreshold(i_objectSpacePos, maxObjSpaceDerivLen, d_perFrameUniforms.alphaHashScale); 53 | } 54 | 55 | if (alpha < clamp(threshold, 0.001, 1.0)) 56 | //if (alpha < clamp(threshold, 0.1, 0.9)) 57 | //if (alpha < clamp(threshold, 0.1 / (10.0 * (1.0 + lod)), 0.9)) 58 | //if (alpha < clamp(threshold, 0.01 / exp2(lod), 1)) 59 | { 60 | discard; 61 | } 62 | } 63 | 64 | o_pixel = (i_visibleMeshletId << MESHLET_PRIMITIVE_BITS) | (i_primitiveId & MESHLET_PRIMITIVE_MASK); 65 | } 66 | -------------------------------------------------------------------------------- /data/shaders/shadows/vsm/VsmShadow.frag.glsl: -------------------------------------------------------------------------------- 1 | #include "../../Config.shared.h" 2 | #define VISBUFFER_NO_PUSH_CONSTANTS 3 | #include "../../visbuffer/VisbufferCommon.h.glsl" 4 | #include "VsmCommon.h.glsl" 5 | 6 | layout(set = 0, binding = FVOG_STORAGE_IMAGE_BINDING, r32ui) uniform uimage2D physicalPagesUintImages[]; 7 | #define i_physicalPagesUint physicalPagesUintImages[physicalPagesUintIndex] 8 | 9 | layout(location = 0) in vec2 v_uv; 10 | layout(location = 1) in flat uint v_materialId; 11 | layout(location = 2) in vec3 i_objectSpacePos; 12 | 13 | #if VSM_USE_TEMP_ZBUFFER || VSM_USE_TEMP_SBUFFER 14 | layout(early_fragment_tests) in; 15 | #endif 16 | 17 | void main() 18 | { 19 | #if VSM_SUPPORT_ALPHA_MASKED_GEOMETRY 20 | const GpuMaterial material = d_materials[NonUniformIndex(v_materialId)]; 21 | 22 | vec2 dxuv = dFdx(v_uv); 23 | vec2 dyuv = dFdy(v_uv); 24 | 25 | const float maxObjSpaceDerivLen = max(length(dFdx(i_objectSpacePos)), length(dFdy(i_objectSpacePos))); 26 | if (material.baseColorTextureIndex != 0 && material.alphaCutoff > 0) 27 | { 28 | // Apply a mip/lod bias to the sampled value 29 | if (perFrameUniforms.bindlessSamplerLodBias != 0) 30 | { 31 | ApplyLodBiasToGradient(dxuv, dyuv, perFrameUniforms.bindlessSamplerLodBias); 32 | } 33 | 34 | float alpha = material.baseColorFactor.a; 35 | if (bool(material.flags & MATERIAL_HAS_BASE_COLOR)) 36 | { 37 | alpha *= textureGrad(Fvog_sampler2D(material.baseColorTextureIndex, materialSamplerIndex), v_uv, dxuv, dyuv).a; 38 | } 39 | 40 | float threshold = material.alphaCutoff; 41 | if (bool(perFrameUniforms.flags & USE_HASHED_TRANSPARENCY)) 42 | { 43 | threshold = ComputeHashedAlphaThreshold(i_objectSpacePos, maxObjSpaceDerivLen, perFrameUniforms.alphaHashScale); 44 | } 45 | 46 | if (alpha < clamp(threshold, 0.001, 1.0)) 47 | { 48 | discard; 49 | } 50 | } 51 | #endif 52 | 53 | const uint clipmapIndex = clipmapUniforms.clipmapTableIndices[clipmapLod]; 54 | const ivec2 pageOffset = clipmapUniforms.clipmapPageOffsets[clipmapLod]; 55 | const ivec2 pageAddressXy = ivec2(mod(vec2(ivec2(gl_FragCoord.xy) / PAGE_SIZE + pageOffset), vec2(imageSize(i_pageTables).xy))); 56 | const uint pageData = imageLoad(i_pageTables, ivec3(pageAddressXy, clipmapIndex)).x; 57 | if (GetIsPageBacked(pageData) && GetIsPageDirty(pageData)) 58 | { 59 | const ivec2 pageTexel = ivec2(gl_FragCoord.xy) % PAGE_SIZE; 60 | const uint page = GetPagePhysicalAddress(pageData); 61 | const int atlasWidth = imageSize(i_physicalPagesUint).x / PAGE_SIZE; 62 | const ivec2 pageCorner = PAGE_SIZE * ivec2(page / atlasWidth, page % atlasWidth); 63 | const uint depthUint = floatBitsToUint(gl_FragCoord.z); 64 | const ivec2 physicalTexel = pageCorner + pageTexel; 65 | imageAtomicMin(i_physicalPagesUint, physicalTexel, depthUint); 66 | #if VSM_RENDER_OVERDRAW 67 | imageAtomicAdd(i_physicalPagesOverdrawHeatmap, physicalTexel, 1); 68 | #endif 69 | } 70 | } -------------------------------------------------------------------------------- /src/Fvog/Timer2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace Fvog 9 | { 10 | class Device; 11 | 12 | /// @brief Async N-buffered timer query that does not induce pipeline stalls 13 | /// 14 | /// Useful for measuring performance of passes every frame without causing stalls. 15 | /// However, the results returned may be from multiple frames ago, 16 | /// and results are not guaranteed to be available. 17 | /// In practice, setting N to 5 should allow at least one query to be available every frame. 18 | class TimerQueryAsync 19 | { 20 | public: 21 | TimerQueryAsync(uint32_t N, std::string name); 22 | ~TimerQueryAsync(); 23 | 24 | TimerQueryAsync(const TimerQueryAsync&) = delete; 25 | TimerQueryAsync& operator=(const TimerQueryAsync&) = delete; 26 | 27 | TimerQueryAsync(TimerQueryAsync&& old) noexcept 28 | : queryPool_(std::exchange(old.queryPool_, nullptr)), 29 | start_(std::exchange(old.start_, 0)), 30 | count_(std::exchange(old.count_, 0)), 31 | capacity_(std::exchange(old.capacity_, 0)), 32 | name_(std::move(old.name_)) 33 | { 34 | } 35 | 36 | TimerQueryAsync& operator=(TimerQueryAsync&& old) noexcept 37 | { 38 | if (&old == this) 39 | return *this; 40 | this->~TimerQueryAsync(); 41 | return *new (this) TimerQueryAsync(std::move(old)); 42 | } 43 | 44 | /// @brief Begins a query zone 45 | /// 46 | /// @note EndZone must be called before another zone can begin 47 | void BeginZone(VkCommandBuffer commandBuffer); 48 | 49 | /// @brief Ends a query zone 50 | /// 51 | /// @note BeginZone must be called before a zone can end 52 | void EndZone(VkCommandBuffer commandBuffer); 53 | 54 | /// @brief Gets the latest available query 55 | /// @return The latest query, if available. Otherwise, std::nullopt is returned 56 | [[nodiscard]] std::optional PopTimestamp(); 57 | 58 | private: 59 | VkQueryPool queryPool_; 60 | uint32_t start_{}; // next timer to be used for measurement 61 | uint32_t count_{}; // number of timers 'buffered', ie measurement was started by result not read yet 62 | uint32_t capacity_{}; 63 | std::string name_; 64 | }; 65 | 66 | /// @brief RAII wrapper for TimerQueryAsync 67 | template 68 | class TimerScoped 69 | { 70 | public: 71 | TimerScoped(T& zone, VkCommandBuffer commandBuffer) 72 | : zone_(zone), 73 | commandBuffer_(commandBuffer) 74 | { 75 | zone_.BeginZone(commandBuffer_); 76 | } 77 | 78 | ~TimerScoped() 79 | { 80 | zone_.EndZone(commandBuffer_); 81 | } 82 | 83 | TimerScoped(const TimerScoped&) = delete; 84 | TimerScoped(TimerScoped&&) noexcept = delete; 85 | TimerScoped& operator=(const TimerScoped&) = delete; 86 | TimerScoped& operator=(TimerScoped&&) noexcept = delete; 87 | 88 | private: 89 | T& zone_; 90 | VkCommandBuffer commandBuffer_; 91 | }; 92 | } // namespace Fvog -------------------------------------------------------------------------------- /data/shaders/volumetric/Frog.h: -------------------------------------------------------------------------------- 1 | #ifndef FROG_H 2 | #define FROG_H 3 | 4 | // All the code in this file (including the Froge SDF) is by Clement Pirelli 5 | // Taken from this shader: https://www.shadertoy.com/view/WstGDs 6 | 7 | struct frog_sdfRet 8 | { 9 | float sdf; 10 | float id; 11 | }; 12 | 13 | struct frog_sphere 14 | { 15 | vec3 c; 16 | float r; 17 | }; 18 | 19 | struct frog_ray 20 | { 21 | vec3 origin; 22 | vec3 direction; 23 | }; 24 | 25 | 26 | // ----distance functions---- 27 | 28 | 29 | float frog_sphDist(vec3 p, frog_sphere s) 30 | { 31 | return distance(p, s.c) - s.r; 32 | } 33 | 34 | float frog_sdCapsule( vec3 p, vec3 a, vec3 b, float r ) 35 | { 36 | vec3 pa = p - a, ba = b - a; 37 | float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); 38 | return length( pa - ba*h ) - r; 39 | } 40 | 41 | //from iq : https://iquilezles.org/articles/distfunctions 42 | float frog_smin( float d1, float d2, float k ) 43 | { 44 | float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 ); 45 | return mix( d2, d1, h ) - k*h*(1.0-h); 46 | } 47 | 48 | //from iq : https://iquilezles.org/articles/distfunctions 49 | float frog_smax( float d1, float d2, float k ) 50 | { 51 | float h = clamp( 0.5 - 0.5*(d2+d1)/k, 0.0, 1.0 ); 52 | return mix( d2, -d1, h ) + k*h*(1.0-h); 53 | } 54 | 55 | 56 | frog_sdfRet frog(vec3 point) 57 | { 58 | //body 59 | float id = .0; 60 | float dist = frog_sphDist(point, frog_sphere(vec3(.0,.05,.0), .25 )); 61 | 62 | //shoulders 63 | dist = frog_smin(dist, frog_sphDist(point, frog_sphere(vec3(.34,-.12,-.1), .08)), .2); 64 | 65 | ////lower body 66 | dist = frog_smin(dist, frog_sphDist(point, frog_sphere(vec3(.0,.02,.3), .1)), .3); 67 | 68 | ////thighs 69 | dist = frog_smin(dist, frog_sphDist(point, frog_sphere(vec3(.24,-.12, .34), .08)), .2); 70 | 71 | //head 72 | dist = frog_smin(dist, frog_sphDist(point, frog_sphere(vec3(.0,.04,-.25), .22)), .1); 73 | 74 | dist = max(-max(frog_sdCapsule(point, vec3(-1.,-.04,-.5),vec3(1.,-.04,-.5),.1),point.y-.01),dist); 75 | 76 | //eyes 77 | float distEyes = frog_sphDist(point, frog_sphere(vec3(.15,.11,-.3), .14)); 78 | 79 | if(dist > distEyes) id = 2.0; 80 | dist = min(dist,distEyes); 81 | 82 | //iris 83 | float distIris = frog_sphDist(point, frog_sphere(vec3(.19,.11,-.32), .1)); 84 | if(dist > distIris) id = 3.0; 85 | dist = min(dist,distIris); 86 | 87 | return frog_sdfRet(dist, id); 88 | } 89 | 90 | frog_sdfRet frog_map(vec3 point) 91 | { 92 | point.z *=-1.0; 93 | point = vec3(abs(point.x),point.y,point.z); 94 | 95 | frog_sdfRet d = frog(point); 96 | 97 | return d; 98 | } 99 | 100 | 101 | vec3 frog_idtocol(float id) 102 | { 103 | vec3 col = vec3(.2,.9,.2); 104 | 105 | if(id > .5) col = vec3(.1,.1,.6); 106 | if(id > 1.5) col = vec3(1.0); 107 | if(id > 2.5) col = vec3(.1); 108 | 109 | return col; 110 | } 111 | 112 | #endif // FROG_H -------------------------------------------------------------------------------- /src/techniques/AutoExposure.cpp: -------------------------------------------------------------------------------- 1 | #include "AutoExposure.h" 2 | #include "Application.h" 3 | 4 | #include "Fvog/Device.h" 5 | #include "Fvog/Rendering2.h" 6 | #include "Fvog/Shader2.h" 7 | 8 | #include "../RendererUtilities.h" 9 | 10 | #include 11 | 12 | namespace Techniques 13 | { 14 | AutoExposure::AutoExposure() 15 | : dataBuffer_(1, "Auto Exposure Data") 16 | { 17 | generateLuminanceHistogramPipeline_ = GetPipelineManager().EnqueueCompileComputePipeline({ 18 | .name = "Generate Luminance Histogram", 19 | .shaderModuleInfo = 20 | PipelineManager::ShaderModuleCreateInfo{ 21 | .stage = Fvog::PipelineStage::COMPUTE_SHADER, 22 | .path = GetShaderDirectory() / "auto_exposure/GenerateLuminanceHistogram.comp.glsl", 23 | }, 24 | }); 25 | 26 | resolveLuminanceHistogramPipeline_ = GetPipelineManager().EnqueueCompileComputePipeline({ 27 | .name = "Resolve Luminance Histogram", 28 | .shaderModuleInfo = 29 | PipelineManager::ShaderModuleCreateInfo{ 30 | .stage = Fvog::PipelineStage::COMPUTE_SHADER, 31 | .path = GetShaderDirectory() / "auto_exposure/ResolveLuminanceHistogram.comp.glsl", 32 | }, 33 | }); 34 | 35 | 36 | // Initialize buckets to zero 37 | //dataBuffer_.FillData(); 38 | Fvog::GetDevice().ImmediateSubmit( 39 | [this](VkCommandBuffer cmd) { 40 | vkCmdFillBuffer(cmd, dataBuffer_.GetDeviceBuffer().Handle(), 0, VK_WHOLE_SIZE, 0); 41 | }); 42 | } 43 | 44 | void AutoExposure::Apply(VkCommandBuffer cmd, const ApplyParams& params) 45 | { 46 | auto ctx = Fvog::Context(cmd); 47 | auto d = ctx.MakeScopedDebugMarker("Auto Exposure"); 48 | 49 | auto uniforms = AutoExposureUniforms{ 50 | .deltaTime = params.deltaTime, 51 | .adjustmentSpeed = params.adjustmentSpeed, 52 | .logMinLuminance = std::log2(params.targetLuminance / std::exp2(params.logMinLuminance)), 53 | .logMaxLuminance = std::log2(params.targetLuminance / std::exp2(params.logMaxLuminance)), 54 | .targetLuminance = params.targetLuminance, 55 | .numPixels = params.image.GetCreateInfo().extent.width * params.image.GetCreateInfo().extent.height, 56 | }; 57 | auto uploaded = AutoExposureBufferData{uniforms}; 58 | dataBuffer_.UpdateData(cmd, {&uploaded, 1}); 59 | 60 | ctx.Barrier(); 61 | 62 | // Generate histogram 63 | ctx.SetPushConstants(AutoExposurePushConstants{ 64 | .autoExposureBufferIndex = dataBuffer_.GetDeviceBuffer().GetResourceHandle().index, 65 | .exposureBufferIndex = params.exposureBuffer.GetResourceHandle().index, 66 | .hdrBufferIndex = params.image.ImageView().GetSampledResourceHandle().index, 67 | }); 68 | 69 | ctx.BindComputePipeline(generateLuminanceHistogramPipeline_.GetPipeline()); 70 | ctx.DispatchInvocations(params.image.GetCreateInfo().extent); 71 | 72 | ctx.Barrier(); 73 | 74 | // Resolve histogram 75 | ctx.BindComputePipeline(resolveLuminanceHistogramPipeline_.GetPipeline()); 76 | ctx.Dispatch(1, 1, 1); 77 | 78 | ctx.Barrier(); 79 | } 80 | } // namespace Techniques -------------------------------------------------------------------------------- /data/shaders/bloom/BloomDownsample.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | #extension GL_GOOGLE_include_directive : enable 4 | 5 | #define LOCAL_SIZE_X 8 6 | #define LOCAL_SIZE_Y 8 7 | #include "BloomDownsampleCommon.h.glsl" 8 | 9 | #include "BloomCommon.h.glsl" 10 | 11 | void main() 12 | { 13 | const ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 14 | const ivec2 lid = ivec2(gl_LocalInvocationID.xy); 15 | 16 | // Center of written pixel 17 | const vec2 uv = (vec2(gid) + 0.5) / uniforms.targetDim; 18 | 19 | InitializeSharedMemory(uniforms.targetDim, uniforms.sourceDim, uniforms.sourceLod); 20 | 21 | barrier(); 22 | 23 | if (any(greaterThanEqual(gid, uniforms.targetDim))) 24 | { 25 | return; 26 | } 27 | 28 | vec3 filterSum = vec3(0); 29 | filterSum += sh_coarseSamples[lid.x + 0][lid.y + 0] * (1.0 / 32.0); 30 | filterSum += sh_coarseSamples[lid.x + 2][lid.y + 0] * (1.0 / 32.0); 31 | filterSum += sh_coarseSamples[lid.x + 0][lid.y + 2] * (1.0 / 32.0); 32 | filterSum += sh_coarseSamples[lid.x + 2][lid.y + 2] * (1.0 / 32.0); 33 | 34 | filterSum += sh_coarseSamples[lid.x + 1][lid.y + 2] * (2.0 / 32.0); 35 | filterSum += sh_coarseSamples[lid.x + 1][lid.y + 0] * (2.0 / 32.0); 36 | filterSum += sh_coarseSamples[lid.x + 2][lid.y + 1] * (2.0 / 32.0); 37 | filterSum += sh_coarseSamples[lid.x + 0][lid.y + 1] * (2.0 / 32.0); 38 | 39 | filterSum += sh_coarseSamples[lid.x + 1][lid.y + 1] * (4.0 / 32.0); 40 | 41 | filterSum += sh_preciseSamples[lid.x + 0][lid.y + 0] * (4.0 / 32.0); 42 | filterSum += sh_preciseSamples[lid.x + 1][lid.y + 0] * (4.0 / 32.0); 43 | filterSum += sh_preciseSamples[lid.x + 0][lid.y + 1] * (4.0 / 32.0); 44 | filterSum += sh_preciseSamples[lid.x + 1][lid.y + 1] * (4.0 / 32.0); 45 | 46 | // Uncomment to debug 47 | // filterSum = vec3(0); 48 | // vec2 texel = 1.0 / uniforms.sourceDim; 49 | // filterSum += textureLod(s_source, uv + texel * vec2(-2, -2), uniforms.sourceLod).rgb * (1.0 / 32.0); 50 | // filterSum += textureLod(s_source, uv + texel * vec2(2, -2) , uniforms.sourceLod).rgb * (1.0 / 32.0); 51 | // filterSum += textureLod(s_source, uv + texel * vec2(-2, 2) , uniforms.sourceLod).rgb * (1.0 / 32.0); 52 | // filterSum += textureLod(s_source, uv + texel * vec2(2, 2) , uniforms.sourceLod).rgb * (1.0 / 32.0); 53 | // filterSum += textureLod(s_source, uv + texel * vec2(0, 2) , uniforms.sourceLod).rgb * (2.0 / 32.0); 54 | // filterSum += textureLod(s_source, uv + texel * vec2(0, -2) , uniforms.sourceLod).rgb * (2.0 / 32.0); 55 | // filterSum += textureLod(s_source, uv + texel * vec2(2, 0) , uniforms.sourceLod).rgb * (2.0 / 32.0); 56 | // filterSum += textureLod(s_source, uv + texel * vec2(-2, 0) , uniforms.sourceLod).rgb * (2.0 / 32.0); 57 | // filterSum += textureLod(s_source, uv + texel * vec2(0, 0) , uniforms.sourceLod).rgb * (4.0 / 32.0); 58 | // filterSum += textureLod(s_source, uv + texel * vec2(-1, -1), uniforms.sourceLod).rgb * (4.0 / 32.0); 59 | // filterSum += textureLod(s_source, uv + texel * vec2(1, -1) , uniforms.sourceLod).rgb * (4.0 / 32.0); 60 | // filterSum += textureLod(s_source, uv + texel * vec2(-1, 1) , uniforms.sourceLod).rgb * (4.0 / 32.0); 61 | // filterSum += textureLod(s_source, uv + texel * vec2(1, 1) , uniforms.sourceLod).rgb * (4.0 / 32.0); 62 | 63 | imageStore(i_target, gid, vec4(filterSum, 1.0)); 64 | } -------------------------------------------------------------------------------- /src/Fvog/detail/SamplerCache2.cpp: -------------------------------------------------------------------------------- 1 | #include "SamplerCache2.h" 2 | #include "../Device.h" 3 | #include "ApiToEnum2.h" 4 | #include "Hash2.h" 5 | #include "Common.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | namespace Fvog::detail 12 | { 13 | Sampler SamplerCache::CreateOrGetCachedTextureSampler(const SamplerCreateInfo& samplerState, std::string name) 14 | { 15 | ZoneScoped; 16 | ZoneNamed(_, true); 17 | ZoneNameV(_, name.data(), name.size()); 18 | if (auto it = samplerCache_.find(samplerState); it != samplerCache_.end()) 19 | { 20 | return it->second; 21 | } 22 | 23 | auto sampler = VkSampler{}; 24 | vkCreateSampler(device_->device_, Address(VkSamplerCreateInfo{ 25 | .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, 26 | .magFilter = samplerState.magFilter, 27 | .minFilter = samplerState.minFilter, 28 | .mipmapMode = samplerState.mipmapMode, 29 | .addressModeU = samplerState.addressModeU, 30 | .addressModeV = samplerState.addressModeV, 31 | .addressModeW = samplerState.addressModeW, 32 | .mipLodBias = samplerState.mipLodBias, 33 | .anisotropyEnable = samplerState.maxAnisotropy >= 1.0f, 34 | .maxAnisotropy = samplerState.maxAnisotropy, 35 | .compareEnable = samplerState.compareEnable, 36 | .compareOp = samplerState.compareOp, 37 | .minLod = samplerState.minLod, 38 | .maxLod = samplerState.maxLod, 39 | .borderColor = samplerState.borderColor, 40 | .unnormalizedCoordinates = VK_FALSE, 41 | }), nullptr, &sampler); 42 | 43 | // TODO: gate behind compile-time switch 44 | vkSetDebugUtilsObjectNameEXT(device_->device_, detail::Address(VkDebugUtilsObjectNameInfoEXT{ 45 | .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, 46 | .objectType = VK_OBJECT_TYPE_SAMPLER, 47 | .objectHandle = reinterpret_cast(sampler), 48 | .pObjectName = name.data(), 49 | })); 50 | 51 | //detail::InvokeVerboseMessageCallback("Created sampler with handle ", sampler); 52 | 53 | auto& descriptorInfo = descriptorCache_.insert({samplerState, device_->AllocateSamplerDescriptor(sampler)}).first->second; 54 | return samplerCache_.insert({samplerState, Sampler(sampler, descriptorInfo)}).first->second; 55 | } 56 | 57 | size_t SamplerCache::Size() const 58 | { 59 | return samplerCache_.size(); 60 | } 61 | 62 | void SamplerCache::Clear() 63 | { 64 | for (const auto& [_, sampler] : samplerCache_) 65 | { 66 | //detail::InvokeVerboseMessageCallback("Destroyed sampler with handle ", sampler.id_); 67 | vkDestroySampler(device_->device_, sampler.Handle(), nullptr); 68 | } 69 | 70 | descriptorCache_.clear(); 71 | samplerCache_.clear(); 72 | } 73 | } // namespace Fwog::detail 74 | 75 | std::size_t std::hash::operator()(const Fvog::SamplerCreateInfo& k) const noexcept 76 | { 77 | auto hashed = std::make_tuple( 78 | k.minFilter, 79 | k.magFilter, 80 | k.mipmapMode, 81 | k.addressModeU, 82 | k.addressModeV, 83 | k.addressModeW, 84 | k.borderColor, 85 | k.maxAnisotropy, 86 | k.compareEnable, 87 | k.compareOp, 88 | k.mipLodBias, 89 | k.minLod, 90 | k.maxLod 91 | ); 92 | 93 | return Fvog::detail::hashing::hash{}(hashed); 94 | } 95 | -------------------------------------------------------------------------------- /data/shaders/volumetric/ApplyVolumetricsDeferred.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #extension GL_GOOGLE_include_directive : enable 3 | #include "Common.h" 4 | 5 | #define EPSILON .0001 6 | 7 | layout(binding = 0) uniform sampler2D s_color; 8 | layout(binding = 1) uniform sampler2D s_depth; 9 | layout(binding = 2) uniform sampler3D s_volume; 10 | layout(binding = 3) uniform sampler2D s_blueNoise; 11 | layout(binding = 0) uniform writeonly image2D i_target; 12 | 13 | // Ported from here: https://gist.github.com/Fewes/59d2c831672040452aa77da6eaab2234 14 | vec4 textureTricubic(sampler3D volume, vec3 coord) 15 | { 16 | vec3 texSize = textureSize(volume, 0); 17 | 18 | vec3 coord_grid = coord * texSize - 0.5; 19 | vec3 index = floor(coord_grid); 20 | vec3 fraction = coord_grid - index; 21 | vec3 one_frac = 1.0 - fraction; 22 | 23 | vec3 w0 = 1.0 / 6.0 * one_frac*one_frac*one_frac; 24 | vec3 w1 = 2.0 / 3.0 - 0.5 * fraction*fraction*(2.0-fraction); 25 | vec3 w2 = 2.0 / 3.0 - 0.5 * one_frac*one_frac*(2.0-one_frac); 26 | vec3 w3 = 1.0 / 6.0 * fraction*fraction*fraction; 27 | 28 | vec3 g0 = w0 + w1; 29 | vec3 g1 = w2 + w3; 30 | vec3 mult = 1.0 / texSize; 31 | vec3 h0 = mult * ((w1 / g0) - 0.5 + index); 32 | vec3 h1 = mult * ((w3 / g1) + 1.5 + index); 33 | 34 | vec4 tex000 = textureLod(volume, h0, 0.0); 35 | vec4 tex100 = textureLod(volume, vec3(h1.x, h0.y, h0.z), 0.0); 36 | tex000 = mix(tex100, tex000, g0.x); 37 | 38 | vec4 tex010 = textureLod(volume, vec3(h0.x, h1.y, h0.z), 0.0); 39 | vec4 tex110 = textureLod(volume, vec3(h1.x, h1.y, h0.z), 0.0); 40 | tex010 = mix(tex110, tex010, g0.x); 41 | tex000 = mix(tex010, tex000, g0.y); 42 | 43 | vec4 tex001 = textureLod(volume, vec3(h0.x, h0.y, h1.z), 0.0); 44 | vec4 tex101 = textureLod(volume, vec3(h1.x, h0.y, h1.z), 0.0); 45 | tex001 = mix(tex101, tex001, g0.x); 46 | 47 | vec4 tex011 = textureLod(volume, vec3(h0.x, h1.y, h1.z), 0.0); 48 | vec4 tex111 = textureLod(volume, h1, 0.0); 49 | tex011 = mix(tex111, tex011, g0.x); 50 | tex001 = mix(tex011, tex001, g0.y); 51 | 52 | return mix(tex001, tex000, g0.z); 53 | } 54 | 55 | layout(local_size_x = 16, local_size_y = 16) in; 56 | void main() 57 | { 58 | ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 59 | ivec2 targetDim = imageSize(i_target); 60 | if (any(greaterThanEqual(gid, targetDim))) 61 | return; 62 | vec2 uv = (vec2(gid) + 0.5) / targetDim; 63 | 64 | // Get Z-buffer depth and reconstruct world position. 65 | float zScr = texelFetch(s_depth, gid, 0).x; 66 | zScr = max(zScr, EPSILON); // prevent infinities 67 | vec3 pWorld = UnprojectUVZO(zScr, uv, uniforms.invViewProjScene); 68 | 69 | // World position to volume clip space. 70 | vec4 volumeClip = uniforms.viewProjVolume * vec4(pWorld, 1.0); 71 | 72 | // Volume clip to volume UV (perspective divide). 73 | vec3 volumeUV = volumeClip.xyz / volumeClip.w; 74 | volumeUV.xy = volumeUV.xy * 0.5 + 0.5; 75 | 76 | // Linearize the window-space depth, then invert the transform applied in accumulateDensity.comp.glsl (volumeUV.z^2). 77 | volumeUV.z = sqrt(LinearizeDepthZO(volumeUV.z, uniforms.volumeNearPlane, uniforms.volumeFarPlane)); 78 | 79 | // Random UV offset of up to half a froxel. 80 | vec3 offset = uniforms.noiseOffsetScale * (texelFetch(s_blueNoise, gid % textureSize(s_blueNoise, 0).xy, 0).xyz - 0.5); 81 | volumeUV += offset / vec3(textureSize(s_volume, 0).xyz); 82 | 83 | vec3 baseColor = texelFetch(s_color, gid, 0).xyz; 84 | vec4 scatteringInfo = textureTricubic(s_volume, volumeUV); 85 | vec3 inScattering = scatteringInfo.rgb; 86 | float transmittance = scatteringInfo.a; 87 | 88 | vec3 finalColor = baseColor * transmittance + inScattering; 89 | 90 | imageStore(i_target, gid, vec4(finalColor, 1.0)); 91 | } -------------------------------------------------------------------------------- /src/MathUtilities.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace Math 11 | { 12 | inline glm::mat4 InfReverseZPerspectiveRH(float fovY_radians, float aspectWbyH, float zNear) 13 | { 14 | float f = 1.0f / tan(fovY_radians / 2.0f); 15 | return { 16 | f / aspectWbyH, 0.0f, 0.0f, 0.0f, 17 | 0.0f, -f, 0.0f, 0.0f, // Negate [1][1] to work with Vulkan 18 | 0.0f, 0.0f, 0.0f, -1.0f, 19 | 0.0f, 0.0f, zNear, 0.0f 20 | }; 21 | } 22 | 23 | inline constexpr uint32_t PreviousPower2(uint32_t x) 24 | { 25 | uint32_t v = 1; 26 | while ((v << 1) < x) 27 | { 28 | v <<= 1; 29 | } 30 | return v; 31 | } 32 | 33 | inline void MakeFrustumPlanes(const glm::mat4& viewProj, glm::vec4 (&planes)[6]) 34 | { 35 | for (auto i = 0; i < 4; ++i) 36 | { 37 | planes[0][i] = viewProj[i][3] + viewProj[i][0]; 38 | } 39 | for (auto i = 0; i < 4; ++i) 40 | { 41 | planes[1][i] = viewProj[i][3] - viewProj[i][0]; 42 | } 43 | for (auto i = 0; i < 4; ++i) 44 | { 45 | planes[2][i] = viewProj[i][3] + viewProj[i][1]; 46 | } 47 | for (auto i = 0; i < 4; ++i) 48 | { 49 | planes[3][i] = viewProj[i][3] - viewProj[i][1]; 50 | } 51 | for (auto i = 0; i < 4; ++i) 52 | { 53 | planes[4][i] = viewProj[i][3] + viewProj[i][2]; 54 | } 55 | for (auto i = 0; i < 4; ++i) 56 | { 57 | planes[5][i] = viewProj[i][3] - viewProj[i][2]; 58 | } 59 | 60 | for (auto& plane : planes) 61 | { 62 | plane /= glm::length(glm::vec3(plane)); 63 | plane.w = -plane.w; 64 | } 65 | } 66 | 67 | // Zero-origin unprojection. E.g., pass sampled depth, screen UV, and invViewProj to get a world-space pos 68 | inline glm::vec3 UnprojectUV_ZO(float depth, glm::vec2 uv, const glm::mat4& invXProj) 69 | { 70 | glm::vec4 ndc = glm::vec4(uv * 2.0f - 1.0f, depth, 1.0f); 71 | glm::vec4 world = invXProj * ndc; 72 | return glm::vec3(world) / world.w; 73 | } 74 | 75 | inline glm::vec2 SignNotZero(glm::vec2 v) 76 | { 77 | return glm::vec2((v.x >= 0.0f) ? +1.0f : -1.0f, (v.y >= 0.0f) ? +1.0f : -1.0f); 78 | } 79 | 80 | inline glm::vec2 Vec3ToOct(glm::vec3 v) 81 | { 82 | glm::vec2 p = glm::vec2{v.x, v.y} * (1.0f / (abs(v.x) + abs(v.y) + abs(v.z))); 83 | return (v.z <= 0.0f) ? ((1.0f - glm::abs(glm::vec2{p.y, p.x})) * SignNotZero(p)) : p; 84 | } 85 | 86 | inline glm::vec3 OctToVec3(glm::vec2 e) 87 | { 88 | using glm::vec2; 89 | using glm::vec3; 90 | vec3 v = vec3(vec2(e.x, e.y), 1.0f - abs(e.x) - abs(e.y)); 91 | vec2 signNotZero = vec2((v.x >= 0.0f) ? +1.0f : -1.0f, (v.y >= 0.0f) ? +1.0f : -1.0f); 92 | if (v.z < 0.0f) { vec2(v.x, v.y) = (1.0f - abs(vec2(v.y, v.x))) * signNotZero; } 93 | return normalize(v); 94 | } 95 | 96 | inline glm::vec3 OctToVec3(uint32_t snorm) 97 | { 98 | return OctToVec3(glm::unpackSnorm2x16(snorm)); 99 | } 100 | 101 | struct SuffixAndDivisor 102 | { 103 | const char* suffix; 104 | double divisor; 105 | }; 106 | 107 | inline SuffixAndDivisor BytesToSuffixAndDivisor(uint64_t bytes) 108 | { 109 | const auto* suffix = "B"; 110 | double divisor = 1.0; 111 | if (bytes > 1000) 112 | { 113 | suffix = "KB"; 114 | divisor = 1000; 115 | } 116 | if (bytes > 1'000'000) 117 | { 118 | suffix = "MB"; 119 | divisor = 1'000'000; 120 | } 121 | if (bytes > 1'000'000'000) 122 | { 123 | suffix = "GB"; 124 | divisor = 1'000'000'000; 125 | } 126 | return {suffix, divisor}; 127 | } 128 | } // namespace Math -------------------------------------------------------------------------------- /src/Fvog/Timer2.cpp: -------------------------------------------------------------------------------- 1 | #include "Timer2.h" 2 | #include "Device.h" 3 | #include "detail/Common.h" 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace Fvog 10 | { 11 | TimerQueryAsync::TimerQueryAsync(uint32_t N, std::string name) 12 | : capacity_(N), 13 | name_(std::move(name)) 14 | { 15 | assert(capacity_ > 0); 16 | 17 | vkCreateQueryPool(Fvog::GetDevice().device_, 18 | detail::Address(VkQueryPoolCreateInfo{ 19 | .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, 20 | .queryType = VK_QUERY_TYPE_TIMESTAMP, 21 | .queryCount = N * 2, // Each query needs a start and end, hence the count is doubled 22 | }), 23 | nullptr, 24 | &queryPool_); 25 | 26 | // TODO: gate behind compile-time switch 27 | vkSetDebugUtilsObjectNameEXT(Fvog::GetDevice().device_, 28 | detail::Address(VkDebugUtilsObjectNameInfoEXT{ 29 | .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, 30 | .objectType = VK_OBJECT_TYPE_QUERY_POOL, 31 | .objectHandle = reinterpret_cast(queryPool_), 32 | .pObjectName = name_.data(), 33 | })); 34 | 35 | vkResetQueryPool(Fvog::GetDevice().device_, queryPool_, 0, N * 2); 36 | } 37 | 38 | TimerQueryAsync::~TimerQueryAsync() 39 | { 40 | if (queryPool_) 41 | { 42 | vkDestroyQueryPool(Fvog::GetDevice().device_, queryPool_, nullptr); 43 | } 44 | } 45 | 46 | void TimerQueryAsync::BeginZone(VkCommandBuffer commandBuffer) 47 | { 48 | // begin a query if there is at least one inactive 49 | if (count_ < capacity_) 50 | { 51 | vkResetQueryPool(Fvog::GetDevice().device_, queryPool_, start_, 1); 52 | vkCmdWriteTimestamp2(commandBuffer, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, queryPool_, start_); 53 | } 54 | } 55 | 56 | void TimerQueryAsync::EndZone(VkCommandBuffer commandBuffer) 57 | { 58 | // end a query if there is at least one inactive 59 | if (count_ < capacity_) 60 | { 61 | vkResetQueryPool(Fvog::GetDevice().device_, queryPool_, start_ + capacity_, 1); 62 | vkCmdWriteTimestamp2(commandBuffer, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, queryPool_, start_ + capacity_); 63 | start_ = (start_ + 1) % capacity_; // wrap 64 | count_++; 65 | } 66 | } 67 | 68 | std::optional TimerQueryAsync::PopTimestamp() 69 | { 70 | // Return nothing if there is no active query 71 | if (count_ == 0) 72 | { 73 | return std::nullopt; 74 | } 75 | 76 | // Get the index of the oldest query 77 | uint32_t index = (start_ + capacity_ - count_) % capacity_; 78 | 79 | // Getting the start result is a sanity check 80 | struct 81 | { 82 | uint64_t timestamp; 83 | uint64_t availability; 84 | } startResult{}, endResult{}; 85 | 86 | constexpr auto flags = VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT; 87 | vkGetQueryPoolResults(Fvog::GetDevice().device_, queryPool_, index, 1, sizeof(startResult), &startResult, 0, flags); 88 | vkGetQueryPoolResults(Fvog::GetDevice().device_, queryPool_, index + capacity_, 1, sizeof(endResult), &endResult, 0, flags); 89 | 90 | // The oldest queries' results are not available, abandon ship! 91 | if (startResult.availability == 0 || endResult.availability == 0) 92 | { 93 | return std::nullopt; 94 | } 95 | 96 | // pop oldest timing and retrieve result 97 | count_--; 98 | const auto period = (uint64_t)Fvog::GetDevice().device_.physical_device.properties.limits.timestampPeriod; 99 | if (endResult.timestamp > startResult.timestamp) 100 | return (endResult.timestamp - startResult.timestamp) * period; 101 | 102 | // Should never happen if the clock is monotonically increasing. 103 | return std::nullopt; 104 | } 105 | } // namespace Fvog -------------------------------------------------------------------------------- /data/shaders/bloom/BloomDownsampleLowPass.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | #extension GL_GOOGLE_include_directive : enable 4 | 5 | #define LOCAL_SIZE_X 16 6 | #define LOCAL_SIZE_Y 16 7 | #include "BloomDownsampleCommon.h.glsl" 8 | 9 | #include "../Utility.h.glsl" 10 | 11 | // Reduce the dynamic range of the input samples 12 | vec3 KarisAverage(vec3 c1, vec3 c2, vec3 c3, vec3 c4) 13 | { 14 | float w1 = 1.0 / (Luminance(c1.rgb) + 1.0); 15 | float w2 = 1.0 / (Luminance(c2.rgb) + 1.0); 16 | float w3 = 1.0 / (Luminance(c3.rgb) + 1.0); 17 | float w4 = 1.0 / (Luminance(c4.rgb) + 1.0); 18 | 19 | return (c1 * w1 + c2 * w2 + c3 * w3 + c4 * w4) / (w1 + w2 + w3 + w4); 20 | } 21 | 22 | void main() 23 | { 24 | const ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 25 | const ivec2 lid = ivec2(gl_LocalInvocationID.xy); 26 | 27 | // Center of written pixel 28 | const vec2 uv = (vec2(gid) + 0.5) / uniforms.targetDim; 29 | 30 | InitializeSharedMemory(uniforms.targetDim, uniforms.sourceDim, uniforms.sourceLod); 31 | 32 | barrier(); 33 | 34 | if (any(greaterThanEqual(gid, uniforms.targetDim))) 35 | { 36 | return; 37 | } 38 | 39 | vec3 samples[13]; 40 | samples[0 ] = sh_coarseSamples[lid.x + 0][lid.y + 0]; // (-2, -2) 41 | samples[1 ] = sh_coarseSamples[lid.x + 1][lid.y + 0]; // (0, -2) 42 | samples[2 ] = sh_coarseSamples[lid.x + 2][lid.y + 0]; // (2, -2) 43 | samples[3 ] = sh_preciseSamples[lid.x + 0][lid.y + 0]; // (-1, -1) 44 | samples[4 ] = sh_preciseSamples[lid.x + 1][lid.y + 0]; // (1, -1) 45 | samples[5 ] = sh_coarseSamples[lid.x + 0][lid.y + 1]; // (-2, 0) 46 | samples[6 ] = sh_coarseSamples[lid.x + 1][lid.y + 1]; // (0, 0) 47 | samples[7 ] = sh_coarseSamples[lid.x + 2][lid.y + 1]; // (2, 0) 48 | samples[8 ] = sh_preciseSamples[lid.x + 0][lid.y + 1]; // (-1, 1) 49 | samples[9 ] = sh_preciseSamples[lid.x + 1][lid.y + 1]; // (1, 1) 50 | samples[10] = sh_coarseSamples[lid.x + 0][lid.y + 2]; // (-2, 2) 51 | samples[11] = sh_coarseSamples[lid.x + 1][lid.y + 2]; // (0, 2) 52 | samples[12] = sh_coarseSamples[lid.x + 2][lid.y + 2]; // (2, 2) 53 | 54 | // Uncomment to debug 55 | // vec2 texel = 1.0 / uniforms.sourceDim; 56 | // samples[0 ] = textureLod(s_source, uv + texel * vec2(-2, -2), uniforms.sourceLod).rgb; 57 | // samples[1 ] = textureLod(s_source, uv + texel * vec2(0, -2) , uniforms.sourceLod).rgb; 58 | // samples[2 ] = textureLod(s_source, uv + texel * vec2(2, -2) , uniforms.sourceLod).rgb; 59 | // samples[3 ] = textureLod(s_source, uv + texel * vec2(-1, -1), uniforms.sourceLod).rgb; 60 | // samples[4 ] = textureLod(s_source, uv + texel * vec2(1, -1) , uniforms.sourceLod).rgb; 61 | // samples[5 ] = textureLod(s_source, uv + texel * vec2(-2, 0) , uniforms.sourceLod).rgb; 62 | // samples[6 ] = textureLod(s_source, uv + texel * vec2(0, 0) , uniforms.sourceLod).rgb; 63 | // samples[7 ] = textureLod(s_source, uv + texel * vec2(2, 0) , uniforms.sourceLod).rgb; 64 | // samples[8 ] = textureLod(s_source, uv + texel * vec2(-1, 1) , uniforms.sourceLod).rgb; 65 | // samples[9 ] = textureLod(s_source, uv + texel * vec2(1, 1) , uniforms.sourceLod).rgb; 66 | // samples[10] = textureLod(s_source, uv + texel * vec2(-2, 2) , uniforms.sourceLod).rgb; 67 | // samples[11] = textureLod(s_source, uv + texel * vec2(0, 2) , uniforms.sourceLod).rgb; 68 | // samples[12] = textureLod(s_source, uv + texel * vec2(2, 2) , uniforms.sourceLod).rgb; 69 | 70 | vec3 filterSum = vec3(0); 71 | filterSum += KarisAverage(samples[3], samples[4], samples[8 ], samples[9 ]) * 0.5; 72 | filterSum += KarisAverage(samples[0], samples[1], samples[5 ], samples[6 ]) * 0.125; 73 | filterSum += KarisAverage(samples[1], samples[2], samples[6 ], samples[7 ]) * 0.125; 74 | filterSum += KarisAverage(samples[5], samples[6], samples[10], samples[11]) * 0.125; 75 | filterSum += KarisAverage(samples[6], samples[7], samples[11], samples[12]) * 0.125; 76 | 77 | imageStore(i_target, gid, vec4(filterSum, 1.0)); 78 | } -------------------------------------------------------------------------------- /src/Renderables.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Fvog/detail/Flags.h" 4 | #include "Fvog/Texture2.h" 5 | 6 | #include "shaders/Resources.h.glsl" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | namespace Render 15 | { 16 | // TODO: split this into separate streams 17 | struct Vertex 18 | { 19 | glm::vec3 position; 20 | uint32_t normal; 21 | glm::vec2 texcoord; 22 | }; 23 | 24 | using index_t = uint32_t; 25 | using primitive_t = uint8_t; 26 | 27 | struct Box3D 28 | { 29 | glm::vec3 min; 30 | glm::vec3 max; 31 | }; 32 | 33 | struct CombinedTextureSampler 34 | { 35 | // Fwog::TextureView texture; 36 | // Fwog::SamplerState sampler; 37 | 38 | Fvog::TextureView texture; 39 | }; 40 | 41 | enum class MaterialFlagBit 42 | { 43 | HAS_BASE_COLOR_TEXTURE = 1 << 0, 44 | HAS_METALLIC_ROUGHNESS_TEXTURE = 1 << 1, 45 | HAS_NORMAL_TEXTURE = 1 << 2, 46 | HAS_OCCLUSION_TEXTURE = 1 << 3, 47 | HAS_EMISSION_TEXTURE = 1 << 4, 48 | }; 49 | FVOG_DECLARE_FLAG_TYPE(MaterialFlags, MaterialFlagBit, uint32_t) 50 | 51 | struct GpuMaterial 52 | { 53 | bool operator==(const GpuMaterial&) const noexcept = default; 54 | 55 | MaterialFlags flags; 56 | FVOG_FLOAT alphaCutoff; 57 | FVOG_FLOAT metallicFactor; 58 | FVOG_FLOAT roughnessFactor; 59 | FVOG_VEC4 baseColorFactor; 60 | FVOG_VEC3 emissiveFactor; 61 | FVOG_FLOAT emissiveStrength; 62 | FVOG_FLOAT normalXyScale; 63 | FVOG_UINT32 baseColorTextureIndex; 64 | FVOG_UINT32 metallicRoughnessTextureIndex; 65 | FVOG_UINT32 normalTextureIndex; 66 | FVOG_UINT32 occlusionTextureIndex; 67 | FVOG_UINT32 emissionTextureIndex; 68 | FVOG_UINT32 _padding[2]; 69 | }; 70 | 71 | struct Material 72 | { 73 | bool operator==(const Material& other) const noexcept 74 | { 75 | return gpuMaterial == other.gpuMaterial; 76 | } 77 | 78 | GpuMaterial gpuMaterial{}; 79 | std::optional albedoTextureSampler; 80 | std::optional metallicRoughnessTextureSampler; 81 | std::optional normalTextureSampler; 82 | std::optional occlusionTextureSampler; 83 | std::optional emissiveTextureSampler; 84 | }; 85 | 86 | struct Meshlet 87 | { 88 | uint32_t vertexOffset = 0; 89 | uint32_t indexOffset = 0; 90 | uint32_t primitiveOffset = 0; 91 | uint32_t indexCount = 0; 92 | uint32_t primitiveCount = 0; 93 | float aabbMin[3] = {}; 94 | float aabbMax[3] = {}; 95 | }; 96 | 97 | struct MeshletInstance 98 | { 99 | uint32_t meshletId; 100 | uint32_t instanceId; // For internal use only 101 | }; 102 | 103 | struct ObjectUniforms 104 | { 105 | bool operator==(const ObjectUniforms&) const noexcept = default; 106 | glm::mat4 modelPrevious; 107 | glm::mat4 modelCurrent; 108 | // TODO: Mesh geometry info should go in its own array 109 | VkDeviceAddress vertexBuffer{}; 110 | VkDeviceAddress indexBuffer{}; 111 | uint32_t materialId = 0; 112 | uint32_t _padding[3]; 113 | }; 114 | 115 | // The ID structs below this line mainly exist in this file as a hack to prevent 116 | // a circular dependency between FrogRenderer2.h and Scene.h. 117 | struct MeshGeometryID 118 | { 119 | explicit operator bool() const noexcept 120 | { 121 | return id != 0; 122 | } 123 | uint64_t id{}; 124 | }; 125 | 126 | struct MaterialID 127 | { 128 | explicit operator bool() const noexcept 129 | { 130 | return id != 0; 131 | } 132 | uint64_t id{}; 133 | }; 134 | 135 | struct MeshID 136 | { 137 | explicit operator bool() const noexcept 138 | { 139 | return id != 0; 140 | } 141 | uint64_t id{}; 142 | }; 143 | 144 | struct LightID 145 | { 146 | explicit operator bool() const noexcept 147 | { 148 | return id != 0; 149 | } 150 | uint64_t id{}; 151 | }; 152 | } -------------------------------------------------------------------------------- /data/shaders/bloom/BloomDownsampleCommon.h.glsl: -------------------------------------------------------------------------------- 1 | //#version 450 2 | #ifndef BLOOM_DOWNSAMPLE_COMMON_H 3 | #define BLOOM_DOWNSAMPLE_COMMON_H 4 | 5 | #include "BloomCommon.h.glsl" 6 | 7 | // FP16 support 8 | #if 0 9 | // #extension GL_EXT_shader_explicit_arithmetic_types : enable 10 | #extension GL_NV_gpu_shader5 : enable 11 | #extension GL_AMD_gpu_shader_half_float : enable 12 | #if !defined(GL_NV_gpu_shader5) && !defined(GL_AMD_gpu_shader_half_float) 13 | #define f16vec3 vec3 14 | #endif 15 | #else 16 | #define f16vec3 vec3 17 | #endif 18 | 19 | // Should be safe to have up to 32x32 (1024) threads (minimum guaranteed). 20 | // 32x32 threads will use slightly less than 32KB shared memory (minimum guaranteed) with full precision vectors (worst case). 21 | // Large groups suffer from barrier() overhead, especially when computing smaller mips. 22 | layout(local_size_x = LOCAL_SIZE_X, local_size_y = LOCAL_SIZE_Y) in; 23 | 24 | /* 25 | We take 13 bilinear samples of the source texture as such: 26 | 27 | O O O 28 | o o 29 | O X O 30 | o o 31 | O O O 32 | 33 | where X is the position of the pixel we are computing. 34 | Samples are intentionally staggered. 35 | */ 36 | 37 | // Cached samples corresponding to the large 'O's in the above image 38 | shared f16vec3 sh_coarseSamples[gl_WorkGroupSize.x + 2][gl_WorkGroupSize.y + 2]; 39 | 40 | // Cached samples corresponding to the small 'o's in the above image 41 | shared f16vec3 sh_preciseSamples[gl_WorkGroupSize.x + 1][gl_WorkGroupSize.y + 1]; 42 | 43 | void InitializeSharedMemory(ivec2 targetDim, ivec2 sourceDim, float sourceLod) 44 | { 45 | const ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 46 | const ivec2 lid = ivec2(gl_LocalInvocationID.xy); 47 | 48 | // Center of written pixel 49 | const vec2 uv = (vec2(gid) + 0.5) / targetDim; 50 | 51 | // Minimum caching for each output pixel 52 | sh_coarseSamples[lid.x + 1][lid.y + 1] = f16vec3(textureLod(s_source, uv + vec2(0, 0) / sourceDim, sourceLod).rgb); 53 | sh_preciseSamples[lid.x + 0][lid.y + 0] = f16vec3(textureLod(s_source, uv + vec2(-1, -1) / sourceDim, sourceLod).rgb); 54 | 55 | // Pixels on the edge of the thread group 56 | // Left 57 | if (lid.x == 0) 58 | { 59 | sh_coarseSamples[lid.x + 0][lid.y + 1] = f16vec3(textureLod(s_source, uv + vec2(-2, 0) / sourceDim, sourceLod).rgb); 60 | } 61 | 62 | // Right 63 | if (lid.x == gl_WorkGroupSize.x - 1) 64 | { 65 | sh_coarseSamples[lid.x + 2][lid.y + 1] = f16vec3(textureLod(s_source, uv + vec2(2, 0) / sourceDim, sourceLod).rgb); 66 | sh_preciseSamples[lid.x + 1][lid.y + 0] = f16vec3(textureLod(s_source, uv + vec2(1, -1) / sourceDim, sourceLod).rgb); 67 | } 68 | 69 | // Bottom 70 | if (lid.y == 0) 71 | { 72 | sh_coarseSamples[lid.x + 1][lid.y + 0] = f16vec3(textureLod(s_source, uv + vec2(0, -2) / sourceDim, sourceLod).rgb); 73 | } 74 | 75 | // Top 76 | if (lid.y == gl_WorkGroupSize.y - 1) 77 | { 78 | sh_coarseSamples[lid.x + 1][lid.y + 2] = f16vec3(textureLod(s_source, uv + vec2(0, 2) / sourceDim, sourceLod).rgb); 79 | sh_preciseSamples[lid.x + 0][lid.y + 1] = f16vec3(textureLod(s_source, uv + vec2(-1, 1) / sourceDim, sourceLod).rgb); 80 | } 81 | 82 | // Bottom-left 83 | if (lid.x == 0 && lid.y == 0) 84 | { 85 | sh_coarseSamples[lid.x + 0][lid.y + 0] = f16vec3(textureLod(s_source, uv + vec2(-2, -2) / sourceDim, sourceLod).rgb); 86 | } 87 | 88 | // Bottom-right 89 | if (lid.x == gl_WorkGroupSize.x - 1 && lid.y == 0) 90 | { 91 | sh_coarseSamples[lid.x + 2][lid.y + 0] = f16vec3(textureLod(s_source, uv + vec2(2, -2) / sourceDim, sourceLod).rgb); 92 | } 93 | 94 | // Top-left 95 | if (lid.x == 0 && lid.y == gl_WorkGroupSize.y - 1) 96 | { 97 | sh_coarseSamples[lid.x + 0][lid.y + 2] = f16vec3(textureLod(s_source, uv + vec2(-2, 2) / sourceDim, sourceLod).rgb); 98 | } 99 | 100 | // Top-right 101 | if (lid == gl_WorkGroupSize.xy - 1) 102 | { 103 | sh_coarseSamples[lid.x + 2][lid.y + 2] = f16vec3(textureLod(s_source, uv + vec2(2, 2) / sourceDim, sourceLod).rgb); 104 | sh_preciseSamples[lid.x + 1][lid.y + 1] = f16vec3(textureLod(s_source, uv + vec2(1, 1) / sourceDim, sourceLod).rgb); 105 | } 106 | } 107 | 108 | #endif // BLOOM_DOWNSAMPLE_COMMON_H -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Frogfood 2 | 3 | A froggylicious renderer. Check out [the wiki!](https://github.com/JuanDiegoMontoya/Frogfood/wiki) 4 | 5 | ## Features 6 | 7 | - Vulkan 1.3 8 | - Virtual shadow mapping (henceforth VSM shall stand for it and absolutely nothing else) 9 | - Meshlet rendering without mesh shaders 10 | - Visibility buffer deferred rendering (the scene is drawn to a 32-bit-per-pixel visibility buffer, then a standard G-buffer is generated) 11 | - Hardware-accelerated ray tracing: 12 | - Path traced global illumination 13 | - Ray traced ambient occlusion 14 | - Ray traced shadows 15 | - Super basic physically-based shading 16 | - Granular culling: 17 | - Meshlets: hi-z and frustum culling 18 | - Triangles: frustum, back-facing, and small primitive culling 19 | - Bloom based on the implementation in [Call of Duty: Advanced Warfare](https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare/) 20 | - Auto exposure based on vibes 21 | - Mediocre but not too terrible color handling and image formation 22 | - Epically fast glTF loading powered by [fastgltf](https://github.com/spnda/fastgltf) (on compilers that support std::execution::par (MSVC)) 23 | - HDR display support and wide gamut rendering 24 | 25 | ## OpenGL 26 | 27 | This renderer was previously written in OpenGL 4.6. The last commit using OpenGL is 439be52. As of writing, all the mentioned features were supported in the OpenGL version (including mesh shader-less meshlet rendering and VSMs). 28 | 29 | ## Building 30 | 31 | Get a modern version of CMake and do the `mkdir build && cd build && cmake ..` thing after cloning this repo. All dependencies are vendored or fetched with FetchContent. Should work on any sufficiently modern desktop GPU on Windows and Linux (I only test on Windows however). 32 | 33 | ## Obligatory Sponza 34 | 35 | ![A render from the lower floor of the Sponza palace's atrium, with sunlight hitting several different colored curtains and softly bouncing onto the floor](media/sponza_0.png) 36 | ![A shot of the Sponza atrium from the second floor. Several columns are visible. The scene is bathed in a dreamy blue light emanating from the entire sky, which the columns softly occlude](media/sponza_1.png) 37 | 38 | ## Pictures That Will Become Outdated Almost Immediately 39 | 40 | ![A scene featuring the exterior of the namesake bistro from the famous Lumberyard Bistro model](media/bistro_0.png) 41 | ![A showcase of basic editing features within the application's GUI. In the center is a viewport showing a large tree from the Lumberyard Bistro model. The tree and its surroundings are tinted by colored square tiles that increase in size with distance from the viewer. These square tiles are rendered when the debug toggle "Show Page Address" is enabled in the GUI.](media/bistro_1.png) 42 | 43 | ## Dependencies 44 | 45 | ### Fetched 46 | 47 | - [GLFW](https://github.com/glfw/glfw) 48 | - [GLM](https://github.com/g-truc/glm) 49 | - [volk](https://github.com/zeux/volk.git) 50 | - [vk-bootstrap](https://github.com/charles-lunarg/vk-bootstrap) 51 | - [Vulkan Memory Allocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator) 52 | - [fastgltf](https://github.com/spnda/fastgltf.git) 53 | - [KTX](https://github.com/KhronosGroup/KTX-Software.git) 54 | - [glslang](https://github.com/KhronosGroup/glslang.git) 55 | - [Dear ImGui](https://github.com/ocornut/imgui) 56 | - [ImPlot](https://github.com/epezent/implot.git) 57 | - [meshoptimizer](https://github.com/zeux/meshoptimizer.git) 58 | - [FidelityFX Super Resolution 2](https://github.com/JuanDiegoMontoya/FidelityFX-FSR2.git) (my fork) 59 | - [Tracy](https://github.com/wolfpld/tracy.git) 60 | 61 | ### Vendored 62 | 63 | - [Material Design Icons](https://github.com/google/material-design-icons/) 64 | - [Font Awesome 6](https://github.com/FortAwesome/Font-Awesome/) 65 | - [stb_image.h](https://github.com/nothings/stb) 66 | - [stb_include.h](https://github.com/nothings/stb) (the vendored version is heavily modified) 67 | - [Tony McMapFace LUT](https://github.com/h3r2tic/tony-mc-mapface) 68 | - [AgX shader](https://www.shadertoy.com/view/Dt3XDr) 69 | - Probably several more code snippets that I've forgotten about 70 | 71 | ## Acknowledgements 72 | 73 | Meshlet and visibility buffer rendering wouldn't have been possible without the early contributions by [LVSTRI](https://github.com/LVSTRI/). 74 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "") 4 | 5 | project(Frogfood) 6 | 7 | set(CMAKE_CXX_STANDARD 20) 8 | 9 | option(FROGRENDER_FSR2_ENABLE "Enable FidelityFX Super Resolution 2. Windows only!" FALSE) 10 | 11 | find_package(Vulkan REQUIRED) 12 | 13 | add_executable(frogRender 14 | src/main.cpp 15 | src/Application.cpp 16 | src/Application.h 17 | src/SceneLoader.cpp 18 | src/SceneLoader.h 19 | vendor/stb_image.cpp 20 | src/Gui.cpp 21 | src/PCG.h 22 | src/techniques/Bloom.h 23 | src/techniques/Bloom.cpp 24 | src/RendererUtilities.h 25 | src/RendererUtilities.cpp 26 | src/techniques/AutoExposure.h 27 | src/techniques/AutoExposure.cpp 28 | src/techniques/VirtualShadowMaps.h 29 | src/techniques/VirtualShadowMaps.cpp 30 | src/Fvog/Shader2.h 31 | src/Fvog/Shader2.cpp 32 | src/Fvog/detail/Common.h 33 | src/Fvog/detail/Common.cpp 34 | src/Fvog/Pipeline2.h 35 | src/Fvog/Pipeline2.cpp 36 | src/Fvog/BasicTypes2.h 37 | src/Fvog/detail/Flags.h 38 | src/Fvog/Texture2.h 39 | src/Fvog/Texture2.cpp 40 | src/Fvog/Device.h 41 | src/Fvog/Device.cpp 42 | src/Fvog/Rendering2.cpp 43 | src/Fvog/Rendering2.h 44 | src/Fvog/detail/ApiToEnum2.h 45 | src/Fvog/detail/ApiToEnum2.cpp 46 | src/Fvog/Buffer2.h 47 | src/Fvog/Buffer2.cpp 48 | src/FrogRenderer2.h 49 | src/FrogRenderer2.cpp 50 | src/Fvog/detail/SamplerCache2.h 51 | src/Fvog/detail/SamplerCache2.cpp 52 | src/Fvog/detail/Hash2.h 53 | src/Fvog/TriviallyCopyableByteSpan.h 54 | src/ImGui/imgui_impl_fvog.cpp 55 | src/ImGui/imgui_impl_fvog.h 56 | src/Fvog/Timer2.h 57 | src/Fvog/Timer2.cpp 58 | src/Scene.h 59 | src/Scene.cpp 60 | src/Renderables.h 61 | src/Fvog/AccelerationStructure.h 62 | src/Fvog/AccelerationStructure.cpp 63 | src/debug/ForwardRenderer.h 64 | src/debug/ForwardRenderer.cpp 65 | src/techniques/ao/RayTracedAO.h 66 | src/techniques/ao/RayTracedAO.cpp 67 | src/PipelineManager.h 68 | src/PipelineManager.cpp 69 | ) 70 | 71 | target_compile_options(frogRender 72 | PRIVATE 73 | $<$,$,$>: 74 | -Wall 75 | -Wextra 76 | -pedantic-errors 77 | -Wno-missing-field-initializers 78 | -Wno-unused-result 79 | > 80 | $<$: 81 | /W4 82 | /WX 83 | /permissive- 84 | /wd4324 # structure was padded 85 | > 86 | ) 87 | 88 | option(FROGRENDER_FORCE_COLORED_OUTPUT "Always produce ANSI-colored output (GNU/Clang only)." TRUE) 89 | if (${FORCE_COLORED_OUTPUT}) 90 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") 91 | add_compile_options(-fdiagnostics-color=always) 92 | elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 93 | add_compile_options(-fcolor-diagnostics) 94 | endif() 95 | endif() 96 | 97 | add_subdirectory(external) 98 | 99 | if (FROGRENDER_FSR2_ENABLE) 100 | set(FSR2_LIBS ffx_fsr2_api_x64 ffx_fsr2_api_vk_x64) 101 | target_compile_definitions(frogRender PUBLIC FROGRENDER_FSR2_ENABLE) 102 | else() 103 | set(FSR2_LIBS "") 104 | endif() 105 | 106 | target_compile_definitions(frogRender PUBLIC 107 | VMA_VULKAN_VERSION=1002000 # Allow VMA to use Vulkan 1.2 functions (BDA) 108 | ) 109 | 110 | target_include_directories(frogRender 111 | PUBLIC 112 | ${FSR2_SOURCE} 113 | ${IMPLOT_SOURCE} 114 | ${Vulkan_INCLUDE_DIRS} 115 | vendor 116 | data 117 | src 118 | ) 119 | 120 | target_link_libraries(frogRender 121 | PRIVATE 122 | glfw 123 | glm 124 | lib_imgui 125 | ${FSR2_LIBS} 126 | ktx 127 | fastgltf 128 | meshoptimizer 129 | Tracy::TracyClient 130 | lib_implot 131 | volk::volk 132 | vk-bootstrap::vk-bootstrap 133 | VulkanMemoryAllocator 134 | glslang 135 | glslang-default-resource-limits 136 | SPIRV 137 | ) 138 | 139 | target_compile_definitions(glm INTERFACE GLM_FORCE_DEPTH_ZERO_TO_ONE VK_NO_PROTOTYPES GLFW_INCLUDE_NONE ImTextureID=ImU64) 140 | 141 | if (MSVC) 142 | target_compile_definitions(frogRender PUBLIC STBI_MSC_SECURE_CRT) 143 | else() 144 | target_link_libraries(frogRender PRIVATE tbb) 145 | endif() 146 | -------------------------------------------------------------------------------- /src/debug/ForwardRenderer.cpp: -------------------------------------------------------------------------------- 1 | #include "ForwardRenderer.h" 2 | #include "RendererUtilities.h" 3 | #include "Fvog/Rendering2.h" 4 | #include "Application.h" 5 | #include 6 | #include 7 | 8 | namespace Debug 9 | { 10 | ForwardRenderer::ForwardRenderer() 11 | { 12 | uniformBuffer_.emplace(Fvog::TypedBufferCreateInfo{1}, "Forward Uniform Buffer"); 13 | } 14 | 15 | void ForwardRenderer::PushDraw(const Drawable& draw) 16 | { 17 | draws_.push_back(draw); 18 | } 19 | 20 | void ForwardRenderer::FlushAndRender(VkCommandBuffer commandBuffer, const ViewParams& view, const Fvog::TextureView& renderTarget, Fvog::Buffer& materialBuffer) 21 | { 22 | ZoneScoped; 23 | auto ctx = Fvog::Context(commandBuffer); 24 | 25 | auto rtExtent = renderTarget.GetTextureCreateInfo().extent; 26 | if (!depthTexture_ || depthTexture_->GetCreateInfo().extent != rtExtent) 27 | { 28 | depthTexture_ = Fvog::CreateTexture2D({rtExtent.width, rtExtent.height}, Fvog::Format::D32_SFLOAT, Fvog::TextureUsage::ATTACHMENT_READ_ONLY, "Forward Depth Texture"); 29 | } 30 | 31 | if (!pipeline_ || lastRenderTargetFormat != renderTarget.GetViewCreateInfo().format) 32 | { 33 | lastRenderTargetFormat = renderTarget.GetViewCreateInfo().format; 34 | 35 | // TODO: This leaks the pipeline :( 36 | pipeline_ = GetPipelineManager().EnqueueCompileGraphicsPipeline({ 37 | .name = "Forward Pipeline", 38 | .vertexModuleInfo = 39 | PipelineManager::ShaderModuleCreateInfo{ 40 | .stage = Fvog::PipelineStage::VERTEX_SHADER, 41 | .path = GetShaderDirectory() / "debug/Forward.vert.glsl", 42 | }, 43 | .fragmentModuleInfo = 44 | PipelineManager::ShaderModuleCreateInfo{ 45 | .stage = Fvog::PipelineStage::FRAGMENT_SHADER, 46 | .path = GetShaderDirectory() / "debug/Forward.frag.glsl", 47 | }, 48 | .state = 49 | { 50 | .depthState = 51 | { 52 | .depthTestEnable = true, 53 | .depthWriteEnable = true, 54 | .depthCompareOp = VK_COMPARE_OP_GREATER, 55 | }, 56 | .renderTargetFormats = 57 | { 58 | .colorAttachmentFormats = {{lastRenderTargetFormat}}, 59 | .depthAttachmentFormat = depthTexture_->GetCreateInfo().format, 60 | }, 61 | }, 62 | }); 63 | } 64 | 65 | ctx.Barrier(); 66 | ctx.ImageBarrierDiscard(depthTexture_.value(), VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL); 67 | 68 | const auto colorAttachment = Fvog::RenderColorAttachment{ 69 | .texture = renderTarget, 70 | .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, 71 | .clearValue = {.1f, .3f, .5f, 0.0f}, 72 | }; 73 | 74 | ctx.BeginRendering(Fvog::RenderInfo{ 75 | .name = "Forward Renderer", 76 | .colorAttachments = {{colorAttachment}}, 77 | .depthAttachment = Fvog::RenderDepthStencilAttachment{ 78 | .texture = depthTexture_.value().ImageView(), 79 | .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, 80 | .clearValue = {.depth = 0.0f}, 81 | }, 82 | }); 83 | 84 | ctx.BindGraphicsPipeline(pipeline_.GetPipeline()); 85 | 86 | auto sampler = Fvog::Sampler(Fvog::SamplerCreateInfo{ 87 | .magFilter = VK_FILTER_LINEAR, 88 | .minFilter = VK_FILTER_LINEAR, 89 | .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, 90 | .maxAnisotropy = 16, 91 | }); 92 | 93 | for (const auto& draw : draws_) 94 | { 95 | // Extremely efficient: make a buffer for every draw 96 | uniformBuffer_.emplace(Fvog::TypedBufferCreateInfo{.count = 1, .flag = Fvog::BufferFlagThingy::MAP_SEQUENTIAL_WRITE_DEVICE}, "Forward Uniform Buffer"); 97 | 98 | auto uniforms = Uniforms{ 99 | .clipFromWorld = view.clipFromWorld, 100 | .worldFromObject = draw.worldFromObject, 101 | .vertexBufferAddress = draw.vertexBufferAddress, 102 | .materialId = draw.materialId, 103 | .materialBufferIndex = materialBuffer.GetResourceHandle().index, 104 | .samplerIndex = sampler.GetResourceHandle().index, 105 | }; 106 | std::memcpy(uniformBuffer_.value().GetMappedMemory(), &uniforms, sizeof(Uniforms)); 107 | 108 | ctx.BindIndexBuffer(*draw.indexBuffer, draw.indexBufferOffset, VK_INDEX_TYPE_UINT32); 109 | ctx.SetPushConstants(uniformBuffer_.value().GetResourceHandle().index); 110 | ctx.DrawIndexed(draw.indexCount, 1, 0, 0, 0); 111 | } 112 | ctx.EndRendering(); 113 | 114 | ctx.Barrier(); 115 | 116 | draws_.clear(); 117 | } 118 | } // namespace Debug -------------------------------------------------------------------------------- /data/shaders/imgui/imgui.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #extension GL_EXT_nonuniform_qualifier : require 3 | #extension GL_EXT_scalar_block_layout : require 4 | 5 | #include "../Color.h.glsl" 6 | 7 | layout(push_constant, scalar) uniform PushConstants 8 | { 9 | uint vertexBufferIndex; 10 | uint textureIndex; 11 | uint samplerIndex; 12 | uint textureColorSpace; 13 | uint displayColorSpace; 14 | float maxDisplayNits; 15 | vec2 scale; 16 | vec2 translation; 17 | } pc; 18 | 19 | layout(set = 0, binding = 3) uniform texture2D textures[]; 20 | layout(set = 0, binding = 4) uniform sampler samplers[]; 21 | 22 | layout(location = 0) in struct { vec4 Color; vec2 UV; } In; 23 | 24 | layout(location = 0) out vec4 fColor; 25 | 26 | vec3 ConvertTextureToOutputColorSpace(vec3 color_in, uint in_color_space, uint out_color_space) 27 | { 28 | if (in_color_space == out_color_space) 29 | { 30 | return color_in; 31 | } 32 | 33 | // When the input texture is scRGB, we assume it's encoded as color * maxDisplayNits / 80. 34 | // To invert that (when necessary), we do color * 80 / maxDisplayNits. 35 | // Normally, this assumption wouldn't be correct, but we can assume that any texture encoded as scRGB will have been the output of the tonemap shader. 36 | switch(in_color_space) 37 | { 38 | case COLOR_SPACE_sRGB_NONLINEAR: 39 | switch(out_color_space) 40 | { 41 | case COLOR_SPACE_sRGB_LINEAR: return color_sRGB_EOTF(color_in); 42 | case COLOR_SPACE_sRGB_NONLINEAR: return color_in; 43 | case COLOR_SPACE_scRGB_LINEAR: return color_sRGB_EOTF(color_in) * pc.maxDisplayNits / 80.0; 44 | case COLOR_SPACE_BT2020_LINEAR: return color_convert_sRGB_to_BT2020(color_sRGB_EOTF(color_in)); 45 | case COLOR_SPACE_HDR10_ST2084: return color_PQ_OETF(color_convert_sRGB_to_BT2020(color_sRGB_EOTF(color_in)) * pc.maxDisplayNits / 10000.0); 46 | } 47 | break; 48 | case COLOR_SPACE_scRGB_LINEAR: 49 | switch(out_color_space) 50 | { 51 | case COLOR_SPACE_sRGB_LINEAR: return color_in * 80.0 / pc.maxDisplayNits; 52 | case COLOR_SPACE_sRGB_NONLINEAR: return color_sRGB_OETF(color_in * 80.0 / pc.maxDisplayNits); 53 | case COLOR_SPACE_scRGB_LINEAR: return color_in; 54 | case COLOR_SPACE_BT2020_LINEAR: return color_convert_sRGB_to_BT2020(color_in * 80.0 / pc.maxDisplayNits); 55 | case COLOR_SPACE_HDR10_ST2084: return color_PQ_OETF(color_convert_sRGB_to_BT2020(color_in * 80.0 / pc.maxDisplayNits) * pc.maxDisplayNits / 10000.0); 56 | } 57 | break; 58 | case COLOR_SPACE_HDR10_ST2084: 59 | switch(out_color_space) 60 | { 61 | case COLOR_SPACE_sRGB_LINEAR: return color_convert_BT2020_to_sRGB(color_PQ_EOTF(color_in) * 10000.0 / pc.maxDisplayNits); 62 | case COLOR_SPACE_sRGB_NONLINEAR: return color_sRGB_OETF(color_convert_BT2020_to_sRGB(color_PQ_EOTF(color_in) * 10000.0 / pc.maxDisplayNits)); 63 | case COLOR_SPACE_scRGB_LINEAR: return color_convert_BT2020_to_sRGB(color_PQ_EOTF(color_in) * 10000.0 / pc.maxDisplayNits) * pc.maxDisplayNits / 80.0; 64 | case COLOR_SPACE_BT2020_LINEAR: return color_PQ_EOTF(color_in) * 10000.0 / pc.maxDisplayNits; 65 | case COLOR_SPACE_HDR10_ST2084: return color_in; 66 | } 67 | break; 68 | case COLOR_SPACE_BT2020_LINEAR: 69 | switch(out_color_space) 70 | { 71 | case COLOR_SPACE_sRGB_LINEAR: return color_convert_BT2020_to_sRGB(color_in); 72 | case COLOR_SPACE_sRGB_NONLINEAR: return color_sRGB_OETF(color_convert_BT2020_to_sRGB(color_in)); 73 | case COLOR_SPACE_scRGB_LINEAR: return color_convert_BT2020_to_sRGB(color_in) * pc.maxDisplayNits / 80.0; 74 | case COLOR_SPACE_BT2020_LINEAR: return color_in; 75 | case COLOR_SPACE_HDR10_ST2084: return color_PQ_OETF(color_in * pc.maxDisplayNits / 10000.0); 76 | } 77 | break; 78 | case COLOR_SPACE_sRGB_LINEAR: 79 | switch(out_color_space) 80 | { 81 | case COLOR_SPACE_sRGB_LINEAR: return color_in; 82 | case COLOR_SPACE_sRGB_NONLINEAR: return color_sRGB_OETF(color_in); 83 | case COLOR_SPACE_scRGB_LINEAR: return color_in * pc.maxDisplayNits / 80.0; 84 | case COLOR_SPACE_BT2020_LINEAR: return color_convert_sRGB_to_BT2020(color_in); 85 | case COLOR_SPACE_HDR10_ST2084: return color_PQ_OETF(color_convert_sRGB_to_BT2020(color_in) * pc.maxDisplayNits / 10000.0); 86 | } 87 | break; 88 | } 89 | 90 | UNREACHABLE; 91 | return color_in; 92 | } 93 | 94 | void main() 95 | { 96 | // Assumes tint and texture are in the same color space 97 | vec4 color_raw = In.Color * texture(sampler2D(textures[pc.textureIndex], samplers[pc.samplerIndex]), In.UV); 98 | fColor.rgb = ConvertTextureToOutputColorSpace(color_raw.rgb, pc.textureColorSpace, pc.displayColorSpace); 99 | fColor.a = color_raw.a; 100 | } -------------------------------------------------------------------------------- /data/config/defaultLayout.ini: -------------------------------------------------------------------------------- 1 | [Window][DockSpace Demo] 2 | Pos=0,0 3 | Size=1920,1080 4 | Collapsed=0 5 | 6 | [Window][Debug##Default] 7 | Pos=60,60 8 | Size=400,400 9 | Collapsed=0 10 | 11 | [Window][Reflective Shadow Maps] 12 | Pos=1572,767 13 | Size=348,313 14 | Collapsed=0 15 | DockId=0x00000004,1 16 | 17 | [Window][###fsr2_window] 18 | Pos=1572,765 19 | Size=348,315 20 | Collapsed=0 21 | DockId=0x00000004,0 22 | 23 | [Window][glTF Viewer] 24 | Pos=1572,24 25 | Size=348,366 26 | Collapsed=0 27 | DockId=0x00000013,0 28 | 29 | [Window][###viewport_window] 30 | Pos=337,24 31 | Size=1233,764 32 | Collapsed=0 33 | DockId=0x00000011,0 34 | 35 | [Window][###mag] 36 | Pos=0,703 37 | Size=335,377 38 | Collapsed=0 39 | DockId=0x0000000C,0 40 | 41 | [Window][###debug_window] 42 | Pos=0,24 43 | Size=335,271 44 | Collapsed=0 45 | DockId=0x0000000D,1 46 | 47 | [Window][###lights_window] 48 | Pos=0,24 49 | Size=335,271 50 | Collapsed=0 51 | DockId=0x0000000D,0 52 | 53 | [Window][###bloom_window] 54 | Pos=1572,561 55 | Size=348,202 56 | Collapsed=0 57 | DockId=0x0000000A,0 58 | 59 | [Window][###auto_exposure_window] 60 | Pos=1572,381 61 | Size=348,178 62 | Collapsed=0 63 | DockId=0x00000009,1 64 | 65 | [Window][###camera_window] 66 | Pos=1572,381 67 | Size=348,178 68 | Collapsed=0 69 | DockId=0x00000009,0 70 | 71 | [Window][Dear ImGui Demo] 72 | Pos=337,24 73 | Size=1233,764 74 | Collapsed=0 75 | DockId=0x00000011,1 76 | 77 | [Window][ Shadow] 78 | Pos=1572,381 79 | Size=348,382 80 | Collapsed=0 81 | DockId=0x0000000A,0 82 | 83 | [Window][ Viewer##viewer_window] 84 | Pos=0,297 85 | Size=335,404 86 | Collapsed=0 87 | DockId=0x0000000E,0 88 | 89 | [Window][ Materials##materials_window] 90 | Pos=0,24 91 | Size=335,271 92 | Collapsed=0 93 | DockId=0x0000000D,2 94 | 95 | [Window][Perf##perf_window] 96 | Pos=337,784 97 | Size=1233,296 98 | Collapsed=0 99 | DockId=0x00000010,0 100 | 101 | [Window][Texture Viewer##viewer_window] 102 | Pos=0,297 103 | Size=335,404 104 | Collapsed=0 105 | DockId=0x0000000E,1 106 | 107 | [Window][Scene Graph##scene_graph_window] 108 | Pos=0,24 109 | Size=335,271 110 | Collapsed=0 111 | DockId=0x0000000D,0 112 | 113 | [Window][FPS Window] 114 | Pos=342,48 115 | Size=149,44 116 | Collapsed=0 117 | 118 | [Window][###component_editor_window] 119 | Pos=0,297 120 | Size=335,404 121 | Collapsed=0 122 | DockId=0x0000000E,0 123 | 124 | [Window][###hdr_window] 125 | Pos=0,24 126 | Size=335,271 127 | Collapsed=0 128 | DockId=0x0000000D,3 129 | 130 | [Window][###shadow_window] 131 | Pos=1572,392 132 | Size=348,371 133 | Collapsed=0 134 | DockId=0x00000014,0 135 | 136 | [Window][Performance##perf_window] 137 | Pos=337,790 138 | Size=1233,290 139 | Collapsed=0 140 | DockId=0x00000012,0 141 | 142 | [Window][###geometry_inspector] 143 | Pos=60,60 144 | Size=177,150 145 | Collapsed=0 146 | 147 | [Window][Ambient Occlusion] 148 | Pos=1572,392 149 | Size=348,371 150 | Collapsed=0 151 | DockId=0x00000014,1 152 | 153 | [Docking][Data] 154 | DockSpace ID=0x4BBE4C7A Window=0x4647B76E Pos=0,24 Size=1920,1056 Split=X Selected=0xE87781F4 155 | DockNode ID=0x00000005 Parent=0x4BBE4C7A SizeRef=335,1056 Split=Y Selected=0x6A2E32C2 156 | DockNode ID=0x0000000B Parent=0x00000005 SizeRef=213,677 Split=Y Selected=0x6A2E32C2 157 | DockNode ID=0x0000000D Parent=0x0000000B SizeRef=335,271 Selected=0xA95BCB0F 158 | DockNode ID=0x0000000E Parent=0x0000000B SizeRef=335,404 Selected=0x67C27239 159 | DockNode ID=0x0000000C Parent=0x00000005 SizeRef=213,377 Selected=0x213C89B5 160 | DockNode ID=0x00000006 Parent=0x4BBE4C7A SizeRef=1583,1056 Split=X 161 | DockNode ID=0x00000001 Parent=0x00000006 SizeRef=1233,1056 Split=Y Selected=0x91EB2F4F 162 | DockNode ID=0x0000000F Parent=0x00000001 SizeRef=1233,758 Split=Y Selected=0x91EB2F4F 163 | DockNode ID=0x00000011 Parent=0x0000000F SizeRef=1233,764 CentralNode=1 Selected=0x91EB2F4F 164 | DockNode ID=0x00000012 Parent=0x0000000F SizeRef=1233,290 Selected=0xED41464D 165 | DockNode ID=0x00000010 Parent=0x00000001 SizeRef=1233,296 Selected=0xE030F72F 166 | DockNode ID=0x00000002 Parent=0x00000006 SizeRef=348,1056 Split=Y Selected=0xFCCC74B6 167 | DockNode ID=0x00000003 Parent=0x00000002 SizeRef=348,739 Split=Y Selected=0xFCCC74B6 168 | DockNode ID=0x00000007 Parent=0x00000003 SizeRef=348,355 Split=Y Selected=0xBE92E3B2 169 | DockNode ID=0x00000013 Parent=0x00000007 SizeRef=348,366 Selected=0xBE92E3B2 170 | DockNode ID=0x00000014 Parent=0x00000007 SizeRef=348,371 Selected=0x4D526E22 171 | DockNode ID=0x00000008 Parent=0x00000003 SizeRef=348,382 Split=Y Selected=0xFCCC74B6 172 | DockNode ID=0x00000009 Parent=0x00000008 SizeRef=348,178 Selected=0x4F8C3FAB 173 | DockNode ID=0x0000000A Parent=0x00000008 SizeRef=348,202 Selected=0x21E9B10B 174 | DockNode ID=0x00000004 Parent=0x00000002 SizeRef=348,315 Selected=0xE83C69A2 175 | 176 | -------------------------------------------------------------------------------- /external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # external content definitions 2 | include(FetchContent) 3 | 4 | option(GLFW_BUILD_TESTS "" OFF) 5 | option(GLFW_BUILD_DOCS "" OFF) 6 | option(GLFW_INSTALL "" OFF) 7 | option(GLFW_BUILD_EXAMPLES "" OFF) 8 | FetchContent_Declare( 9 | glfw 10 | GIT_REPOSITORY https://github.com/glfw/glfw 11 | GIT_TAG 3.4 12 | ) 13 | 14 | FetchContent_Declare( 15 | glm 16 | GIT_REPOSITORY https://github.com/g-truc/glm 17 | GIT_TAG 1.0.1 18 | ) 19 | 20 | FetchContent_Declare( 21 | fastgltf 22 | GIT_REPOSITORY https://github.com/spnda/fastgltf.git 23 | GIT_TAG 916723260c6c064a7c5350f768a07187499d509c 24 | ) 25 | 26 | FetchContent_Declare( 27 | imgui 28 | GIT_REPOSITORY https://github.com/ocornut/imgui 29 | GIT_TAG fdc084f532189fda8474079f79e74fa5e3541c9f 30 | ) 31 | 32 | FetchContent_Declare( 33 | meshoptimizer 34 | GIT_REPOSITORY https://github.com/zeux/meshoptimizer.git 35 | GIT_TAG v0.19 36 | ) 37 | 38 | FetchContent_Declare( 39 | implot 40 | GIT_REPOSITORY https://github.com/epezent/implot.git 41 | GIT_TAG v0.16 42 | ) 43 | 44 | option(KTX_FEATURE_TESTS "" OFF) 45 | option(KTX_FEATURE_VULKAN "" OFF) 46 | option(KTX_FEATURE_GL_UPLOAD "" OFF) 47 | option(KTX_FEATURE_VK_UPLOAD "" OFF) 48 | option(KTX_FEATURE_WRITE "" OFF) 49 | option(KTX_FEATURE_TOOLS "" OFF) 50 | option(KTX_FEATURE_STATIC_LIBRARY "" ON) 51 | FetchContent_Declare( 52 | ktx 53 | GIT_REPOSITORY https://github.com/KhronosGroup/KTX-Software.git 54 | GIT_TAG v4.1.0 55 | SYSTEM 56 | ) 57 | 58 | FetchContent_Declare( 59 | volk 60 | GIT_REPOSITORY https://github.com/zeux/volk.git 61 | GIT_TAG 1.3.270 62 | ) 63 | 64 | FetchContent_Declare( 65 | vkbootstrap 66 | GIT_REPOSITORY https://github.com/charles-lunarg/vk-bootstrap 67 | GIT_TAG v1.3.296 68 | ) 69 | 70 | # Since we are loading Vulkan with Volk, do not load it from a static library or DLL 71 | set(VMA_STATIC_VULKAN_FUNCTIONS OFF CACHE BOOL "" FORCE) 72 | set(VMA_DYNAMIC_VULKAN_FUNCTIONS OFF CACHE BOOL "" FORCE) 73 | FetchContent_Declare( 74 | vma 75 | GIT_REPOSITORY https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator 76 | GIT_TAG v3.1.0 77 | SYSTEM 78 | ) 79 | 80 | option(ENABLE_OPT "" OFF) 81 | FetchContent_Declare( 82 | glslang 83 | GIT_REPOSITORY https://github.com/KhronosGroup/glslang.git 84 | GIT_TAG 5398d55e33dff7d26fecdd2c35808add986c558c 85 | #SYSTEM 86 | ) 87 | 88 | if(FROGRENDER_FSR2_ENABLE) 89 | option(GFX_API_DX12 "" OFF) 90 | option(GFX_API_VK "" OFF) 91 | option(GFX_API_GL "" OFF) 92 | option(FFX_FSR2_API_DX12 "" OFF) 93 | option(FFX_FSR2_API_VK "" ON) 94 | option(FFX_FSR2_API_GL "" OFF) 95 | FetchContent_Declare( 96 | fsr2 97 | GIT_REPOSITORY https://github.com/JuanDiegoMontoya/FidelityFX-FSR2.git 98 | GIT_TAG de42d95ffed4be263629950a6b04ba6e71215931 99 | GIT_SUBMODULES "" 100 | ) 101 | FetchContent_MakeAvailable(fsr2) 102 | endif() 103 | 104 | FetchContent_Declare( 105 | tracy 106 | GIT_REPOSITORY https://github.com/wolfpld/tracy.git 107 | GIT_TAG v0.11.1 108 | ) 109 | 110 | option(TRACY_ENABLE "Enable profiling" ON) 111 | option(TRACY_ONLY_IPV4 "IPv4 only" ON) 112 | 113 | FetchContent_MakeAvailable(glm fastgltf ktx meshoptimizer tracy volk vkbootstrap vma glslang) 114 | 115 | set(FSR2_SOURCE ${fsr2_SOURCE_DIR} PARENT_SCOPE) 116 | 117 | FetchContent_GetProperties(imgui) 118 | if(NOT imgui_POPULATED) 119 | FetchContent_Populate(imgui) 120 | 121 | add_library(lib_imgui 122 | ${imgui_SOURCE_DIR}/imgui.cpp 123 | ${imgui_SOURCE_DIR}/imgui_demo.cpp 124 | ${imgui_SOURCE_DIR}/imgui_draw.cpp 125 | ${imgui_SOURCE_DIR}/imgui_widgets.cpp 126 | ${imgui_SOURCE_DIR}/imgui_tables.cpp 127 | ${imgui_SOURCE_DIR}/backends/imgui_impl_glfw.cpp) 128 | 129 | target_include_directories(lib_imgui PUBLIC 130 | ${imgui_SOURCE_DIR} 131 | ${imgui_SOURCE_DIR}/backends 132 | ${Vulkan_INCLUDE_DIRS} 133 | ${glfw_SOURCE_DIR}/include) 134 | 135 | target_compile_definitions(lib_imgui PRIVATE VK_NO_PROTOTYPES ImTextureID=ImU64) 136 | target_link_libraries(lib_imgui PRIVATE glfw volk::volk) 137 | endif() 138 | 139 | FetchContent_GetProperties(implot) 140 | if(NOT implot_POPULATED) 141 | FetchContent_Populate(implot) 142 | 143 | add_library(lib_implot 144 | ${implot_SOURCE_DIR}/implot.cpp 145 | ${implot_SOURCE_DIR}/implot_items.cpp 146 | ${implot_SOURCE_DIR}/implot_demo.cpp 147 | ) 148 | 149 | target_include_directories(lib_implot PUBLIC 150 | ${implot_SOURCE_DIR} 151 | ${imgui_SOURCE_DIR} # ImPlot must see ImGui headers 152 | ) 153 | 154 | target_compile_definitions(lib_implot PRIVATE ImTextureID=ImU64) 155 | endif() 156 | 157 | set(IMPLOT_SOURCE ${implot_SOURCE_DIR} PARENT_SCOPE) 158 | 159 | FetchContent_MakeAvailable(glfw) 160 | 161 | target_compile_definitions(glm INTERFACE GLM_FORCE_SILENT_WARNINGS) -------------------------------------------------------------------------------- /src/Application.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Fvog/Device.h" 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define GLM_ENABLE_EXPERIMENTAL 16 | #include 17 | #include 18 | #include 19 | 20 | struct GLFWwindow; 21 | 22 | namespace tracy 23 | { 24 | class VkCtx; 25 | } 26 | 27 | // Represents the camera's position and orientation. 28 | struct View 29 | { 30 | glm::vec3 position{}; 31 | float pitch{}; // pitch angle in radians 32 | float yaw{}; // yaw angle in radians 33 | 34 | glm::vec3 GetForwardDir() const 35 | { 36 | return glm::vec3{cos(pitch) * cos(yaw), sin(pitch), cos(pitch) * sin(yaw)}; 37 | } 38 | 39 | glm::mat4 GetViewMatrix() const 40 | { 41 | return glm::lookAt(position, position + GetForwardDir(), glm::vec3(0, 1, 0)); 42 | } 43 | }; 44 | 45 | // List of functions to execute in reverse order in its destructor 46 | class DestroyList 47 | { 48 | public: 49 | DestroyList() = default; 50 | void Push(std::function fn); 51 | ~DestroyList(); 52 | 53 | DestroyList(const DestroyList&) = delete; 54 | DestroyList(DestroyList&&) noexcept = delete; 55 | DestroyList&& operator=(const DestroyList&) = delete; 56 | DestroyList&& operator=(DestroyList&&) noexcept = delete; 57 | 58 | private: 59 | std::vector> destructorList; 60 | }; 61 | 62 | class Application 63 | { 64 | public: 65 | struct CreateInfo 66 | { 67 | std::string_view name = ""; 68 | bool maximize = false; 69 | bool decorate = true; 70 | VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; 71 | }; 72 | 73 | // TODO: An easy way to load shaders should probably be a part of Fwog 74 | static std::string LoadFile(const std::filesystem::path& path); 75 | static std::pair, std::size_t> LoadBinaryFile(const std::filesystem::path& path); 76 | 77 | Application(const CreateInfo& createInfo); 78 | Application(const Application&) = delete; 79 | Application(Application&&) noexcept = delete; 80 | Application& operator=(const Application&) = delete; 81 | Application& operator=(Application&&) noexcept = delete; 82 | 83 | virtual ~Application(); 84 | 85 | void Run(); 86 | 87 | protected: 88 | // Create swapchain size-dependent resources 89 | virtual void OnFramebufferResize([[maybe_unused]] uint32_t newWidth, [[maybe_unused]] uint32_t newHeight){} 90 | virtual void OnUpdate([[maybe_unused]] double dt){} 91 | virtual void OnRender( 92 | [[maybe_unused]] double dt, 93 | [[maybe_unused]] VkCommandBuffer commandBuffer, 94 | [[maybe_unused]] uint32_t swapchainImageIndex) {} 95 | virtual void OnGui([[maybe_unused]] double dt, [[maybe_unused]] VkCommandBuffer commandBuffer) {} 96 | virtual void OnPathDrop([[maybe_unused]] std::span paths){} 97 | 98 | // destroyList will be the last object to be automatically destroyed after the destructor returns 99 | DestroyList destroyList_; 100 | vkb::Instance instance_{}; 101 | VkSurfaceKHR surface_{}; 102 | VkDescriptorPool imguiDescriptorPool_{}; 103 | vkb::Swapchain swapchain_{}; 104 | std::vector swapchainImages_; 105 | std::vector swapchainImageViews_; 106 | std::vector availableSurfaceFormats_; 107 | static constexpr VkSurfaceFormatKHR defaultSwapchainFormat = {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}; 108 | VkSurfaceFormatKHR swapchainFormat_ = defaultSwapchainFormat; // Only Application should modify this 109 | VkSurfaceFormatKHR nextSwapchainFormat_ = swapchainFormat_; // Workaround to prevent ImGui backend from using incorrect pipeline layout after changing swapchainFormat in GUI 110 | float maxDisplayNits = 200.0f; 111 | 112 | tracy::VkCtx* tracyVkContext_{}; 113 | GLFWwindow* window; 114 | View mainCamera{}; 115 | float cursorSensitivity = 0.0025f; 116 | float cameraSpeed = 4.5f; 117 | bool cursorIsActive = true; 118 | 119 | uint32_t windowFramebufferWidth{}; 120 | uint32_t windowFramebufferHeight{}; 121 | 122 | glm::dvec2 cursorPos{}; 123 | 124 | // Resizing from UI is deferred until next frame so texture handles remain valid when ImGui is rendered 125 | bool shouldResizeNextFrame = false; 126 | bool shouldRemakeSwapchainNextFrame = false; 127 | VkPresentModeKHR presentMode; 128 | uint32_t numSwapchainImages = 3; 129 | 130 | private: 131 | friend class ApplicationAccess; 132 | 133 | void RemakeSwapchain(uint32_t newWidth, uint32_t newHeight); 134 | void Draw(); 135 | double timeOfLastDraw = 0; 136 | 137 | glm::dvec2 cursorFrameOffset{}; 138 | bool cursorJustEnteredWindow = true; 139 | bool graveHeldLastFrame = false; 140 | bool swapchainOk = true; 141 | }; 142 | 143 | std::filesystem::path GetAssetDirectory(); 144 | std::filesystem::path GetShaderDirectory(); 145 | std::filesystem::path GetTextureDirectory(); 146 | std::filesystem::path GetConfigDirectory(); 147 | -------------------------------------------------------------------------------- /data/shaders/Pbr.h.glsl: -------------------------------------------------------------------------------- 1 | #ifndef PBR_H 2 | #define PBR_H 3 | 4 | #include "Math.h.glsl" 5 | #include "Color.h.glsl" 6 | #include "ShadeDeferredPbr.h.glsl" 7 | 8 | // Punctual light attenuation factor 9 | float GetSquareFalloffAttenuation(vec3 posToLight, float lightInvRadius) 10 | { 11 | float distanceSquared = dot(posToLight, posToLight); 12 | float factor = distanceSquared * lightInvRadius * lightInvRadius; 13 | float smoothFactor = max(1.0 - factor * factor, 0.0); 14 | return (smoothFactor * smoothFactor) / max(distanceSquared, 1e-4); 15 | } 16 | 17 | // Spot light angle attenuation factor. 18 | float GetSpotAngleAttenuation(float innerConeAngle, float outerConeAngle, vec3 lightDirection, vec3 L) 19 | { 20 | float lightAngleScale = 1.0f / max(0.001, cos(innerConeAngle) - cos(outerConeAngle)); 21 | float lightAngleOffset = -cos(outerConeAngle) * lightAngleScale; 22 | 23 | float cd = dot(-lightDirection, L); 24 | float angularAttenuation = clamp(cd * lightAngleScale + lightAngleOffset, 0.0, 1.0); 25 | 26 | // Real spot angle attenuation is complex, but we note that it generally declines rapidly, so squaring linear attenutation is a reasonable approximation 27 | return angularAttenuation * angularAttenuation; 28 | } 29 | 30 | // BRDF 31 | // Normal distribution function (appearance of specular highlights) 32 | float D_GGX(float NoH, float roughness) 33 | { 34 | // Hack to make perfectly smooth materials display a non-infinitesimal specular highlight 35 | //roughness = max(roughness, 1e-3); 36 | 37 | // Hack to prevent zero in the denominator 38 | //NoH = min(NoH, 0.9999); 39 | 40 | float a = NoH * roughness; 41 | float k = roughness / clamp(1.0 - NoH * NoH + a * a, 0.0001, 0.9999); 42 | return k * k * (1.0 / M_PI); 43 | } 44 | 45 | // Visibility (geometric shadowing and masking) 46 | float V_SmithGGXCorrelated(float NoV, float NoL, float a) 47 | { 48 | float a2 = max(a * a, 1e-5); 49 | float ggxl = NoV * sqrt((-NoL * a2 + NoL) * NoL + a2); 50 | float ggxv = NoL * sqrt((-NoV * a2 + NoV) * NoV + a2); 51 | return 0.5 / (ggxv + ggxl); 52 | } 53 | 54 | // Fresnel. f0 = reflectance of material at normal incidence. f90 = same thing, but 90 degrees (grazing) incidence 55 | // TODO: use better Fresnel term 56 | float F_Schlick1(float u, float f0, float f90) 57 | { 58 | return f0 + (f90 - f0) * pow(1.0 - u, 5.0); 59 | } 60 | 61 | vec3 F_Schlick3(float u, vec3 f0, float f90) 62 | { 63 | return f0 + (vec3(f90) - f0) * pow(1.0 - u, 5.0); 64 | } 65 | 66 | // Lambertian diffuse BRDF 67 | float Fd_Lambert() 68 | { 69 | return 1.0 / M_PI; 70 | } 71 | 72 | // Disney diffuse BRDF. Apparently not energy-conserving, but takes into account surface roughness, unlike Lambert's 73 | float Fd_Burley(float NoV, float NoL, float LoH, float roughness) 74 | { 75 | float f90 = 0.5 + 2.0 * roughness * LoH * LoH; 76 | float lightScatter = F_Schlick1(NoL, 1.0, f90); 77 | float viewScatter = F_Schlick1(NoV, 1.0, f90); 78 | return lightScatter * viewScatter * (1.0 / M_PI); 79 | } 80 | 81 | struct Surface 82 | { 83 | vec3 albedo; 84 | vec3 normal; 85 | vec3 position; 86 | float metallic; 87 | float perceptualRoughness; 88 | float reflectance; // = 0.5 89 | float f90; 90 | }; 91 | 92 | vec3 BRDF(vec3 viewDir, vec3 L, Surface surface) 93 | { 94 | vec3 f0 = 0.16 * surface.reflectance * surface.reflectance * (1.0 - surface.metallic) + surface.albedo * surface.metallic; 95 | vec3 H = normalize(viewDir + L); 96 | 97 | float NoV = abs(dot(surface.normal, viewDir)) + 1e-5; 98 | float NoL = clamp(dot(surface.normal, L), 0.0, 1.0); 99 | float NoH = clamp(dot(surface.normal, H), 0.0, 1.0); 100 | float LoH = clamp(dot(L, H), 0.0, 1.0); 101 | 102 | // Perceptually linear roughness to roughness (see parameterization) 103 | float roughness = surface.perceptualRoughness * surface.perceptualRoughness; 104 | 105 | float D = D_GGX(NoH, roughness); 106 | vec3 F = F_Schlick3(LoH, f0, 1.0); 107 | float V = V_SmithGGXCorrelated(NoV, NoL, roughness); 108 | 109 | // Specular BRDF (Cook-Torrance) 110 | vec3 Fr = D * V * F; 111 | 112 | // Diffuse BRDF 113 | vec3 Fd = surface.albedo * Fd_Lambert(); 114 | //vec3 Fd = surface.albedo * Fd_Burley(NoV, NoL, LoH, roughness); 115 | 116 | // Combine BRDFs according to very scientific formula 117 | return Fr + Fd * (vec3(1) - F) * (1.0 - surface.metallic); 118 | } 119 | 120 | vec3 EvaluatePunctualLight(vec3 viewDir, GpuLight light, Surface surface, uint internalColorSpace) 121 | { 122 | vec3 surfaceToLight = light.position - surface.position; 123 | vec3 L = normalize(surfaceToLight); 124 | float NoL = clamp(dot(surface.normal, L), 0.0, 1.0); 125 | 126 | float attenuation = GetSquareFalloffAttenuation(surfaceToLight, 1.0 / light.range); 127 | 128 | if (light.type == LIGHT_TYPE_SPOT) 129 | { 130 | attenuation *= GetSpotAngleAttenuation(light.innerConeAngle, light.outerConeAngle, light.direction, L); 131 | } 132 | 133 | vec3 lightColor_internal_space = color_convert_src_to_dst(light.color, 134 | light.colorSpace, 135 | internalColorSpace); 136 | return BRDF(viewDir, L, surface) * attenuation * NoL * lightColor_internal_space * light.intensity; 137 | } 138 | 139 | #endif // PBR_H -------------------------------------------------------------------------------- /data/shaders/ShadeDeferredPbr.h.glsl: -------------------------------------------------------------------------------- 1 | #ifndef SHADE_DEFERRED_PBR_H 2 | #define SHADE_DEFERRED_PBR_H 3 | 4 | #include "Resources.h.glsl" 5 | #include "Color.h.glsl" 6 | 7 | #if defined(__cplusplus) || defined(SHADING_PUSH_CONSTANTS) 8 | #ifdef __cplusplus 9 | using namespace shared; 10 | #endif 11 | FVOG_DECLARE_ARGUMENTS(ShadingPushConstants) 12 | { 13 | FVOG_UINT32 globalUniformsIndex; 14 | FVOG_UINT32 shadingUniformsIndex; 15 | FVOG_UINT32 shadowUniformsIndex; 16 | FVOG_UINT32 lightBufferIndex; 17 | 18 | FVOG_UINT32 gAlbedoIndex; 19 | FVOG_UINT32 gNormalAndFaceNormalIndex; 20 | FVOG_UINT32 gDepthIndex; 21 | FVOG_UINT32 gSmoothVertexNormalIndex; 22 | FVOG_UINT32 gEmissionIndex; 23 | FVOG_UINT32 gMetallicRoughnessAoIndex; 24 | Texture2D ambientOcclusion; 25 | 26 | FVOG_UINT32 pageTablesIndex; 27 | FVOG_UINT32 physicalPagesIndex; 28 | FVOG_UINT32 vsmBitmaskHzbIndex; 29 | FVOG_UINT32 vsmUniformsBufferIndex; 30 | FVOG_UINT32 dirtyPageListBufferIndex; 31 | FVOG_UINT32 clipmapUniformsBufferIndex; 32 | FVOG_UINT32 nearestSamplerIndex; 33 | 34 | FVOG_UINT32 physicalPagesOverdrawIndex; 35 | Buffer debugLinesBuffer; 36 | }; 37 | #endif 38 | 39 | #define LIGHT_TYPE_DIRECTIONAL 0u 40 | #define LIGHT_TYPE_POINT 1u 41 | #define LIGHT_TYPE_SPOT 2u 42 | 43 | struct GpuLight 44 | { 45 | #ifdef __cplusplus 46 | GpuLight() : 47 | colorSpace(COLOR_SPACE_sRGB_LINEAR) 48 | {} 49 | bool operator==(const GpuLight&) const noexcept = default; 50 | #endif 51 | FVOG_VEC3 color; 52 | FVOG_UINT32 type; 53 | FVOG_VEC3 direction; // Directional and spot only 54 | // Point and spot lights use candela (lm/sr) while directional use lux (lm/m^2) 55 | FVOG_FLOAT intensity; 56 | FVOG_VEC3 position; // Point and spot only 57 | FVOG_FLOAT range; // Point and spot only 58 | FVOG_FLOAT innerConeAngle; // Spot only 59 | FVOG_FLOAT outerConeAngle; // Spot only 60 | FVOG_UINT32 colorSpace; // sRGB_LINEAR or BT2020_LINEAR only 61 | FVOG_UINT32 _padding; 62 | }; 63 | 64 | #define SHADOW_MODE_VIRTUAL_SHADOW_MAP 0 65 | #define SHADOW_MODE_RAY_TRACED 1 66 | 67 | #define SHADOW_MAP_FILTER_NONE 0 68 | #define SHADOW_MAP_FILTER_PCSS 1 69 | #define SHADOW_MAP_FILTER_SMRT 2 70 | 71 | struct ShadowUniforms 72 | { 73 | #ifdef __cplusplus 74 | ShadowUniforms() : 75 | shadowMode(SHADOW_MODE_VIRTUAL_SHADOW_MAP), 76 | shadowMapFilter(SHADOW_MAP_FILTER_PCSS), 77 | 78 | pcfSamples(16), 79 | lightWidth(0.002f), // The sun's real angular radius is about 0.0087 radians. 80 | maxPcfRadius(0.032f), 81 | blockerSearchSamples(16), 82 | blockerSearchRadius(0.032f), 83 | 84 | shadowRays(7), 85 | stepsPerRay(7), 86 | rayStepSize(0.1f), 87 | heightmapThickness(0.5f), 88 | sourceAngleRad(0.05f), 89 | 90 | rtNumSunShadowRays(1), 91 | rtSunDiameterRadians(0.0087f), 92 | rtTraceLocalLights(false) 93 | {} 94 | #endif 95 | 96 | FVOG_UINT32 shadowMode; 97 | FVOG_UINT32 shadowMapFilter; 98 | 99 | // PCSS 100 | FVOG_UINT32 pcfSamples; 101 | FVOG_FLOAT lightWidth; 102 | FVOG_FLOAT maxPcfRadius; 103 | FVOG_UINT32 blockerSearchSamples; 104 | FVOG_FLOAT blockerSearchRadius; 105 | 106 | // SMRT 107 | FVOG_UINT32 shadowRays; 108 | FVOG_UINT32 stepsPerRay; 109 | FVOG_FLOAT rayStepSize; 110 | FVOG_FLOAT heightmapThickness; 111 | FVOG_FLOAT sourceAngleRad; 112 | 113 | // Ray traced 114 | FVOG_UINT32 rtNumSunShadowRays; 115 | FVOG_FLOAT rtSunDiameterRadians; 116 | FVOG_UINT32 rtTraceLocalLights; 117 | }; 118 | 119 | #define VSM_SHOW_CLIPMAP_ID (1 << 0) 120 | #define VSM_SHOW_PAGE_ADDRESS (1 << 1) 121 | #define VSM_SHOW_PAGE_OUTLINES (1 << 2) 122 | #define VSM_SHOW_SHADOW_DEPTH (1 << 3) 123 | #define VSM_SHOW_DIRTY_PAGES (1 << 4) 124 | #define BLEND_NORMALS (1 << 5) 125 | #define VSM_SHOW_OVERDRAW (1 << 6) 126 | #define SHOW_AO_ONLY (1 << 7) 127 | 128 | #define GI_METHOD_CONSTANT_AMBIENT 1 129 | #define GI_METHOD_PATH_TRACED 2 130 | 131 | struct ShadingUniforms 132 | { 133 | #ifdef __cplusplus 134 | ShadingUniforms() : 135 | ambientIlluminance(1, 1, 1, 0.03f), 136 | skyIlluminance(.1f, .3f, .5f, 1.0f), 137 | debugFlags(0), 138 | shadingInternalColorSpace(COLOR_SPACE_BT2020_LINEAR), 139 | captureActive(0), 140 | globalIlluminationMethod(GI_METHOD_CONSTANT_AMBIENT), 141 | numGiRays(1), 142 | numGiBounces(3) 143 | {} 144 | #endif 145 | 146 | FVOG_VEC4 sunDir; 147 | FVOG_VEC4 sunIlluminance; 148 | FVOG_VEC4 ambientIlluminance; 149 | FVOG_VEC4 skyIlluminance; 150 | FVOG_VEC2 random; 151 | FVOG_UINT32 numberOfLights; 152 | FVOG_UINT32 debugFlags; 153 | FVOG_UINT32 shadingInternalColorSpace; 154 | 155 | // TODO: temp rt stuff 156 | FVOG_UINT32 materialBufferIndex; 157 | FVOG_UINT32 instanceBufferIndex; 158 | FVOG_UINT32 tlasIndex; 159 | FVOG_IVEC2 captureRayPos; 160 | FVOG_UINT64 tlasAddress; 161 | FVOG_BOOL32 captureActive; // 1 == capture rays from one invocation, 2 == capture rays from all invocations 162 | 163 | FVOG_UINT32 globalIlluminationMethod; // For indirect lighting only, but named this way for consistency 164 | FVOG_UINT32 numGiRays; // TEMP 165 | FVOG_UINT32 numGiBounces; // TEMP 166 | Texture2D noiseTexture; 167 | FVOG_UINT32 frameNumber; 168 | }; 169 | 170 | #endif // SHADE_DEFERRED_PBR_H -------------------------------------------------------------------------------- /data/shaders/Math.h.glsl: -------------------------------------------------------------------------------- 1 | #ifndef MATH_H 2 | #define MATH_H 3 | 4 | #include "Hash.h.glsl" 5 | 6 | // Constants 7 | const float M_PI = 3.141592654; 8 | 9 | 10 | // Functions 11 | vec3 PolarToCartesian(float phi, float theta) 12 | { 13 | const float sinTheta = sin(theta); 14 | return vec3( 15 | cos(phi) * sinTheta, 16 | sin(phi) * sinTheta, 17 | cos(theta) 18 | ); 19 | } 20 | 21 | // PDF: solid_angle_mapping_PDF (see bottom of this file) 22 | vec3 RandVecInCone(vec2 xi, vec3 N, float angle) 23 | { 24 | float phi = 2.0 * M_PI * xi.x; 25 | 26 | float theta = sqrt(xi.y) * angle; 27 | float cosTheta = cos(theta); 28 | float sinTheta = sin(theta); 29 | 30 | vec3 H; 31 | H.x = cos(phi) * sinTheta; 32 | H.y = sin(phi) * sinTheta; 33 | H.z = cosTheta; 34 | 35 | vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); 36 | vec3 tangent = normalize(cross(up, N)); 37 | vec3 bitangent = cross(N, tangent); 38 | mat3 tbn = mat3(tangent, bitangent, N); 39 | 40 | vec3 sampleVec = tbn * H; 41 | return normalize(sampleVec); 42 | } 43 | 44 | float hash(vec2 n) 45 | { 46 | return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453); 47 | } 48 | 49 | vec2 Hammersley(uint i, uint N) 50 | { 51 | return vec2(float(i) / float(N), float(bitfieldReverse(i)) * 2.3283064365386963e-10); 52 | } 53 | 54 | // Zero-to-one depth convention 55 | vec3 UnprojectUV_ZO(float depth, vec2 uv, mat4 invXProj) 56 | { 57 | vec4 ndc = vec4(uv * 2.0 - 1.0, depth, 1.0); 58 | vec4 world = invXProj * ndc; 59 | return world.xyz / world.w; 60 | } 61 | 62 | float Remap(float val, float start1, float end1, float start2, float end2) 63 | { 64 | return (val - start1) / (end1 - start1) * (end2 - start2) + start2; 65 | } 66 | 67 | // TODO: this should probably just multiply dxuv and dyuv by exp2(bias) 68 | void ApplyLodBiasToGradient(inout vec2 dxuv, inout vec2 dyuv, float bias) 69 | { 70 | float ddx2 = dot(dxuv, dxuv); 71 | float ddy2 = dot(dyuv, dyuv); 72 | float actual_mip = exp2(bias + 0.5 * log2(max(ddx2, ddy2))); 73 | float min_mip = sqrt(min(ddx2, ddy2)); 74 | dxuv *= actual_mip / min_mip; 75 | dyuv *= actual_mip / min_mip; 76 | } 77 | 78 | bool RectIntersectRect(vec2 bottomLeft0, vec2 topRight0, vec2 bottomLeft1, vec2 topRight1) 79 | { 80 | return !(any(lessThan(topRight0, bottomLeft1)) || any(greaterThan(bottomLeft0, topRight1))); 81 | } 82 | 83 | // Hashed Alpha Testing 84 | // https://casual-effects.com/research/Wyman2017Hashed/Wyman2017Hashed.pdf 85 | // maxObjSpaceDerivLen = max(length(dFdx(i_objectSpacePos)), length(dFdy(i_objectSpacePos))); 86 | float ComputeHashedAlphaThreshold(vec3 objectSpacePos, float maxObjSpaceDerivLen, float hashScale) 87 | { 88 | float pixScale = 1.0 / (hashScale + maxObjSpaceDerivLen); 89 | float pixScaleMin = exp2(floor(log2(pixScale))); 90 | float pixScaleMax = exp2(ceil(log2(pixScale))); 91 | vec2 alpha = vec2(MM_Hash3(floor(pixScaleMin * objectSpacePos)), MM_Hash3(floor(pixScaleMax * objectSpacePos))); 92 | float lerpFactor = fract(log2(pixScale)); 93 | float x = (1.0 - lerpFactor) * alpha.x + lerpFactor * alpha.y; 94 | float a = min(lerpFactor, 1.0 - lerpFactor); 95 | vec3 cases = vec3(x * x / (2.0 * a * (1.0 - a)), (x - 0.5 * a) / (1.0 - a), 1.0 - ((1.0 - x) * (1.0 - x) / (2.0 * a * (1.0 - a)))); 96 | float threshold = (x < (1.0 - a)) ? ((x < a) ? cases.x : cases.y) : cases.z; 97 | return clamp(threshold, 1e-6, 1.0); 98 | } 99 | 100 | // Copyright 2019 Google LLC. 101 | // SPDX-License-Identifier: Apache-2.0 102 | 103 | // Polynomial approximation in GLSL for the Turbo colormap 104 | // Original LUT: https://gist.github.com/mikhailov-work/ee72ba4191942acecc03fe6da94fc73f 105 | 106 | // Authors: 107 | // Colormap Design: Anton Mikhailov (mikhailov@google.com) 108 | // GLSL Approximation: Ruofei Du (ruofei@google.com) 109 | 110 | vec3 TurboColormap(float x) 111 | { 112 | const vec4 kRedVec4 = vec4(0.13572138, 4.61539260, -42.66032258, 132.13108234); 113 | const vec4 kGreenVec4 = vec4(0.09140261, 2.19418839, 4.84296658, -14.18503333); 114 | const vec4 kBlueVec4 = vec4(0.10667330, 12.64194608, -60.58204836, 110.36276771); 115 | const vec2 kRedVec2 = vec2(-152.94239396, 59.28637943); 116 | const vec2 kGreenVec2 = vec2(4.27729857, 2.82956604); 117 | const vec2 kBlueVec2 = vec2(-89.90310912, 27.34824973); 118 | 119 | x = clamp(x, 0, 1); 120 | vec4 v4 = vec4( 1.0, x, x * x, x * x * x); 121 | vec2 v2 = v4.zw * v4.z; 122 | return vec3( 123 | dot(v4, kRedVec4) + dot(v2, kRedVec2), 124 | dot(v4, kGreenVec4) + dot(v2, kGreenVec2), 125 | dot(v4, kBlueVec4) + dot(v2, kBlueVec2) 126 | ); 127 | } 128 | 129 | bool isfinite(float x) 130 | { 131 | return !isnan(x) && !isinf(x); 132 | } 133 | 134 | bvec2 isfinite(vec2 x) 135 | { 136 | return bvec2(isfinite(x.x), isfinite(x.y)); 137 | } 138 | 139 | bvec3 isfinite(vec3 x) 140 | { 141 | return bvec3(isfinite(x.x), isfinite(x.y), isfinite(x.z)); 142 | } 143 | 144 | bvec4 isfinite(vec4 x) 145 | { 146 | return bvec4(isfinite(x.x), isfinite(x.y), isfinite(x.z), isfinite(x.w)); 147 | } 148 | 149 | // Stolen from void 150 | vec3 map_to_unit_sphere(vec2 uv) 151 | { 152 | float cos_theta = 2.0 * uv.x - 1.0; 153 | float phi = 2.0 * M_PI * uv.y; 154 | float sin_theta = sqrt(1.0 - cos_theta * cos_theta); 155 | float sin_phi = sin(phi); 156 | float cos_phi = cos(phi); 157 | 158 | return vec3(sin_theta * cos_phi, cos_theta, sin_theta * sin_phi); 159 | } 160 | 161 | vec3 map_to_unit_hemisphere_cosine_weighted(vec2 uv, vec3 n) 162 | { 163 | vec3 p = map_to_unit_sphere(uv); 164 | return n + p; 165 | } 166 | 167 | // From void 168 | float solid_angle_mapping_PDF(float theta_max) 169 | { 170 | return 1.0 / (2.0 * M_PI * (1.0 - cos(theta_max))); 171 | } 172 | 173 | float uniform_hemisphere_PDF() 174 | { 175 | return 1.0 / (2.0 * M_PI); 176 | } 177 | 178 | float cosine_weighted_hemisphere_PDF(float cosTheta) 179 | { 180 | return cosTheta / M_PI; 181 | } 182 | 183 | #endif // MATH_H -------------------------------------------------------------------------------- /src/Fvog/AccelerationStructure.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicTypes2.h" 4 | #include "Buffer2.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | namespace Fvog 13 | { 14 | enum class AccelerationStructureGeometryFlag : uint32_t 15 | { 16 | OPAQUE = VK_GEOMETRY_OPAQUE_BIT_KHR, 17 | NO_DUPLICATE_ANYHIT_INVOCATION = VK_GEOMETRY_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_KHR, 18 | }; 19 | FVOG_DECLARE_FLAG_TYPE(AccelerationStructureGeometryFlags, AccelerationStructureGeometryFlag, uint32_t); 20 | 21 | enum class AccelerationStructureBuildFlag : uint32_t 22 | { 23 | ALLOW_UPDATE = VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR, 24 | ALLOW_COMPACTION = VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_COMPACTION_BIT_KHR, 25 | FAST_TRACE = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR, 26 | FAST_BUILD = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR, 27 | ALLOW_MICROMAP_OPACITY_UPDATE = VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_OPACITY_MICROMAP_UPDATE_EXT, 28 | ALLOW_DISABLE_OPACITY_MICROMAPS = VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_DISABLE_OPACITY_MICROMAPS_EXT, 29 | ALLOW_OPACITY_MICROMAP_DATA_UPDATE = VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_OPACITY_MICROMAP_DATA_UPDATE_EXT, 30 | ALLOW_DATA_ACCESS = VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_DATA_ACCESS_KHR, 31 | }; 32 | FVOG_DECLARE_FLAG_TYPE(AccelerationStructureBuildFlags, AccelerationStructureBuildFlag, uint32_t); 33 | 34 | enum class AccelerationStructureGeometryInstanceFlag : uint32_t 35 | { 36 | TRIANGLE_FACING_CULL_DISABLE_BIT = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR, 37 | TRIANGLE_FLIP_FACING_BIT = VK_GEOMETRY_INSTANCE_TRIANGLE_FLIP_FACING_BIT_KHR, 38 | FORCE_OPAQUE_BIT = VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR, 39 | FORCE_NO_OPAQUE_BIT = VK_GEOMETRY_INSTANCE_FORCE_NO_OPAQUE_BIT_KHR, 40 | FORCE_OPACITY_MICROMAP_2_STAT = VK_GEOMETRY_INSTANCE_FORCE_OPACITY_MICROMAP_2_STATE_EXT, 41 | DISABLE_OPACITY_MICROMAPS = VK_GEOMETRY_INSTANCE_DISABLE_OPACITY_MICROMAPS_EXT, 42 | TRIANGLE_FRONT_COUNTERCLOCKWISE_BIT = VK_GEOMETRY_INSTANCE_TRIANGLE_FRONT_COUNTERCLOCKWISE_BIT_KHR, 43 | }; 44 | 45 | struct BlasCreateInfo 46 | { 47 | std::optional commandBuffer = {}; 48 | AccelerationStructureGeometryFlags geoemtryFlags = {}; 49 | AccelerationStructureBuildFlags buildFlags = {}; 50 | 51 | VkFormat vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; 52 | VkDeviceAddress vertexBuffer = 0; 53 | VkDeviceAddress indexBuffer = 0; 54 | VkDeviceSize vertexStride = 0; 55 | uint32_t numVertices = 0; 56 | VkIndexType indexType = VK_INDEX_TYPE_UINT32; 57 | uint32_t numIndices = 0; 58 | }; 59 | 60 | class Blas 61 | { 62 | public: 63 | explicit Blas(const BlasCreateInfo& createInfo, std::string name = {}); 64 | ~Blas(); 65 | 66 | Blas(const Blas& other) = delete; 67 | Blas& operator=(const Blas& other) = delete; 68 | Blas(Blas&& other) noexcept; 69 | Blas& operator=(Blas&& other) noexcept; 70 | 71 | VkAccelerationStructureKHR Handle() const noexcept 72 | { 73 | return handle_; 74 | } 75 | 76 | const Buffer& GetBuffer() const noexcept 77 | { 78 | return *buffer_; 79 | } 80 | 81 | VkDeviceSize GetAddress() const noexcept 82 | { 83 | return address_; 84 | } 85 | 86 | const BlasCreateInfo& GetCreateInfo() const noexcept 87 | { 88 | return createInfo_; 89 | } 90 | 91 | private: 92 | VkAccelerationStructureKHR handle_; 93 | // Buffer holding the actual AS data 94 | std::optional buffer_; 95 | // Address of the acceleration structure 96 | VkDeviceSize address_; 97 | 98 | BlasCreateInfo createInfo_; 99 | }; 100 | 101 | struct TlasInstance 102 | { 103 | VkTransformMatrixKHR transform = {}; 104 | uint32_t instanceCustomIndex : 24 = 0; 105 | uint32_t mask : 8 = 0; 106 | uint32_t shaderBindingTableOffset : 24 = 0; 107 | AccelerationStructureGeometryInstanceFlag flags : 8 = {}; 108 | VkDeviceAddress blasAddress = 0; 109 | }; 110 | static_assert(sizeof(TlasInstance) == sizeof(VkAccelerationStructureInstanceKHR)); 111 | 112 | struct TlasCreateInfo 113 | { 114 | std::optional commandBuffer = {}; 115 | AccelerationStructureGeometryFlags geoemtryFlags = {}; 116 | AccelerationStructureBuildFlags buildFlags = {}; 117 | 118 | const Buffer* instanceBuffer = nullptr; 119 | }; 120 | 121 | class Tlas 122 | { 123 | public: 124 | Tlas(const TlasCreateInfo& createInfo, std::string name = {}); 125 | ~Tlas(); 126 | 127 | Tlas(const Tlas& other) = delete; 128 | Tlas& operator=(const Tlas& other) = delete; 129 | Tlas(Tlas&& other) noexcept; 130 | Tlas& operator=(Tlas&& other) noexcept; 131 | 132 | VkAccelerationStructureKHR Handle() const noexcept 133 | { 134 | return handle_; 135 | } 136 | 137 | const Buffer& GetBuffer() const noexcept 138 | { 139 | return *buffer_; 140 | } 141 | 142 | VkDeviceSize GetAddress() const noexcept 143 | { 144 | return address_; 145 | } 146 | 147 | const TlasCreateInfo& GetCreateInfo() const noexcept 148 | { 149 | return createInfo_; 150 | } 151 | 152 | Device::DescriptorInfo::ResourceHandle GetResourceHandle() 153 | { 154 | return descriptorInfo_.value().GpuResource(); 155 | } 156 | 157 | private: 158 | VkAccelerationStructureKHR handle_; 159 | // Buffer holding the actual AS data 160 | std::optional buffer_; 161 | // Address of the acceleration structure 162 | VkDeviceSize address_; 163 | std::optional descriptorInfo_; 164 | 165 | TlasCreateInfo createInfo_; 166 | }; 167 | } // namespace Fvog 168 | -------------------------------------------------------------------------------- /src/techniques/Bloom.cpp: -------------------------------------------------------------------------------- 1 | #include "Bloom.h" 2 | 3 | #include "shaders/bloom/BloomCommon.h.glsl" 4 | #include "Application.h" 5 | 6 | #include "Fvog/Rendering2.h" 7 | #include "Fvog/Shader2.h" 8 | 9 | #include "../RendererUtilities.h" 10 | 11 | namespace Techniques 12 | { 13 | Bloom::Bloom() 14 | { 15 | downsampleLowPassPipeline = GetPipelineManager().EnqueueCompileComputePipeline({ 16 | .name = "Bloom Downsample (low-pass)", 17 | .shaderModuleInfo = PipelineManager::ShaderModuleCreateInfo{ 18 | .stage = Fvog::PipelineStage::COMPUTE_SHADER, 19 | .path = GetShaderDirectory() / "bloom/BloomDownsampleLowPass.comp.glsl", 20 | }, 21 | }); 22 | 23 | downsamplePipeline = GetPipelineManager().EnqueueCompileComputePipeline({ 24 | .name = "Bloom Downsample", 25 | .shaderModuleInfo = 26 | PipelineManager::ShaderModuleCreateInfo{ 27 | .stage = Fvog::PipelineStage::COMPUTE_SHADER, 28 | .path = GetShaderDirectory() / "bloom/BloomDownsample.comp.glsl", 29 | }, 30 | }); 31 | 32 | upsamplePipeline = GetPipelineManager().EnqueueCompileComputePipeline({ 33 | .name = "Bloom Upsample", 34 | .shaderModuleInfo = 35 | PipelineManager::ShaderModuleCreateInfo{ 36 | .stage = Fvog::PipelineStage::COMPUTE_SHADER, 37 | .path = GetShaderDirectory() / "bloom/BloomUpsample.comp.glsl", 38 | }, 39 | }); 40 | } 41 | 42 | void Bloom::Apply(VkCommandBuffer commandBuffer, const ApplyParams& params) 43 | { 44 | assert(params.passes <= params.scratchTexture.GetCreateInfo().mipLevels && "Bloom target is too small for the number of passes"); 45 | 46 | auto linearSampler = Fvog::Sampler({ 47 | .magFilter = VK_FILTER_LINEAR, 48 | .minFilter = VK_FILTER_LINEAR, 49 | .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, 50 | .addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, 51 | .addressModeV = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, 52 | }, "Linear Mirror Sampler"); 53 | 54 | auto ctx = Fvog::Context(commandBuffer); 55 | auto marker = ctx.MakeScopedDebugMarker("Bloom"); 56 | 57 | ctx.ImageBarrier(params.target, VK_IMAGE_LAYOUT_GENERAL); 58 | ctx.ImageBarrierDiscard(params.scratchTexture, VK_IMAGE_LAYOUT_GENERAL); 59 | 60 | { 61 | auto marker2 = ctx.MakeScopedDebugMarker("Downsample"); 62 | for (uint32_t i = 0; i < params.passes; i++) 63 | { 64 | ctx.Barrier(); 65 | 66 | Fvog::Extent2D sourceDim{}; 67 | Fvog::Extent2D targetDim = params.target.GetCreateInfo().extent >> (i + 1); 68 | float sourceLod{}; 69 | 70 | Fvog::Texture* sourceTex = nullptr; 71 | 72 | // We use a low-pass filter on the first downsample (mip0 -> mip1) to resolve flickering/temporal aliasing that occurs otherwise 73 | if (i == 0) 74 | { 75 | if (params.useLowPassFilterOnFirstPass) 76 | { 77 | ctx.BindComputePipeline(downsampleLowPassPipeline.GetPipeline()); 78 | } 79 | else 80 | { 81 | ctx.BindComputePipeline(downsamplePipeline.GetPipeline()); 82 | } 83 | 84 | sourceLod = 0; 85 | sourceTex = ¶ms.target; 86 | sourceDim = params.target.GetCreateInfo().extent; 87 | } 88 | else 89 | { 90 | ctx.BindComputePipeline(downsamplePipeline.GetPipeline()); 91 | 92 | sourceLod = static_cast(i - 1); 93 | sourceTex = ¶ms.scratchTexture; 94 | sourceDim = params.target.GetCreateInfo().extent >> i; 95 | } 96 | 97 | ctx.SetPushConstants(BloomUniforms{ 98 | .sourceSampledImageIdx = sourceTex->ImageView().GetSampledResourceHandle().index, 99 | .targetStorageImageIdx = params.scratchTexture.CreateSingleMipView(i).GetStorageResourceHandle().index, 100 | .linearSamplerIdx = linearSampler.GetResourceHandle().index, 101 | .sourceDim = {sourceDim.width, sourceDim.height}, 102 | .targetDim = {targetDim.width, targetDim.height}, 103 | .sourceLod = sourceLod, 104 | }); 105 | 106 | ctx.DispatchInvocations(targetDim.width, targetDim.height, 1); 107 | } 108 | } 109 | 110 | { 111 | auto marker2 = ctx.MakeScopedDebugMarker("Upsample"); 112 | ctx.BindComputePipeline(upsamplePipeline.GetPipeline()); 113 | for (int32_t i = params.passes - 1; i >= 0; i--) 114 | { 115 | ctx.Barrier(); 116 | 117 | Fvog::Extent2D sourceDim = params.target.GetCreateInfo().extent >> (i + 1); 118 | Fvog::Extent2D targetDim{}; 119 | Fvog::Texture* targetTex = nullptr; 120 | uint32_t targetLod{}; 121 | 122 | // final pass 123 | if (i == 0) 124 | { 125 | targetLod = 0; 126 | targetTex = ¶ms.target; 127 | targetDim = params.target.GetCreateInfo().extent; 128 | } 129 | else 130 | { 131 | targetLod = i - 1; 132 | targetTex = ¶ms.scratchTexture; 133 | targetDim = params.target.GetCreateInfo().extent >> i; 134 | } 135 | 136 | ctx.SetPushConstants(BloomUniforms{ 137 | .sourceSampledImageIdx = params.scratchTexture.ImageView().GetSampledResourceHandle().index, 138 | .targetSampledImageIdx = targetTex->ImageView().GetSampledResourceHandle().index, 139 | .targetStorageImageIdx = targetTex->CreateSingleMipView(targetLod).GetStorageResourceHandle().index, 140 | .linearSamplerIdx = linearSampler.GetResourceHandle().index, 141 | .sourceDim = {sourceDim.width, sourceDim.height}, 142 | .targetDim = {targetDim.width, targetDim.height}, 143 | .width = params.width, 144 | .strength = params.strength, 145 | .sourceLod = static_cast(i), 146 | .targetLod = static_cast(targetLod), 147 | .numPasses = params.passes, 148 | .isFinalPass = i == 0, 149 | }); 150 | 151 | ctx.DispatchInvocations(targetDim.width, targetDim.height, 1); 152 | } 153 | } 154 | } 155 | } // namespace Techniques -------------------------------------------------------------------------------- /src/PipelineManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Fvog/Pipeline2.h" 3 | #include "Fvog/Shader2.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // The purpose of this class is to serve as a central place to manage shader and pipeline compilation. 12 | // The interface should be such that multithreaded compilation and shader deduplication could be transparently performed, 13 | // i.e. the user gets indirect handles to pipelines which can act as an IOU. 14 | // This system additionally allows pipelines to be swapped out without invalidating any of the user's handles, 15 | // enabling simple runtime shader compilation. 16 | class PipelineManager 17 | { 18 | public: 19 | PipelineManager() = default; 20 | 21 | // Moving would invalidate references from child objects, so forbid it (this is probably very smelly) 22 | PipelineManager(PipelineManager&&) noexcept = delete; 23 | PipelineManager& operator=(PipelineManager&&) noexcept = delete; 24 | 25 | class GraphicsPipelineKey 26 | { 27 | public: 28 | GraphicsPipelineKey() = default; 29 | 30 | operator bool() const noexcept 31 | { 32 | return id_ != 0; 33 | } 34 | 35 | [[nodiscard]] Fvog::GraphicsPipeline& GetPipeline() const 36 | { 37 | assert(pipelineManager_); 38 | auto& pipeline = pipelineManager_->graphicsPipelines_.at(id_).pipeline; 39 | assert(pipeline); 40 | return *pipeline; 41 | } 42 | 43 | private: 44 | friend class PipelineManager; 45 | GraphicsPipelineKey(uint64_t id, PipelineManager* pipelineManager) 46 | : id_(id), pipelineManager_(pipelineManager){} 47 | 48 | uint64_t id_{}; 49 | PipelineManager* pipelineManager_{}; 50 | }; 51 | 52 | class ComputePipelineKey 53 | { 54 | public: 55 | ComputePipelineKey() = default; 56 | 57 | operator bool() const noexcept 58 | { 59 | return id_ != 0; 60 | } 61 | 62 | [[nodiscard]] Fvog::ComputePipeline& GetPipeline() const 63 | { 64 | assert(pipelineManager_); 65 | auto& pipeline = pipelineManager_->computePipelines_.at(id_).pipeline; 66 | assert(pipeline); 67 | return *pipeline; 68 | } 69 | 70 | //[[nodiscard]] bool QueryIsCompiled 71 | 72 | private: 73 | friend class PipelineManager; 74 | ComputePipelineKey(uint64_t id, PipelineManager* pipelineManager) 75 | : id_(id), pipelineManager_(pipelineManager){} 76 | 77 | uint64_t id_{}; 78 | PipelineManager* pipelineManager_{}; 79 | }; 80 | 81 | struct ShaderModuleCreateInfo 82 | { 83 | bool operator==(const ShaderModuleCreateInfo&) const noexcept = default; 84 | Fvog::PipelineStage stage = Fvog::PipelineStage::COMPUTE_SHADER; 85 | std::filesystem::path path; 86 | // TODO: Defines, specialization stuff 87 | }; 88 | 89 | struct ComputePipelineCreateInfo 90 | { 91 | std::string name = {}; 92 | ShaderModuleCreateInfo shaderModuleInfo; 93 | }; 94 | 95 | struct GraphicsPipelineState 96 | { 97 | Fvog::InputAssemblyState inputAssemblyState = {}; 98 | Fvog::RasterizationState rasterizationState = {}; 99 | Fvog::MultisampleState multisampleState = {}; 100 | Fvog::DepthState depthState = {}; 101 | Fvog::StencilState stencilState = {}; 102 | Fvog::ColorBlendState colorBlendState = {}; 103 | Fvog::RenderTargetFormats renderTargetFormats = {}; 104 | }; 105 | 106 | struct GraphicsPipelineCreateInfo 107 | { 108 | std::string name = {}; 109 | std::optional vertexModuleInfo; 110 | std::optional fragmentModuleInfo; 111 | GraphicsPipelineState state; 112 | }; 113 | 114 | //GraphicsPipelineKey EnqueueCompileGraphicsPipeline(); 115 | 116 | // Returns an opaque handle to a compute pipeline. 117 | [[nodiscard]] ComputePipelineKey EnqueueCompileComputePipeline(const ComputePipelineCreateInfo& createInfo); 118 | 119 | [[nodiscard]] GraphicsPipelineKey EnqueueCompileGraphicsPipeline(const GraphicsPipelineCreateInfo& createInfo); 120 | 121 | void EnqueueRecompileShader(const ShaderModuleCreateInfo& shaderInfo); 122 | 123 | void PollModifiedShaders(); 124 | 125 | void EnqueueModifiedShaders(); 126 | 127 | enum class Status 128 | { 129 | PENDING, 130 | SUCCESS, 131 | FAILED, 132 | }; 133 | 134 | struct ShaderModuleValue 135 | { 136 | Status status = Status::PENDING; 137 | // Duplicate of map key, but I'm too dumb to figure out a cleaner solution (set won't work due to immutability constraint) 138 | ShaderModuleCreateInfo info; 139 | // TODO: file watcher? 140 | std::unique_ptr shader; 141 | std::filesystem::file_time_type lastWriteTime{}; 142 | bool isOutOfDate = false; // If true, current shader is older than file contents 143 | }; 144 | 145 | [[nodiscard]] std::vector GetShaderModules() const; 146 | [[nodiscard]] std::vector GetGraphicsPipelines() const; 147 | [[nodiscard]] std::vector GetComputePipelines() const; 148 | 149 | private: 150 | uint64_t nextId_ = 1; 151 | 152 | struct GraphicsPipelineValue 153 | { 154 | std::unique_ptr pipeline; 155 | std::vector stages; 156 | GraphicsPipelineState state; 157 | }; 158 | 159 | struct ComputePipelineValue 160 | { 161 | std::unique_ptr pipeline; 162 | ShaderModuleValue* shaderModuleValue; 163 | }; 164 | 165 | ShaderModuleValue& EmplaceOrGetShaderModuleValue(const ShaderModuleCreateInfo& createInfo); 166 | 167 | //struct ShaderModuleValue; 168 | //struct GraphicsPipelineValue; 169 | //struct ComputePipelineValue; 170 | 171 | struct HashShaderModuleCreateInfo 172 | { 173 | std::size_t operator()(const ShaderModuleCreateInfo& s) const noexcept; 174 | }; 175 | 176 | // Caches the compilation of shader modules 177 | std::unordered_map shaderModules_; 178 | std::unordered_map graphicsPipelines_; 179 | std::unordered_map computePipelines_; 180 | }; 181 | 182 | void CreateGlobalPipelineManager(); 183 | [[nodiscard]] PipelineManager& GetPipelineManager(); 184 | void DestroyGlobalPipelineManager(); --------------------------------------------------------------------------------