├── .clang-format ├── .git-blame-ignore-revs ├── .github └── workflows │ └── cmake.yml ├── .gitignore ├── CMakeLists.txt ├── CMakeSettings.json ├── LICENSE ├── README.md ├── cmake └── FindSphinx.cmake ├── docs ├── CMakeLists.txt ├── Doxyfile.in ├── Makefile ├── basics.rst ├── conf.py ├── index.rst ├── make.bat ├── pipelines.rst ├── reference.rst ├── rendering.rst └── requirements.txt ├── example ├── 01_hello_triangle.cpp ├── 02_deferred.cpp ├── 03_gltf_viewer.cpp ├── 04_volumetric.cpp ├── 05_gpu_driven.cpp ├── 06_msaa.cpp ├── 07_cpp_triangle.cpp ├── CMakeLists.txt ├── README.md ├── common │ ├── AppImage.h │ ├── Application.cpp │ ├── Application.h │ ├── RsmTechnique.cpp │ ├── RsmTechnique.h │ ├── SceneLoader.cpp │ └── SceneLoader.h ├── external │ └── CMakeLists.txt ├── media │ ├── deferred.png │ ├── gltf_viewer.png │ ├── gpu_driven.png │ ├── hello_triangle.png │ ├── msaa.png │ └── volumetric0.png ├── models │ ├── README.md │ ├── hierarchyTest.glb │ └── simple_scene.glb ├── shaders │ ├── FullScreenTri.vert.glsl │ ├── RSMScene.frag.glsl │ ├── RSMScenePbr.frag.glsl │ ├── SceneDeferred.frag.glsl │ ├── SceneDeferred.vert.glsl │ ├── SceneDeferredPbr.frag.glsl │ ├── SceneDeferredPbr.vert.glsl │ ├── SceneDeferredSimple.frag.glsl │ ├── SceneDeferredSimple.vert.glsl │ ├── SceneShadow.vert.glsl │ ├── ShadeDeferred.frag.glsl │ ├── ShadeDeferredPbr.frag.glsl │ ├── ShadeDeferredSimple.frag.glsl │ ├── Texture.frag.glsl │ ├── TonemapAndDither.frag.glsl │ ├── clustered │ │ ├── CompactVisibleClusters.comp.glsl │ │ ├── CullLights.comp.glsl │ │ └── MarkVisibleClusters.comp.glsl │ ├── gpu_driven │ │ ├── BoundingBox.vert.glsl │ │ ├── Common.h │ │ ├── CullVisibility.frag.glsl │ │ ├── SceneForward.frag.glsl │ │ ├── SceneForward.vert.glsl │ │ └── SolidColor.frag.glsl │ ├── rsm │ │ ├── Bilateral.h.glsl │ │ ├── Bilateral5x5.comp.glsl │ │ ├── BlitTexture.comp.glsl │ │ ├── Common.h.glsl │ │ ├── Indirect.comp.glsl │ │ ├── IndirectDitheredFiltered.comp.glsl │ │ ├── Kernels.h.glsl │ │ ├── Modulate.comp.glsl │ │ ├── ModulateUpscale.comp.glsl │ │ └── Reproject.comp.glsl │ └── volumetric │ │ ├── ApplyVolumetricsDeferred.comp.glsl │ │ ├── CellLightingAndDensity.comp.glsl │ │ ├── Common.h │ │ ├── Depth2exponential.comp.glsl │ │ ├── Frog.h │ │ ├── GaussianBlur.comp.glsl │ │ ├── MarchVolume.comp.glsl │ │ └── TonemapAndDither.comp.glsl ├── textures │ ├── RobotoCondensed-Regular.ttf │ ├── bluenoise16.png │ ├── bluenoise256.png │ ├── bluenoise32.png │ └── fog_mie_data.txt └── vendor │ ├── stb_image.cpp │ ├── stb_image.h │ └── stb_include.h ├── external ├── CMakeLists.txt └── glad │ ├── include │ ├── KHR │ │ └── khrplatform.h │ └── glad │ │ └── gl.h │ └── src │ └── gl.c ├── include └── Fwog │ ├── BasicTypes.h │ ├── Buffer.h │ ├── Config.h │ ├── Context.h │ ├── DebugMarker.h │ ├── Exception.h │ ├── Fence.h │ ├── Pipeline.h │ ├── Rendering.h │ ├── Shader.h │ ├── Texture.h │ ├── Timer.h │ └── detail │ ├── ApiToEnum.h │ ├── ContextState.h │ ├── Flags.h │ ├── FramebufferCache.h │ ├── Hash.h │ ├── PipelineManager.h │ ├── SamplerCache.h │ ├── ShaderCPP.h │ ├── ShaderGLSL.h │ ├── ShaderSPIRV.h │ └── VertexArrayCache.h ├── media └── logo.png └── src ├── Buffer.cpp ├── Context.cpp ├── DebugMarker.cpp ├── Fence.cpp ├── Pipeline.cpp ├── Rendering.cpp ├── Shader.cpp ├── Texture.cpp ├── Timer.cpp └── detail ├── ApiToEnum.cpp ├── FramebufferCache.cpp ├── PipelineManager.cpp ├── SamplerCache.cpp ├── ShaderCPP.cpp ├── ShaderGLSL.cpp ├── ShaderSPIRV.cpp └── VertexArrayCache.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AlignAfterOpenBracket: Align 3 | AlignConsecutiveMacros: 'true' 4 | AlignEscapedNewlines: Left 5 | AlignOperands: 'true' 6 | AlignTrailingComments: 'true' 7 | AllowAllArgumentsOnNextLine: 'false' 8 | AllowShortCaseLabelsOnASingleLine: 'true' 9 | AllowShortFunctionsOnASingleLine: 'Empty' 10 | AlwaysBreakTemplateDeclarations: 'Yes' 11 | BinPackArguments: 'false' 12 | BinPackParameters: 'false' 13 | BreakBeforeBraces: Allman 14 | BreakConstructorInitializers: BeforeColon 15 | ColumnLimit: '120' 16 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' 17 | ConstructorInitializerIndentWidth: '2' 18 | ContinuationIndentWidth: '2' 19 | FixNamespaceComments: 'true' 20 | IndentCaseLabels: 'false' 21 | IndentPPDirectives: BeforeHash 22 | IndentWidth: '2' 23 | NamespaceIndentation: All 24 | PenaltyBreakBeforeFirstCallParameter: '1' 25 | PenaltyExcessCharacter: '5' 26 | PenaltyReturnTypeOnItsOwnLine: '10000' 27 | PointerAlignment: Left 28 | SpaceAfterCStyleCast: 'false' 29 | SpaceAfterLogicalNot: 'false' 30 | SpaceAfterTemplateKeyword: 'false' 31 | SpaceBeforeAssignmentOperators: 'true' 32 | SpaceBeforeCpp11BracedList: 'false' 33 | SpaceBeforeCtorInitializerColon: 'true' 34 | SpaceBeforeInheritanceColon: 'true' 35 | SpaceBeforeParens: ControlStatements 36 | SpacesInAngles: 'false' 37 | SpacesInCStyleCastParentheses: 'false' 38 | SpacesInSquareBrackets: 'false' 39 | UseTab: Never 40 | TabWidth: '2' 41 | 42 | ... 43 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Added clang-format, applied it to all examples 2 | 348e8eb900170e04a41e26baa679540bb0966b9b 3 | 4 | # Applied clang-format to Fwog API 5 | 935c88a7daef53a7673f9a09d9f71f1a8b0c1b12 -------------------------------------------------------------------------------- /.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: [10, 11, 12, 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: Update Repos 29 | run: sudo apt-get update 30 | 31 | - name: Install gcc-${{ matrix.gccversion }} 32 | run: sudo apt-get install gcc-${{ matrix.gccversion }} g++-${{ matrix.gccversion }} 33 | 34 | - name: Configure CMake with GCC-${{ matrix.gccversion }} 35 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_COMPILER=g++-${{ matrix.gccversion }} 36 | env: 37 | CC: gcc-${{ matrix.gccversion }} 38 | CXX: g++-${{ matrix.gccversion }} 39 | 40 | - name: Build with gcc-${{ matrix.gccversion }} 41 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 42 | -------------------------------------------------------------------------------- /.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/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # the project's main CMakeLists file 2 | 3 | cmake_minimum_required(VERSION 3.15) 4 | 5 | project(Fwog) 6 | 7 | set(CMAKE_CXX_STANDARD 20) 8 | 9 | add_subdirectory(external) 10 | 11 | set(fwog_source_files 12 | src/Buffer.cpp 13 | src/DebugMarker.cpp 14 | src/Fence.cpp 15 | src/Shader.cpp 16 | src/Texture.cpp 17 | src/Rendering.cpp 18 | src/Pipeline.cpp 19 | src/Timer.cpp 20 | src/detail/ApiToEnum.cpp 21 | src/detail/PipelineManager.cpp 22 | src/detail/FramebufferCache.cpp 23 | src/detail/SamplerCache.cpp 24 | src/detail/VertexArrayCache.cpp 25 | src/Context.cpp 26 | src/detail/ShaderGLSL.cpp 27 | src/detail/ShaderSPIRV.cpp 28 | ) 29 | 30 | set(fwog_header_files 31 | include/Fwog/BasicTypes.h 32 | include/Fwog/Buffer.h 33 | include/Fwog/DebugMarker.h 34 | include/Fwog/Fence.h 35 | include/Fwog/Shader.h 36 | include/Fwog/Texture.h 37 | include/Fwog/Rendering.h 38 | include/Fwog/Pipeline.h 39 | include/Fwog/Timer.h 40 | include/Fwog/Exception.h 41 | include/Fwog/detail/Flags.h 42 | include/Fwog/detail/ApiToEnum.h 43 | include/Fwog/detail/PipelineManager.h 44 | include/Fwog/detail/FramebufferCache.h 45 | include/Fwog/detail/Hash.h 46 | include/Fwog/detail/SamplerCache.h 47 | include/Fwog/detail/VertexArrayCache.h 48 | include/Fwog/Config.h 49 | include/Fwog/Context.h 50 | include/Fwog/detail/ContextState.h 51 | include/Fwog/detail/ShaderGLSL.h 52 | include/Fwog/detail/ShaderSPIRV.h 53 | ) 54 | 55 | add_library(fwog ${fwog_source_files} ${fwog_header_files}) 56 | 57 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT fwog) 58 | target_include_directories(fwog PUBLIC include) 59 | 60 | find_package(OpenGL REQUIRED) 61 | 62 | target_compile_options(fwog 63 | PRIVATE 64 | $<$,$,$>: 65 | -Wall 66 | -Wextra 67 | -pedantic-errors 68 | -Wno-missing-field-initializers 69 | -Wno-unused-result 70 | #-Werror 71 | #-Wconversion 72 | #-Wsign-conversion 73 | > 74 | $<$: 75 | /W4 76 | /WX 77 | /permissive- 78 | /wd4324 # structure was padded 79 | > 80 | ) 81 | 82 | option(FWOG_VCC_ENABLE "Use Vulkan Clang Compiler to compile C and C++ shaders. Experimental feature. Requires LLVM 14.x in the path" FALSE) 83 | 84 | if (FWOG_VCC_ENABLE) 85 | include(FetchContent) 86 | FetchContent_Declare( 87 | shady 88 | GIT_REPOSITORY https://github.com/JuanDiegoMontoya/shady.git 89 | GIT_TAG 58611db72688f7576b2f564db6ae7499f7280bcd 90 | ) 91 | FetchContent_MakeAvailable(shady) 92 | 93 | target_link_libraries(fwog 94 | driver 95 | shady_emit_common 96 | shady 97 | ) 98 | target_compile_definitions(fwog PUBLIC "-DFWOG_VCC_INCLUDE_DIR=\"${shady_BINARY_DIR}/share/vcc/include/\"") 99 | target_compile_definitions(fwog PUBLIC FWOG_VCC_ENABLE=1) 100 | target_sources(fwog PRIVATE 101 | src/detail/ShaderCPP.cpp 102 | include/Fwog/detail/ShaderCPP.h 103 | ) 104 | else() 105 | target_compile_definitions(fwog PUBLIC FWOG_VCC_ENABLE=0) 106 | endif() 107 | 108 | option(FWOG_FORCE_COLORED_OUTPUT "Always produce ANSI-colored output (GNU/Clang only)." TRUE) 109 | if (${FORCE_COLORED_OUTPUT}) 110 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") 111 | add_compile_options(-fdiagnostics-color=always) 112 | elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 113 | add_compile_options(-fcolor-diagnostics) 114 | endif() 115 | endif() 116 | 117 | 118 | target_link_libraries(fwog lib_glad) 119 | 120 | option(FWOG_BUILD_EXAMPLES "Build the example projects for Fwog." FALSE) 121 | if (${FWOG_BUILD_EXAMPLES}) 122 | add_subdirectory(example) 123 | endif() 124 | 125 | option(FWOG_BUILD_DOCS "Build the documentation for Fwog." FALSE) 126 | if (${FWOG_BUILD_DOCS}) 127 | # Add the cmake folder so the FindSphinx module is found 128 | set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) 129 | add_subdirectory(docs) 130 | endif() 131 | 132 | install(TARGETS fwog 133 | RUNTIME DESTINATION bin 134 | LIBRARY DESTINATION lib 135 | ARCHIVE DESTINATION lib) 136 | 137 | install(DIRECTORY include/ DESTINATION include) -------------------------------------------------------------------------------- /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 | }, 14 | { 15 | "name": "x64-RelWithDebInfo", 16 | "generator": "Ninja", 17 | "configurationType": "RelWithDebInfo", 18 | "buildRoot": "${projectDir}\\out\\build\\${name}", 19 | "installRoot": "${projectDir}\\out\\install\\${name}", 20 | "cmakeCommandArgs": "", 21 | "buildCommandArgs": "", 22 | "ctestCommandArgs": "", 23 | "inheritEnvironments": [ "msvc_x64_x64" ] 24 | }, 25 | { 26 | "name": "x64-Release", 27 | "generator": "Ninja", 28 | "configurationType": "Release", 29 | "buildRoot": "${projectDir}\\out\\build\\${name}", 30 | "installRoot": "${projectDir}\\out\\install\\${name}", 31 | "cmakeCommandArgs": "", 32 | "buildCommandArgs": "", 33 | "ctestCommandArgs": "", 34 | "inheritEnvironments": [ "msvc_x64_x64" ] 35 | }, 36 | { 37 | "name": "x64-MinSizeRel", 38 | "generator": "Ninja", 39 | "configurationType": "MinSizeRel", 40 | "buildRoot": "${projectDir}\\out\\build\\${name}", 41 | "installRoot": "${projectDir}\\out\\install\\${name}", 42 | "cmakeCommandArgs": "", 43 | "buildCommandArgs": "", 44 | "ctestCommandArgs": "", 45 | "inheritEnvironments": [ "msvc_x64_x64" ], 46 | "variables": [] 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 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 | -------------------------------------------------------------------------------- /cmake/FindSphinx.cmake: -------------------------------------------------------------------------------- 1 | #Look for an executable called sphinx-build 2 | find_program(SPHINX_EXECUTABLE 3 | NAMES sphinx-build 4 | DOC "Path to sphinx-build executable") 5 | 6 | include(FindPackageHandleStandardArgs) 7 | 8 | #Handle standard arguments to find_package like REQUIRED and QUIET 9 | find_package_handle_standard_args(Sphinx 10 | "Failed to find sphinx-build executable" 11 | SPHINX_EXECUTABLE) -------------------------------------------------------------------------------- /docs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Doxygen REQUIRED) 2 | find_package(Sphinx REQUIRED) 3 | 4 | # Find all the public headers 5 | get_target_property(FWOG_PUBLIC_HEADER_DIR fwog INTERFACE_INCLUDE_DIRECTORIES) 6 | file(GLOB_RECURSE FWOG_PUBLIC_HEADERS ${FWOG_PUBLIC_HEADER_DIR}/*.h) 7 | 8 | set(DOXYGEN_INPUT_DIR ${PROJECT_SOURCE_DIR}/include/Fwog) 9 | set(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/doxygen) 10 | set(DOXYGEN_INDEX_FILE ${DOXYGEN_OUTPUT_DIR}/xml/index.xml) 11 | set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) 12 | set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) 13 | 14 | # Replace variables inside @@ with the current values 15 | configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY) 16 | 17 | # Doxygen won't create this for us 18 | file(MAKE_DIRECTORY ${DOXYGEN_OUTPUT_DIR}) 19 | 20 | # Only regenerate Doxygen when the Doxyfile or public headers change 21 | add_custom_command(OUTPUT ${DOXYGEN_INDEX_FILE} 22 | DEPENDS ${FWOG_PUBLIC_HEADERS} 23 | COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} 24 | MAIN_DEPENDENCY ${DOXYFILE_OUT} ${DOXYFILE_IN} 25 | COMMENT "Generating docs" 26 | VERBATIM) 27 | 28 | # Nice named target so we can run the job easily 29 | add_custom_target(Doxygen ALL DEPENDS ${DOXYGEN_INDEX_FILE}) 30 | 31 | set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}) 32 | set(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}/sphinx) 33 | set(SPHINX_INDEX_FILE ${SPHINX_BUILD}/index.html) 34 | 35 | # Only regenerate Sphinx when: 36 | # - Doxygen has rerun 37 | # - Our doc files have been updated 38 | # - The Sphinx config has been updated 39 | add_custom_command(OUTPUT ${SPHINX_INDEX_FILE} 40 | COMMAND 41 | ${SPHINX_EXECUTABLE} -b html 42 | # Tell Breathe where to find the Doxygen output 43 | -Dbreathe_projects.Fwog=${DOXYGEN_OUTPUT_DIR}/xml 44 | ${SPHINX_SOURCE} ${SPHINX_BUILD} 45 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 46 | DEPENDS 47 | # Other docs files you want to track should go here (or in some variable) 48 | ${CMAKE_CURRENT_SOURCE_DIR}/index.rst 49 | ${CMAKE_CURRENT_SOURCE_DIR}/basics.rst 50 | ${CMAKE_CURRENT_SOURCE_DIR}/pipelines.rst 51 | ${CMAKE_CURRENT_SOURCE_DIR}/rendering.rst 52 | ${CMAKE_CURRENT_SOURCE_DIR}/reference.rst 53 | ${DOXYGEN_INDEX_FILE} 54 | MAIN_DEPENDENCY ${SPHINX_SOURCE}/conf.py 55 | COMMENT "Generating documentation with Sphinx") 56 | 57 | # Nice named target so we can run the job easily 58 | add_custom_target(Sphinx ALL DEPENDS ${SPHINX_INDEX_FILE}) -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/basics.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | To install Fwog, run the following commands in your friendly neighborhood terminal: 4 | 5 | .. code-block:: bash 6 | 7 | git clone https://github.com/JuanDiegoMontoya/Fwog.git 8 | cd Fwog 9 | mkdir build 10 | cd build 11 | cmake .. 12 | 13 | Creating a Fwog 14 | --------------- 15 | To create a Fwog (context), you will first need an OpenGL 4.6 context (e.g., one created with GLFW) and have loaded OpenGL function pointers in the appropriate location. Then, you can call :cpp:func:`Fwog::Initialize`. 16 | 17 | `#include "Fwog/Context.h"` 18 | 19 | .. doxygenfile:: Context.h 20 | :sections: func innernamespace 21 | 22 | Debugging 23 | --------- 24 | Fwog helps you debug by enabling certain features in debug builds (unless overridden). 25 | 26 | First, assert macros are generously sprinkled throughout the source to catch programming bugs. These will catch things like trying to clear a render target with an incompatible clear color or trying to call rendering commands outside of a rendering scope. 27 | 28 | Fwog also clears all resource bindings at the end of each pass to ensure there is no reliance on leaked state. 29 | 30 | These debugging features are disabled in release builds to ensure maximum performance, but it also means the programming bugs they catch will result in undefined behavior rather than an instant crash. 31 | 32 | Configuring 33 | ----------- 34 | Fwog can be configured by adding certain defines to your compile command: 35 | 36 | - ``FWOG_FORCE_DEBUG``: If defined, debug functionality will be enabled in all builds. 37 | - ``FWOG_ASSERT ``: Defines a custom assert function/macro for Fwog to use internally. By default, Fwog will use ``assert``. 38 | - ``FWOG_UNREACHABLE ``: Defines a custom unreachable function/macro for Fwog to use internally. By default, Fwog will simply use ``FWOG_ASSERT(0)`` for unreachable paths. 39 | - ``FWOG_OPENGL_HEADER ``: Allows the user to define where OpenGL function declarations can be found. By default, Fwog will search for ````. 40 | - ``FWOG_DEFAULT_CLIP_DEPTH_RANGE_ZERO_TO_ONE``: If defined, the default value for Viewport::depthRange will be :cpp:enumerator:`Fwog::ClipDepthRange::ZeroToOne`. Otherwise, its default value will be :cpp:enumerator:`Fwog::ClipDepthRange::NegativeOneToOne`. 41 | 42 | Errors 43 | ------ 44 | Fwog uses exceptions to propagate errors. Currently, exceptions are only used in a few places. 45 | 46 | .. doxygenfile:: Exception.h 47 | 48 | Device Properties 49 | ----------------- 50 | Sometimes, it can be useful to query some information about the device. Fwog provides :cpp:func:`Fwog::GetDeviceProperties` to query the following information: 51 | 52 | .. doxygenstruct:: Fwog::DeviceLimits 53 | :members: 54 | :undoc-members: 55 | 56 | .. doxygenstruct:: Fwog::DeviceFeatures 57 | :members: 58 | :undoc-members: 59 | 60 | .. doxygenstruct:: Fwog::DeviceProperties 61 | :members: 62 | :undoc-members: -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | 7 | import subprocess, os 8 | 9 | def configureDoxyfile(input_dir, output_dir): 10 | with open('Doxyfile.in', 'r') as file : 11 | filedata = file.read() 12 | 13 | filedata = filedata.replace('@DOXYGEN_INPUT_DIR@', input_dir) 14 | filedata = filedata.replace('@DOXYGEN_OUTPUT_DIR@', output_dir) 15 | 16 | with open('Doxyfile', 'w') as file: 17 | file.write(filedata) 18 | 19 | # Check if we're running on Read the Docs' servers 20 | read_the_docs_build = os.environ.get('READTHEDOCS', None) == 'True' 21 | 22 | # -- Project information ----------------------------------------------------- 23 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 24 | 25 | project = 'Fwog' 26 | copyright = '2023, Jake Ryan' 27 | author = 'Jake Ryan' 28 | 29 | # -- General configuration --------------------------------------------------- 30 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 31 | 32 | extensions = ['breathe'] 33 | cpp_index_common_prefix = ['Fwog::'] 34 | 35 | templates_path = ['_templates'] 36 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 37 | 38 | 39 | 40 | # -- Options for HTML output ------------------------------------------------- 41 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 42 | 43 | html_theme = 'sphinx_rtd_theme' 44 | html_static_path = ['_static'] 45 | html_theme_options = { 46 | } 47 | 48 | # Breathe Configuration 49 | breathe_default_project = "Fwog" 50 | 51 | breathe_projects = {} 52 | 53 | if read_the_docs_build: 54 | input_dir = '../include/Fwog' 55 | output_dir = 'build' 56 | configureDoxyfile(input_dir, output_dir) 57 | subprocess.call('doxygen', shell=True) 58 | breathe_projects['Fwog'] = output_dir + '/xml' -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to Fwog's documentation! 2 | ================================ 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | basics 9 | pipelines 10 | rendering 11 | reference 12 | 13 | Summary 14 | ------- 15 | Fwog is a low-level OpenGL 4.6 abstraction written in C++20 which aims to offer improved ergonomics and eliminate deprecated functionality. The goal is to wrap OpenGL in an expressive, type-safe, and easy-to-use wrapper. 16 | 17 | The design of Fwog is motivated by some of the shortcomings of OpenGL identified `in my blog post `__. For example, Fwog wraps OpenGL's integer constants with strongly-typed enumerations. Fwog also offers RAII wrappers for most OpenGL object types (buffers, textures, shaders, fences, and timer queries). Other types, like vertex arrays and framebuffers, are completely abstracted away in favor of a cleaner programming model. 18 | 19 | Fwog uses pipeline objects to encapsulate what would otherwise be global OpenGL state. These objects are bound before drawing to set the state of the pipeline, making it clear to the reader which state will be used and preventing subtle bugs where state leaks from one pass to the next. Pipelines also offer the potential benefit of reduced stuttering by providing more information to the driver up-front (`source `__). 20 | 21 | Caveats 22 | ------- 23 | Given Fwog's goals, there are a number of features that the API does not expose: 24 | 25 | - The default uniform block (i.e., uniforms set with ``glUniform*``) 26 | - Geometry shaders 27 | - Multi-viewport/layered rendering 28 | - Transform feedback 29 | - Hardware occlusion queries 30 | - All deprecated OpenGL features 31 | 32 | Some of these (such as geometry shaders) have not been implemented due to a lack of perceived interest, while others (such as ``glUniform`` and transform feedback) aren't due to their orthogonality to Fwog's design. Please raise an issue if you would like a feature to be implemented. Otherwise, it is possible to use certain unsupported features alongside Fwog's abstraction. 33 | 34 | Indices and tables 35 | ================== 36 | 37 | * :ref:`genindex` -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/pipelines.rst: -------------------------------------------------------------------------------- 1 | Pipelines 2 | ========= 3 | Pipeline objects are a concept that are familiar to Vulkan or D3D12 users, but new to OpenGL users. Essentially, they allow us to specify the entire state of the graphics pipeline (shaders, vertex input, blending, depth stencil, etc.) up-front. 4 | 5 | To use a pipeline, we first bind it in a rendering scope. All draw calls issued thereafter will use the state encapsulated in the pipeline. 6 | 7 | .. code-block:: cpp 8 | 9 | Fwog::BeginRendering(...); 10 | Fwog::Cmd::BindGraphicsPipeline(fooPipeline); 11 | Fwog::Cmd::Draw(...); // Will use the state from fooPipeline 12 | Fwog::Cmd::BindGraphicsPipeline(barPipeline); 13 | Fwog::Cmd::Draw(...); // Will use the state from barPipeline 14 | Fwog::EndRendering(); 15 | 16 | Note that it's illegal to issue a draw in a rendering scope without first binding a pipeline. This means you cannot rely on stale bindings from other scopes. 17 | 18 | Under the Hood 19 | -------------- 20 | Internally, Fwog tracks relevant OpenGL state to ensure that binding pipelines won't set redundant state. Pipeline binding will only incur the cost of setting the difference between that pipeline and the previous (and the cost to find the difference). 21 | 22 | `#include "Fwog/Pipeline.h"` 23 | 24 | .. doxygenfile:: Pipeline.h -------------------------------------------------------------------------------- /docs/reference.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | This file contains a reference for headers that weren't documented elsewhere. 4 | 5 | `BasicTypes.h` 6 | -------------- 7 | 8 | .. doxygenfile:: BasicTypes.h 9 | 10 | `Buffer.h` 11 | ---------- 12 | 13 | .. doxygenfile:: Buffer.h 14 | 15 | `DebugMarker.h` 16 | --------------- 17 | 18 | .. doxygenfile:: DebugMarker.h 19 | 20 | `Fence.h` 21 | --------- 22 | 23 | .. doxygenfile:: Fence.h 24 | 25 | `Shader.h` 26 | ---------- 27 | 28 | .. doxygenfile:: Shader.h 29 | 30 | `Texture.h` 31 | ----------- 32 | 33 | .. doxygenfile:: Texture.h 34 | 35 | `Timer.h` 36 | --------- 37 | 38 | .. doxygenfile:: Timer.h -------------------------------------------------------------------------------- /docs/rendering.rst: -------------------------------------------------------------------------------- 1 | Rendering 2 | ========= 3 | Fwog forgoes framebuffers in favor of specifying a list of render targets at draw time, taking inspiration from `VK_KHR_dynamic_rendering `__. 4 | 5 | Color attachments are bound in the order in which they're provided, which means your fragment shader outputs should use sequential explicit locations starting at zero. 6 | 7 | .. code-block:: cpp 8 | 9 | Fwog::RenderColorAttachments colorAttachments[] = {aAlbedo, aNormal, aMaterial}; 10 | 11 | and the corresponding fragment shader outputs: 12 | 13 | .. code-block:: c 14 | 15 | layout(location = 0) out vec3 o_albedo; 16 | layout(location = 1) out vec3 o_normal; 17 | layout(location = 2) out vec3 o_material; 18 | 19 | To bind the render targets and begin a rendering pass, call :cpp:func:`Fwog::Render`. 20 | 21 | .. code-block:: cpp 22 | 23 | Fwog::Render({ 24 | .colorAttachments = colorAttachments, 25 | .depthAttachment = &depthAttachment, 26 | } 27 | [&] { 28 | // Bind pipelines, resources, and make draw calls here 29 | }); 30 | 31 | Then, you can bind pipelines and resources and issue draw calls inside of the callable that is passed. 32 | 33 | If you wish to render to the screen, call :cpp:func:`Fwog::RenderToSwapchain`. 34 | 35 | Compute 36 | ------- 37 | Compute piplines are similar to graphics pipelines, except they only encapsulate a compute shader. To issue dispatches, call :cpp:func:`Fwog::BeginCompute` to begin a compute scope. 38 | 39 | Color Spaces 40 | ------------ 41 | Fwog enables ``GL_FRAMEBUFFER_SRGB`` by default. :cpp:class:`Fwog::TextureView` can be used to view an image in a different color space if desired. This follows the same rules as `glTextureView `__. 42 | 43 | Synchronization 44 | --------------- 45 | Like in plain OpenGL, most operations are automatically synchronized with respect to each other. However, there are certain instances where the driver may not automatically resolve a hazard. These can be dealt with by calling :cpp:func:`Fwog::MemoryBarrier` and :cpp:func:`Fwog::TextureBarrier`. Consult the OpenGL specification for more information. 46 | 47 | `#include "Fwog/Rendering.h"` 48 | 49 | .. doxygenfile:: Rendering.h -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | breathe -------------------------------------------------------------------------------- /example/01_hello_triangle.cpp: -------------------------------------------------------------------------------- 1 | #include "common/Application.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | /* 01_hello_triangle 12 | * 13 | * This example renders a simple triangle with Fwog. 14 | * 15 | * Shown: 16 | * + Creating vertex buffers 17 | * + Specifying vertex attributes 18 | * + Loading shaders 19 | * + Creating a graphics pipeline 20 | * + Rendering to the screen 21 | * 22 | * All of the examples use a common framework to reduce code duplication between examples. 23 | * It abstracts away boring things like creating a window and loading OpenGL function pointers. 24 | * It also provides a main loop, inside of which our rendering function is called. 25 | */ 26 | 27 | ////////////////////////////////////// Globals 28 | const char* gVertexSource = R"( 29 | #version 460 core 30 | 31 | layout(location = 0) in vec2 a_pos; 32 | layout(location = 1) in vec3 a_color; 33 | 34 | layout(location = 0) out vec3 v_color; 35 | 36 | void main() 37 | { 38 | v_color = a_color; 39 | gl_Position = vec4(a_pos, 0.0, 1.0); 40 | } 41 | )"; 42 | 43 | const char* gFragmentSource = R"( 44 | #version 460 core 45 | 46 | layout(location = 0) out vec4 o_color; 47 | 48 | layout(location = 0) in vec3 v_color; 49 | 50 | void main() 51 | { 52 | o_color = vec4(v_color, 1.0); 53 | } 54 | )"; 55 | 56 | class TriangleApplication final : public Application 57 | { 58 | public: 59 | TriangleApplication(const Application::CreateInfo& createInfo); 60 | 61 | // All Fwog resources will be automatically cleaned up as they go out of scope. 62 | ~TriangleApplication() = default; 63 | 64 | void OnRender(double dt) override; 65 | 66 | private: 67 | static constexpr std::array triPositions = {-0, -0, 1, -1, 1, 1}; 68 | static constexpr std::array triColors = {255, 0, 0, 0, 255, 0, 0, 0, 255}; 69 | 70 | Fwog::Buffer vertexPosBuffer; 71 | Fwog::Buffer vertexColorBuffer; 72 | Fwog::GraphicsPipeline pipeline; 73 | }; 74 | 75 | static Fwog::GraphicsPipeline CreatePipeline() 76 | { 77 | // Specify our two vertex attributes: position and color. 78 | // Positions are 2x float, so we will use R32G32_FLOAT like we would in Vulkan. 79 | auto descPos = Fwog::VertexInputBindingDescription{ 80 | .location = 0, 81 | .binding = 0, 82 | .format = Fwog::Format::R32G32_FLOAT, 83 | .offset = 0, 84 | }; 85 | // Colors are 3x uint8, so we will use R8G8B8. 86 | // To treat them as normalized floats in [0, 1], we make sure it's a _UNORM format. 87 | // This means we do not need to specify whether the data is normalized like we would with glVertexAttribPointer. 88 | auto descColor = Fwog::VertexInputBindingDescription{ 89 | .location = 1, 90 | .binding = 1, 91 | .format = Fwog::Format::R8G8B8_UNORM, 92 | .offset = 0, 93 | }; 94 | // Create an initializer list or array (or anything implicitly convertable to std::span) 95 | // of our input binding descriptions to send to the pipeline. 96 | auto inputDescs = {descPos, descColor}; 97 | 98 | // We compile our shaders here. Just provide the shader source string and you are good to go! 99 | // Note that the shaders are compiled here and throw ShaderCompilationException if they fail. 100 | // The compiler's error message will be stored in the exception. 101 | // In a real application we might handle these exceptions, but here we will let them propagate up. 102 | auto vertexShader = Fwog::Shader(Fwog::PipelineStage::VERTEX_SHADER, gVertexSource, "Triangle VS"); 103 | auto fragmentShader = Fwog::Shader(Fwog::PipelineStage::FRAGMENT_SHADER, gFragmentSource, "Triangle FS"); 104 | 105 | // The graphics pipeline contains all the state necessary for rendering. 106 | // It is self-contained, immutable, and isolated from other pipelines' state (state leaking cannot happen). 107 | // Here we give it our two shaders and the input binding descriptions. 108 | // We could specify a lot more state if we wanted, but for this simple example the defaults will suffice. 109 | // Note that compiling a pipeline will link its non-null shaders together. 110 | // If linking fails, a PipelineCompilationException containing the linker error will be thrown. 111 | // Similar to before, we will let possible exceptions propagate up. 112 | return Fwog::GraphicsPipeline{{ 113 | .name = "Triangle Pipeline", 114 | .vertexShader = &vertexShader, 115 | .fragmentShader = &fragmentShader, 116 | .inputAssemblyState = {.topology = Fwog::PrimitiveTopology::TRIANGLE_LIST}, 117 | .vertexInputState = {inputDescs}, 118 | }}; 119 | } 120 | 121 | TriangleApplication::TriangleApplication(const Application::CreateInfo& createInfo) 122 | : Application(createInfo), 123 | // Load the triangle's vertices (3 * vec2 position, 3 * 8 bit colors). 124 | // The colors will be specified as a UNORM integer format so they are treated as [0, 1] floats in the shader. 125 | vertexPosBuffer(triPositions), 126 | vertexColorBuffer(triColors), 127 | pipeline(CreatePipeline()) 128 | { 129 | } 130 | 131 | // OnRender is automatically called every frame when we run the app 132 | void TriangleApplication::OnRender([[maybe_unused]] double dt) 133 | { 134 | // Before we are allowed to render anything, we must declare what we are rendering to. 135 | // In this case we are rendering straight to the screen, so we can use RenderToSwapchain. 136 | // We are also provided with an opportunity to clear any of the render targets here (by setting the load op to clear). 137 | // We will use it to clear the color buffer with a soothing dark magenta. 138 | Fwog::RenderToSwapchain( 139 | Fwog::SwapchainRenderInfo{ 140 | .name = "Render Triangle", 141 | .viewport = Fwog::Viewport{.drawRect{.offset = {0, 0}, .extent = {windowWidth, windowHeight}}}, 142 | .colorLoadOp = Fwog::AttachmentLoadOp::CLEAR, 143 | .clearColorValue = {.2f, .0f, .2f, 1.0f}, 144 | }, 145 | [&] 146 | { 147 | // Functions in Fwog::Cmd can only be called inside a rendering (Render() or RenderToSwapchain()) or compute scope (Compute()). 148 | // Pipelines must be bound before we can issue drawing-related calls. 149 | // This is where, under the hood, the actual GL program is bound and all the pipeline state is set. 150 | Fwog::Cmd::BindGraphicsPipeline(pipeline); 151 | 152 | // Vertex buffers are bound at draw time, similar to Vulkan or with glBindVertexBuffer. 153 | Fwog::Cmd::BindVertexBuffer(0, vertexPosBuffer, 0, 2 * sizeof(float)); 154 | Fwog::Cmd::BindVertexBuffer(1, vertexColorBuffer, 0, 3 * sizeof(uint8_t)); 155 | 156 | // Let's draw 1 instance with 3 vertices. 157 | Fwog::Cmd::Draw(3, 1, 0, 0); 158 | }); 159 | } 160 | 161 | int main() 162 | { 163 | // Create our app with the specified settings and run it. 164 | auto appInfo = Application::CreateInfo{ 165 | .name = "Hello Triangle", 166 | .maximize = false, 167 | .decorate = true, 168 | .vsync = true, 169 | }; 170 | auto app = TriangleApplication(appInfo); 171 | 172 | app.Run(); 173 | 174 | return 0; 175 | } 176 | -------------------------------------------------------------------------------- /example/06_msaa.cpp: -------------------------------------------------------------------------------- 1 | #include "common/Application.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | /* 06_msaa 17 | * 18 | * This example renders a spinning triangle with MSAA. 19 | * 20 | * All of the examples use a common framework to reduce code duplication between examples. 21 | * It abstracts away boring things like creating a window and loading OpenGL function pointers. 22 | * It also provides a main loop, inside of which our rendering function is called. 23 | */ 24 | 25 | ////////////////////////////////////// Globals 26 | const char* gVertexSource = R"( 27 | #version 460 core 28 | 29 | layout(location = 0) in vec2 a_pos; 30 | layout(location = 1) in vec3 a_color; 31 | 32 | layout(location = 0) out vec3 v_color; 33 | 34 | layout(binding = 0) uniform Uniforms { float time; }; 35 | 36 | void main() 37 | { 38 | v_color = a_color; 39 | 40 | mat2 rot = mat2( 41 | cos(time), sin(time), 42 | -sin(time), cos(time) 43 | ); 44 | 45 | gl_Position = vec4(rot * a_pos, 0.0, 1.0); 46 | } 47 | )"; 48 | 49 | const char* gFragmentSource = R"( 50 | #version 460 core 51 | 52 | layout(location = 0) out vec4 o_color; 53 | 54 | layout(location = 0) in vec3 v_color; 55 | 56 | void main() 57 | { 58 | o_color = vec4(v_color, 1.0); 59 | } 60 | )"; 61 | 62 | class MultisampleApplication final : public Application 63 | { 64 | public: 65 | MultisampleApplication(const Application::CreateInfo& createInfo); 66 | 67 | ~MultisampleApplication() = default; 68 | 69 | void OnWindowResize(uint32_t newWidth, uint32_t newHeight) override; 70 | 71 | void OnRender(double dt) override; 72 | 73 | void OnGui(double dt) override; 74 | 75 | private: 76 | static constexpr std::array triPositions = {-0.5, -0.5, 0.5, -0.5, 0.0, 0.5}; 77 | static constexpr std::array triColors = {255, 0, 0, 0, 255, 0, 0, 0, 255}; 78 | 79 | Fwog::Buffer vertexPosBuffer; 80 | Fwog::Buffer vertexColorBuffer; 81 | Fwog::TypedBuffer timeBuffer; 82 | Fwog::GraphicsPipeline pipeline; 83 | std::optional msColorTex; 84 | std::optional resolveColorTex; 85 | 86 | double timeAccum = 0.0; 87 | Fwog::SampleCount numSamples = Fwog::SampleCount::SAMPLES_8; 88 | }; 89 | 90 | static Fwog::GraphicsPipeline CreatePipeline() 91 | { 92 | auto descPos = Fwog::VertexInputBindingDescription{ 93 | .location = 0, 94 | .binding = 0, 95 | .format = Fwog::Format::R32G32_FLOAT, 96 | .offset = 0, 97 | }; 98 | 99 | auto descColor = Fwog::VertexInputBindingDescription{ 100 | .location = 1, 101 | .binding = 1, 102 | .format = Fwog::Format::R8G8B8_UNORM, 103 | .offset = 0, 104 | }; 105 | 106 | auto inputDescs = {descPos, descColor}; 107 | 108 | auto vertexShader = Fwog::Shader(Fwog::PipelineStage::VERTEX_SHADER, gVertexSource); 109 | auto fragmentShader = Fwog::Shader(Fwog::PipelineStage::FRAGMENT_SHADER, gFragmentSource); 110 | 111 | return Fwog::GraphicsPipeline{{ 112 | .vertexShader = &vertexShader, 113 | .fragmentShader = &fragmentShader, 114 | .inputAssemblyState = {.topology = Fwog::PrimitiveTopology::TRIANGLE_LIST}, 115 | .vertexInputState = {inputDescs}, 116 | }}; 117 | } 118 | 119 | MultisampleApplication::MultisampleApplication(const Application::CreateInfo& createInfo) 120 | : Application(createInfo), 121 | vertexPosBuffer(triPositions), 122 | vertexColorBuffer(triColors), 123 | timeBuffer(Fwog::BufferStorageFlag::DYNAMIC_STORAGE), 124 | pipeline(CreatePipeline()) 125 | { 126 | OnWindowResize(windowWidth, windowHeight); 127 | } 128 | 129 | void MultisampleApplication::OnWindowResize(uint32_t newWidth, uint32_t newHeight) 130 | { 131 | msColorTex = Fwog::Texture({ 132 | .imageType = Fwog::ImageType::TEX_2D_MULTISAMPLE, 133 | .format = Fwog::Format::R8G8B8A8_SRGB, 134 | .extent = {newWidth / 8, newHeight / 8}, 135 | .mipLevels = 1, 136 | .arrayLayers = 1, 137 | .sampleCount = numSamples, 138 | }); 139 | 140 | resolveColorTex = Fwog::Texture({ 141 | .imageType = Fwog::ImageType::TEX_2D, 142 | .format = Fwog::Format::R8G8B8A8_SRGB, 143 | .extent = msColorTex->Extent(), 144 | .mipLevels = 1, 145 | .arrayLayers = 1, 146 | .sampleCount = Fwog::SampleCount::SAMPLES_1, 147 | }); 148 | } 149 | 150 | void MultisampleApplication::OnRender(double dt) 151 | { 152 | timeAccum += dt * 0.02; 153 | timeBuffer.UpdateData(timeAccum); 154 | 155 | auto attachment = Fwog::RenderColorAttachment{ 156 | .texture = msColorTex.value(), 157 | .loadOp = Fwog::AttachmentLoadOp::CLEAR, 158 | .clearValue = {.2f, .0f, .2f, 1.0f}, 159 | }; 160 | 161 | Fwog::Render( 162 | { 163 | .colorAttachments = {&attachment, 1}, 164 | }, 165 | [&] 166 | { 167 | Fwog::Cmd::BindGraphicsPipeline(pipeline); 168 | Fwog::Cmd::BindVertexBuffer(0, vertexPosBuffer, 0, 2 * sizeof(float)); 169 | Fwog::Cmd::BindVertexBuffer(1, vertexColorBuffer, 0, 3 * sizeof(uint8_t)); 170 | Fwog::Cmd::BindUniformBuffer(0, timeBuffer); 171 | Fwog::Cmd::Draw(3, 1, 0, 0); 172 | }); 173 | 174 | // Resolve multisample texture by blitting it to a same-size non-multisample texture 175 | Fwog::BlitTexture(*msColorTex, *resolveColorTex, {}, {}, msColorTex->Extent(), resolveColorTex->Extent(), Fwog::Filter::LINEAR); 176 | 177 | // Blit resolved texture to screen with nearest neighbor filter to make MSAA resolve more obvious 178 | Fwog::BlitTextureToSwapchain(*resolveColorTex, 179 | {}, 180 | {}, 181 | resolveColorTex->Extent(), 182 | {windowWidth, windowHeight, 1}, 183 | Fwog::Filter::NEAREST); 184 | } 185 | 186 | void MultisampleApplication::OnGui(double dt) 187 | { 188 | ImGui::Begin("Options"); 189 | ImGui::Text("Framerate: %.0f Hertz", 1 / dt); 190 | ImGui::Text("Max samples: %d", Fwog::GetDeviceProperties().limits.maxSamples); 191 | ImGui::RadioButton("1 Sample", (int*)&numSamples, 1); 192 | ImGui::RadioButton("2 Samples", (int*)&numSamples, 2); 193 | ImGui::RadioButton("4 Samples", (int*)&numSamples, 4); 194 | ImGui::RadioButton("8 Samples", (int*)&numSamples, 8); 195 | ImGui::RadioButton("16 Samples", (int*)&numSamples, 16); 196 | ImGui::RadioButton("32 Samples", (int*)&numSamples, 32); 197 | ImGui::End(); 198 | 199 | if (numSamples != msColorTex->GetCreateInfo().sampleCount) 200 | { 201 | OnWindowResize(windowWidth, windowHeight); 202 | } 203 | } 204 | 205 | int main() 206 | { 207 | auto appInfo = Application::CreateInfo{ 208 | .name = "MSAA", 209 | .maximize = false, 210 | .decorate = true, 211 | .vsync = true, 212 | }; 213 | auto app = MultisampleApplication(appInfo); 214 | 215 | app.Run(); 216 | 217 | return 0; 218 | } 219 | -------------------------------------------------------------------------------- /example/07_cpp_triangle.cpp: -------------------------------------------------------------------------------- 1 | #include "common/Application.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | /* 07_cpp_triangle 17 | * 18 | * This example renders a simple triangle with Fwog. 19 | * 20 | * Shown: 21 | * + Creating vertex buffers 22 | * + Specifying vertex attributes 23 | * + Loading shaders 24 | * + Creating a graphics pipeline 25 | * + Rendering to the screen 26 | * 27 | * All of the examples use a common framework to reduce code duplication between examples. 28 | * It abstracts away boring things like creating a window and loading OpenGL function pointers. 29 | * It also provides a main loop, inside of which our rendering function is called. 30 | */ 31 | 32 | 33 | ////////////////////////////////////// Globals 34 | const char* gVertexSource = R"( 35 | #version 460 core 36 | 37 | layout(location = 0) in vec2 a_pos; 38 | layout(location = 1) in vec3 a_color; 39 | 40 | layout(location = 0) out vec3 v_color; 41 | 42 | void main() 43 | { 44 | v_color = a_color; 45 | gl_Position = vec4(a_pos, 0.0, 1.0); 46 | } 47 | )"; 48 | 49 | // Fragment shader written in freestanding C++20 50 | const char* gFragmentSourceCpp = R"( 51 | #include 52 | 53 | location(0) output vcc::native_vec4 o_color; 54 | 55 | location(0) input vcc::native_vec3 v_color; 56 | 57 | [[nodiscard]] auto to_vec4(auto vec) -> vcc::native_vec4 58 | { 59 | return {vec.x, vec.y, vec.z, 1.0f}; 60 | } 61 | 62 | extern "C" 63 | fragment_shader 64 | void main() 65 | { 66 | o_color = to_vec4(v_color); 67 | } 68 | )"; 69 | 70 | class CppTriangleApplication final : public Application 71 | { 72 | public: 73 | CppTriangleApplication(const Application::CreateInfo& createInfo); 74 | 75 | ~CppTriangleApplication() = default; 76 | 77 | void OnRender(double dt) override; 78 | 79 | private: 80 | static constexpr std::array triPositions = {-0, -0, 1, -1, 1, 1}; 81 | static constexpr std::array triColors = {255, 0, 0, 0, 255, 0, 0, 0, 255}; 82 | 83 | Fwog::Buffer vertexPosBuffer; 84 | Fwog::Buffer vertexColorBuffer; 85 | Fwog::GraphicsPipeline pipeline; 86 | }; 87 | 88 | static Fwog::GraphicsPipeline CreatePipeline() 89 | { 90 | auto descPos = Fwog::VertexInputBindingDescription{ 91 | .location = 0, 92 | .binding = 0, 93 | .format = Fwog::Format::R32G32_FLOAT, 94 | .offset = 0, 95 | }; 96 | auto descColor = Fwog::VertexInputBindingDescription{ 97 | .location = 1, 98 | .binding = 1, 99 | .format = Fwog::Format::R8G8B8_UNORM, 100 | .offset = 0, 101 | }; 102 | auto inputDescs = {descPos, descColor}; 103 | 104 | auto vertexShader = Fwog::Shader(Fwog::PipelineStage::VERTEX_SHADER, gVertexSource, "Triangle VS"); 105 | const auto fragmentSourceInfo = Fwog::ShaderCppInfo{ 106 | .source = gFragmentSourceCpp, 107 | }; 108 | auto fragmentShader = Fwog::Shader(Fwog::PipelineStage::FRAGMENT_SHADER, fragmentSourceInfo, "Triangle FS"); 109 | 110 | return Fwog::GraphicsPipeline{{ 111 | .name = "Triangle Pipeline", 112 | .vertexShader = &vertexShader, 113 | .fragmentShader = &fragmentShader, 114 | .inputAssemblyState = {.topology = Fwog::PrimitiveTopology::TRIANGLE_LIST}, 115 | .vertexInputState = {inputDescs}, 116 | }}; 117 | } 118 | 119 | CppTriangleApplication::CppTriangleApplication(const Application::CreateInfo& createInfo) 120 | : Application(createInfo), 121 | vertexPosBuffer(triPositions), 122 | vertexColorBuffer(triColors), 123 | pipeline(CreatePipeline()) 124 | { 125 | } 126 | 127 | // OnRender is automatically called every frame when we run the app 128 | void CppTriangleApplication::OnRender([[maybe_unused]] double dt) 129 | { 130 | Fwog::RenderToSwapchain( 131 | Fwog::SwapchainRenderInfo{ 132 | .name = "Render Triangle", 133 | .viewport = Fwog::Viewport{.drawRect{.offset = {0, 0}, .extent = {windowWidth, windowHeight}}}, 134 | .colorLoadOp = Fwog::AttachmentLoadOp::CLEAR, 135 | .clearColorValue = {.2f, .0f, .2f, 1.0f}, 136 | }, 137 | [&] 138 | { 139 | Fwog::Cmd::BindGraphicsPipeline(pipeline); 140 | 141 | Fwog::Cmd::BindVertexBuffer(0, vertexPosBuffer, 0, 2 * sizeof(float)); 142 | Fwog::Cmd::BindVertexBuffer(1, vertexColorBuffer, 0, 3 * sizeof(uint8_t)); 143 | 144 | Fwog::Cmd::Draw(3, 1, 0, 0); 145 | }); 146 | } 147 | 148 | int main() 149 | { 150 | auto appInfo = Application::CreateInfo{ 151 | .name = "Hello Triangle", 152 | .maximize = false, 153 | .decorate = true, 154 | .vsync = true, 155 | }; 156 | auto app = CppTriangleApplication(appInfo); 157 | 158 | app.Run(); 159 | 160 | return 0; 161 | } 162 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | option(FWOG_FSR2_ENABLE "Enable FSR2 for examples that support it (currently 03_gltf_viewer). Windows only!" FALSE) 2 | 3 | add_subdirectory(external) 4 | 5 | add_custom_target(copy_shaders ALL COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/shaders ${CMAKE_CURRENT_BINARY_DIR}/shaders) 6 | add_custom_target(copy_models ALL COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/models ${CMAKE_CURRENT_BINARY_DIR}/models) 7 | add_custom_target(copy_textures ALL COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/textures ${CMAKE_CURRENT_BINARY_DIR}/textures) 8 | 9 | add_executable(01_hello_triangle "01_hello_triangle.cpp" common/Application.cpp common/Application.h vendor/stb_image.cpp) 10 | target_link_libraries(01_hello_triangle PRIVATE glfw lib_glad fwog glm lib_imgui) 11 | 12 | add_executable(02_deferred "02_deferred.cpp" common/Application.cpp common/Application.h common/RsmTechnique.h common/RsmTechnique.cpp vendor/stb_image.cpp) 13 | target_include_directories(02_deferred PUBLIC vendor) 14 | target_link_libraries(02_deferred PRIVATE glfw lib_glad fwog glm lib_imgui fastgltf) 15 | add_dependencies(02_deferred copy_shaders copy_textures) 16 | 17 | add_executable(03_gltf_viewer "03_gltf_viewer.cpp" common/Application.cpp common/Application.h common/SceneLoader.cpp common/SceneLoader.h common/RsmTechnique.h common/RsmTechnique.cpp vendor/stb_image.cpp) 18 | if (FWOG_FSR2_ENABLE) 19 | set(FSR2_LIBS ffx_fsr2_api_x64 ffx_fsr2_api_gl_x64) 20 | target_compile_definitions(03_gltf_viewer PUBLIC FWOG_FSR2_ENABLE) 21 | else() 22 | set(FSR2_LIBS "") 23 | endif() 24 | target_include_directories(03_gltf_viewer PUBLIC ${FSR2_SOURCE} vendor) 25 | target_link_libraries(03_gltf_viewer PRIVATE glfw lib_glad fwog glm lib_imgui ${FSR2_LIBS} ktx fastgltf) 26 | add_dependencies(03_gltf_viewer copy_shaders copy_models copy_textures) 27 | 28 | add_executable(04_volumetric "04_volumetric.cpp" common/Application.cpp common/Application.h common/SceneLoader.cpp common/SceneLoader.h vendor/stb_image.cpp) 29 | target_include_directories(04_volumetric PUBLIC vendor) 30 | target_link_libraries(04_volumetric PRIVATE glfw lib_glad fwog glm lib_imgui ktx fastgltf) 31 | add_dependencies(04_volumetric copy_shaders copy_models copy_textures) 32 | 33 | add_executable(05_gpu_driven "05_gpu_driven.cpp" common/Application.cpp common/Application.h common/SceneLoader.cpp common/SceneLoader.h vendor/stb_image.cpp) 34 | target_include_directories(05_gpu_driven PUBLIC vendor) 35 | target_link_libraries(05_gpu_driven PRIVATE glfw lib_glad fwog glm lib_imgui ktx fastgltf) 36 | add_dependencies(05_gpu_driven copy_shaders copy_models) 37 | 38 | add_executable(06_msaa "06_msaa.cpp" common/Application.cpp common/Application.h vendor/stb_image.cpp) 39 | target_link_libraries(06_msaa PRIVATE glfw lib_glad fwog glm lib_imgui) 40 | 41 | if (FWOG_VCC_ENABLE) 42 | add_executable(07_cpp_triangle "07_cpp_triangle.cpp" common/Application.cpp common/Application.h vendor/stb_image.cpp) 43 | target_link_libraries(07_cpp_triangle PRIVATE glfw lib_glad fwog glm lib_imgui) 44 | add_dependencies(07_cpp_triangle copy_shaders) 45 | endif() 46 | 47 | if (MSVC) 48 | target_compile_definitions(03_gltf_viewer PUBLIC STBI_MSC_SECURE_CRT) 49 | target_compile_definitions(04_volumetric PUBLIC STBI_MSC_SECURE_CRT) 50 | target_compile_definitions(05_gpu_driven PUBLIC STBI_MSC_SECURE_CRT) 51 | endif() -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Examples in this folder 2 | 3 | ## 01_hello_triangle 4 | 5 | A simple example showing how to render an RGB triangle. 6 | ![hello_triangle](media/hello_triangle.png "A large triangle with red, blue, and green vertices on a magenta background") 7 | 8 | ## 02_deferred 9 | 10 | An implementation of a basic deferred renderer with a single directional light. This example also implements the paper Reflective Shadow Maps. 11 | ![deferred](media/deferred.png "Cornell box-like scene with a single light coming from the viewer and color from the walls softly bleeding onto the others") 12 | 13 | ## 03_gltf_viewer 14 | 15 | A program that demonstrates the loading and rendering of glTF scene files using tinygltf and Fwog. Sponza glTF not included. 16 | ![gltf_viewer](media/gltf_viewer.png "View of the atrium in Sponza from below, with the sun illuminating the center of the ground floor") 17 | 18 | ## 04_volumetric 19 | 20 | A ray-marched volumetric fog implementation using a frustum-aligned 3D grid. Supports fog shadows and local lights. 21 | ![volumetric](media/volumetric0.png "A forest scene featuring a cube of fog and some local lights illuminating it") 22 | 23 | ## 05_gpu_driven 24 | 25 | An example using bindless textures, GPU-driven fragment shader occlusion culling, and indirect multidraw to minimize draw calls. 26 | ![gpu_driven](media/gpu_driven.png "A forest scene with wireframe bounding boxes around each object") 27 | 28 | ## 06_msaa 29 | 30 | Shows how to render a spinning triangle to a multisample image and resolve it. 31 | ![msaa](media/msaa.png "An RGB triangle with smooth, antialiased edges on a magenta background") 32 | -------------------------------------------------------------------------------- /example/common/Application.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | struct GLFWwindow; 13 | 14 | // Represents the camera's position and orientation. 15 | struct View 16 | { 17 | glm::vec3 position{}; 18 | float pitch{}; // pitch angle in radians 19 | float yaw{}; // yaw angle in radians 20 | 21 | glm::vec3 GetForwardDir() const; 22 | 23 | glm::mat4 GetViewMatrix() const; 24 | }; 25 | 26 | class Application 27 | { 28 | public: 29 | struct CreateInfo 30 | { 31 | std::string_view name = ""; 32 | bool maximize = false; 33 | bool decorate = true; 34 | bool vsync = true; 35 | }; 36 | 37 | // TODO: An easy way to load shaders should probably be a part of Fwog 38 | static std::string LoadFile(const std::filesystem::path& path); 39 | static std::pair, std::size_t> LoadBinaryFile(const std::filesystem::path& path); 40 | 41 | Application(const CreateInfo& createInfo); 42 | Application(const Application&) = delete; 43 | Application(Application&&) noexcept = delete; 44 | Application& operator=(const Application&) = delete; 45 | Application& operator=(Application&&) noexcept = delete; 46 | 47 | virtual ~Application(); 48 | 49 | void Run(); 50 | 51 | protected: 52 | virtual void OnWindowResize([[maybe_unused]] uint32_t newWidth, [[maybe_unused]] uint32_t newHeight){} 53 | virtual void OnUpdate([[maybe_unused]] double dt){} 54 | virtual void OnRender([[maybe_unused]] double dt){} 55 | virtual void OnGui([[maybe_unused]] double dt){} 56 | 57 | GLFWwindow* window; 58 | View mainCamera{}; 59 | float cursorSensitivity = 0.0025f; 60 | float cameraSpeed = 4.5f; 61 | bool cursorIsActive = true; 62 | 63 | uint32_t windowWidth{}; 64 | uint32_t windowHeight{}; 65 | 66 | private: 67 | friend class ApplicationAccess; 68 | 69 | void Draw(double dt); 70 | 71 | double previousCursorPosX{}; 72 | double previousCursorPosY{}; 73 | double cursorFrameOffsetX{}; 74 | double cursorFrameOffsetY{}; 75 | bool cursorJustEnteredWindow = true; 76 | bool graveHeldLastFrame = false; 77 | }; -------------------------------------------------------------------------------- /example/common/RsmTechnique.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace RSM 12 | { 13 | struct CameraUniforms 14 | { 15 | glm::mat4 viewProj; 16 | glm::mat4 invViewProj; 17 | glm::mat4 proj; 18 | glm::vec4 cameraPos; 19 | glm::vec3 viewDir; 20 | uint32_t _padding00; 21 | glm::vec2 jitterOffset{}; 22 | glm::vec2 lastFrameJitterOffset{}; 23 | }; 24 | 25 | class RsmTechnique 26 | { 27 | public: 28 | RsmTechnique(uint32_t width, uint32_t height); 29 | 30 | void SetResolution(uint32_t newWidth, uint32_t newHeight); 31 | 32 | // Input: camera uniforms, g-buffers, RSM buffers, previous g-buffer depth (for reprojection) 33 | void ComputeIndirectLighting(const glm::mat4& lightViewProj, 34 | const CameraUniforms& cameraUniforms, 35 | const Fwog::Texture& gAlbedo, 36 | const Fwog::Texture& gNormal, 37 | const Fwog::Texture& gDepth, 38 | const Fwog::Texture& rsmFlux, 39 | const Fwog::Texture& rsmNormal, 40 | const Fwog::Texture& rsmDepth, 41 | const Fwog::Texture& gDepthPrev, 42 | const Fwog::Texture& gNormalPrev, 43 | const Fwog::Texture& gMotion); 44 | 45 | Fwog::Texture& GetIndirectLighting(); 46 | 47 | void DrawGui(); 48 | 49 | int inverseResolutionScale = 1; 50 | int smallRsmSize = 512; 51 | int rsmSamples = 400; 52 | int rsmFilteredSamples = 8; 53 | float rMax = 0.2f; 54 | float spatialFilterStep = 1.0f; 55 | float alphaIlluminance = 0.05f; 56 | float phiNormal = 0.3f; 57 | float phiDepth = 0.2f; 58 | bool rsmFiltered = true; 59 | bool rsmFilteredSkipAlbedoModulation = false; 60 | bool seedEachFrame = true; 61 | bool useSeparableFilter = true; 62 | 63 | private: 64 | struct RsmUniforms 65 | { 66 | glm::mat4 sunViewProj; 67 | glm::mat4 invSunViewProj; 68 | glm::ivec2 targetDim; 69 | float rMax; 70 | uint32_t currentPass; 71 | uint32_t samples; 72 | uint32_t _padding00; 73 | glm::vec2 random; 74 | }; 75 | 76 | struct ReprojectionUniforms 77 | { 78 | glm::mat4 invViewProjCurrent; 79 | glm::mat4 viewProjPrevious; 80 | glm::mat4 invViewProjPrevious; 81 | glm::mat4 proj; 82 | glm::vec3 viewPos; 83 | float temporalWeightFactor; 84 | glm::ivec2 targetDim; 85 | float alphaIlluminance; 86 | float phiDepth; 87 | float phiNormal; 88 | uint32_t _padding00; 89 | glm::vec2 jitterOffset; 90 | glm::vec2 lastFrameJitterOffset; 91 | }; 92 | 93 | struct FilterUniforms 94 | { 95 | glm::mat4 proj; 96 | glm::mat4 invViewProj; 97 | glm::vec3 viewPos; 98 | float stepWidth; 99 | glm::ivec2 targetDim; 100 | glm::ivec2 direction; 101 | float phiNormal; 102 | float phiDepth; 103 | uint32_t _padding00; 104 | uint32_t _padding01; 105 | }; 106 | 107 | uint32_t width; 108 | uint32_t height; 109 | uint32_t internalWidth; 110 | uint32_t internalHeight; 111 | glm::mat4 viewProjPrevious{1}; 112 | glm::uint seedX; 113 | glm::uint seedY; 114 | RsmUniforms rsmUniforms; 115 | Fwog::TypedBuffer rsmUniformBuffer; 116 | Fwog::TypedBuffer cameraUniformBuffer; 117 | Fwog::TypedBuffer reprojectionUniformBuffer; 118 | Fwog::TypedBuffer filterUniformBuffer; 119 | Fwog::ComputePipeline rsmIndirectPipeline; 120 | Fwog::ComputePipeline rsmIndirectFilteredPipeline; 121 | Fwog::ComputePipeline rsmReprojectPipeline; 122 | Fwog::ComputePipeline bilateral5x5Pipeline; 123 | Fwog::ComputePipeline modulatePipeline; 124 | Fwog::ComputePipeline modulateUpscalePipeline; 125 | Fwog::ComputePipeline blitPipeline; 126 | std::optional indirectUnfilteredTex; 127 | std::optional indirectUnfilteredTexPrev; // for temporal accumulation 128 | std::optional indirectFilteredTex; 129 | std::optional indirectFilteredTexPingPong; 130 | std::optional historyLengthTex; 131 | std::optional illuminationUpscaled; 132 | std::optional rsmFluxSmall; 133 | std::optional rsmNormalSmall; 134 | std::optional rsmDepthSmall; 135 | std::optional noiseTex; 136 | std::optional gNormalSmall; 137 | std::optional gDepthSmall; 138 | std::optional gNormalPrevSmall; 139 | std::optional gDepthPrevSmall; 140 | }; 141 | } // namespace RSM -------------------------------------------------------------------------------- /example/common/SceneLoader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace Utility 16 | { 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 | 26 | struct Box3D 27 | { 28 | glm::vec3 offset; 29 | glm::vec3 halfExtent; 30 | }; 31 | 32 | struct CombinedTextureSampler 33 | { 34 | Fwog::TextureView texture; 35 | Fwog::SamplerState sampler; 36 | }; 37 | 38 | enum class MaterialFlagBit 39 | { 40 | HAS_BASE_COLOR_TEXTURE = 1 << 0, 41 | }; 42 | FWOG_DECLARE_FLAG_TYPE(MaterialFlags, MaterialFlagBit, uint32_t) 43 | 44 | struct GpuMaterial 45 | { 46 | MaterialFlags flags{}; 47 | float alphaCutoff{}; 48 | uint32_t pad01{}; 49 | uint32_t pad02{}; 50 | glm::vec4 baseColorFactor{}; 51 | }; 52 | 53 | struct GpuMaterialBindless 54 | { 55 | MaterialFlags flags{}; 56 | float alphaCutoff{}; 57 | uint64_t baseColorTextureHandle{}; 58 | glm::vec4 baseColorFactor{}; 59 | }; 60 | 61 | struct Material 62 | { 63 | GpuMaterial gpuMaterial{}; 64 | std::optional albedoTextureSampler; 65 | }; 66 | 67 | struct Mesh 68 | { 69 | //const GeometryBuffers* buffers; 70 | Fwog::Buffer vertexBuffer; 71 | Fwog::Buffer indexBuffer; 72 | uint32_t materialIdx{}; 73 | glm::mat4 transform{}; 74 | }; 75 | 76 | struct Scene 77 | { 78 | std::vector meshes; 79 | std::vector materials; 80 | }; 81 | 82 | struct MeshBindless 83 | { 84 | int32_t startVertex{}; 85 | uint32_t startIndex{}; 86 | uint32_t indexCount{}; 87 | uint32_t materialIdx{}; 88 | glm::mat4 transform{}; 89 | Box3D boundingBox{}; 90 | }; 91 | 92 | struct SceneBindless 93 | { 94 | std::vector meshes; 95 | std::vector vertices; 96 | std::vector indices; 97 | std::vector materials; 98 | std::vector textures; 99 | std::vector samplers; 100 | }; 101 | 102 | bool LoadModelFromFile(Scene& scene, 103 | std::string_view fileName, 104 | glm::mat4 rootTransform = glm::mat4{ 1 }, 105 | bool binary = false); 106 | 107 | bool LoadModelFromFileBindless(SceneBindless& scene, 108 | std::string_view fileName, 109 | glm::mat4 rootTransform = glm::mat4{ 1 }, 110 | bool binary = false); 111 | } -------------------------------------------------------------------------------- /example/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.3.2 12 | ) 13 | 14 | FetchContent_Declare( 15 | glm 16 | GIT_REPOSITORY https://github.com/g-truc/glm 17 | GIT_TAG cc98465e3508535ba8c7f6208df934c156a018dc 18 | ) 19 | 20 | FetchContent_Declare( 21 | fastgltf 22 | GIT_REPOSITORY https://github.com/spnda/fastgltf.git 23 | GIT_TAG 9bb00ddbde6f23f1cab0809112adf80ac3c735e1 24 | ) 25 | 26 | FetchContent_Declare( 27 | imgui 28 | GIT_REPOSITORY https://github.com/ocornut/imgui 29 | GIT_TAG v1.88 30 | ) 31 | 32 | option(KTX_FEATURE_TESTS "" OFF) 33 | option(KTX_FEATURE_VULKAN "" OFF) 34 | option(KTX_FEATURE_GL_UPLOAD "" OFF) 35 | option(KTX_FEATURE_VK_UPLOAD "" OFF) 36 | option(KTX_FEATURE_WRITE "" OFF) 37 | option(KTX_FEATURE_TOOLS "" OFF) 38 | FetchContent_Declare( 39 | ktx 40 | GIT_REPOSITORY https://github.com/KhronosGroup/KTX-Software.git 41 | GIT_TAG v4.1.0 42 | ) 43 | 44 | if(FWOG_FSR2_ENABLE) 45 | option(GFX_API_DX12 "" OFF) 46 | option(GFX_API_VK "" OFF) 47 | option(GFX_API_GL "" ON) 48 | option(FFX_FSR2_API_DX12 "" OFF) 49 | option(FFX_FSR2_API_VK "" OFF) 50 | option(FFX_FSR2_API_GL "" ON) 51 | FetchContent_Declare( 52 | fsr2 53 | GIT_REPOSITORY https://github.com/JuanDiegoMontoya/FidelityFX-FSR2.git 54 | GIT_TAG a5d60a6f581182166dbb5fac89b77212e028ea91 55 | GIT_SUBMODULES "" 56 | ) 57 | FetchContent_MakeAvailable(fsr2) 58 | endif() 59 | 60 | FetchContent_MakeAvailable(glm fastgltf ktx glfw) 61 | 62 | # Disable warnings for libktx 63 | target_compile_options(ktx 64 | PRIVATE 65 | $<$,$,$>: 66 | -Wno-everything> 67 | $<$: 68 | -w> 69 | ) 70 | 71 | set(FSR2_SOURCE ${fsr2_SOURCE_DIR} PARENT_SCOPE) 72 | 73 | FetchContent_GetProperties(imgui) 74 | if(NOT imgui_POPULATED) 75 | FetchContent_Populate(imgui) 76 | 77 | add_library(lib_imgui 78 | ${imgui_SOURCE_DIR}/imgui.cpp 79 | ${imgui_SOURCE_DIR}/imgui_demo.cpp 80 | ${imgui_SOURCE_DIR}/imgui_draw.cpp 81 | ${imgui_SOURCE_DIR}/imgui_widgets.cpp 82 | ${imgui_SOURCE_DIR}/imgui_tables.cpp 83 | ${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp 84 | ${imgui_SOURCE_DIR}/backends/imgui_impl_glfw.cpp 85 | ) 86 | 87 | target_include_directories(lib_imgui PUBLIC 88 | ${imgui_SOURCE_DIR} 89 | ${imgui_SOURCE_DIR}/backends 90 | ${glfw_SOURCE_DIR}/include 91 | ) 92 | 93 | target_link_libraries(lib_imgui PRIVATE glfw) 94 | endif() 95 | 96 | target_compile_definitions(glm INTERFACE GLM_FORCE_SILENT_WARNINGS) -------------------------------------------------------------------------------- /example/media/deferred.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Fwog/ca0317d3cf0f8b15656ed7e6508df4e2e4ac27fe/example/media/deferred.png -------------------------------------------------------------------------------- /example/media/gltf_viewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Fwog/ca0317d3cf0f8b15656ed7e6508df4e2e4ac27fe/example/media/gltf_viewer.png -------------------------------------------------------------------------------- /example/media/gpu_driven.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Fwog/ca0317d3cf0f8b15656ed7e6508df4e2e4ac27fe/example/media/gpu_driven.png -------------------------------------------------------------------------------- /example/media/hello_triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Fwog/ca0317d3cf0f8b15656ed7e6508df4e2e4ac27fe/example/media/hello_triangle.png -------------------------------------------------------------------------------- /example/media/msaa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Fwog/ca0317d3cf0f8b15656ed7e6508df4e2e4ac27fe/example/media/msaa.png -------------------------------------------------------------------------------- /example/media/volumetric0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Fwog/ca0317d3cf0f8b15656ed7e6508df4e2e4ac27fe/example/media/volumetric0.png -------------------------------------------------------------------------------- /example/models/README.md: -------------------------------------------------------------------------------- 1 | # Put models here 2 | -------------------------------------------------------------------------------- /example/models/hierarchyTest.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Fwog/ca0317d3cf0f8b15656ed7e6508df4e2e4ac27fe/example/models/hierarchyTest.glb -------------------------------------------------------------------------------- /example/models/simple_scene.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Fwog/ca0317d3cf0f8b15656ed7e6508df4e2e4ac27fe/example/models/simple_scene.glb -------------------------------------------------------------------------------- /example/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_VertexID == 0, gl_VertexID == 2); 8 | v_uv = pos.xy * 2.0; 9 | gl_Position = vec4(pos * 4.0 - 1.0, 0.0, 1.0); 10 | } -------------------------------------------------------------------------------- /example/shaders/RSMScene.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(location = 0) out vec3 o_flux; 4 | layout(location = 1) out vec3 o_normal; 5 | 6 | layout(location = 0) in vec3 v_position; 7 | layout(location = 1) in vec3 v_normal; 8 | layout(location = 2) in vec2 v_uv; 9 | layout(location = 3) in vec3 v_color; 10 | 11 | layout(binding = 1, std140) uniform ShadingUniforms 12 | { 13 | mat4 sunViewProj; 14 | vec4 sunDir; 15 | vec4 sunStrength; 16 | }shadingUniforms; 17 | 18 | void main() 19 | { 20 | o_normal = normalize(v_normal); 21 | o_flux = v_color * shadingUniforms.sunStrength.rgb;// * max(dot(o_normal, -normalize(shadingUniforms.sunDir.xyz)), 0.0); 22 | } -------------------------------------------------------------------------------- /example/shaders/RSMScenePbr.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(location = 0) out vec3 o_flux; 4 | layout(location = 1) out vec3 o_normal; 5 | 6 | layout(location = 0) in vec3 v_position; 7 | layout(location = 1) in vec3 v_normal; 8 | layout(location = 2) in vec2 v_uv; 9 | 10 | layout(binding = 0) uniform sampler2D s_baseColor; 11 | 12 | layout(binding = 1, std140) uniform ShadingUniforms 13 | { 14 | mat4 sunViewProj; 15 | vec4 sunDir; 16 | vec4 sunStrength; 17 | }shadingUniforms; 18 | 19 | #define HAS_BASE_COLOR_TEXTURE (1 << 0) 20 | layout(binding = 2, std140) uniform MaterialUniforms 21 | { 22 | uint flags; 23 | float alphaCutoff; 24 | uint pad01; 25 | uint pad02; 26 | vec4 baseColorFactor; 27 | }u_material; 28 | 29 | void main() 30 | { 31 | vec4 color = u_material.baseColorFactor.rgba; 32 | if ((u_material.flags & HAS_BASE_COLOR_TEXTURE) != 0) 33 | { 34 | color *= texture(s_baseColor, v_uv); 35 | } 36 | 37 | color.rgb *= shadingUniforms.sunStrength.rgb; 38 | 39 | if (color.a < u_material.alphaCutoff) 40 | { 41 | discard; 42 | } 43 | 44 | o_flux = color.rgb; 45 | o_normal = normalize(v_normal); 46 | } -------------------------------------------------------------------------------- /example/shaders/SceneDeferred.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(location = 0) out vec3 o_color; 4 | layout(location = 1) out vec3 o_normal; 5 | layout(location = 2) out vec2 o_motion; 6 | 7 | layout(location = 0) in vec3 v_position; 8 | layout(location = 1) in vec3 v_normal; 9 | layout(location = 2) in vec2 v_uv; 10 | layout(location = 3) in vec3 v_color; 11 | layout(location = 4) in vec4 v_curPos; 12 | layout(location = 5) in vec4 v_oldPos; 13 | 14 | void main() 15 | { 16 | o_color = v_color; 17 | o_normal = normalize(v_normal); 18 | o_motion = ((v_oldPos.xy / v_oldPos.w) - (v_curPos.xy / v_curPos.w)) * 0.5; 19 | } -------------------------------------------------------------------------------- /example/shaders/SceneDeferred.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(location = 0) in vec3 a_pos; 4 | layout(location = 1) in vec3 a_normal; 5 | layout(location = 2) in vec2 a_uv; 6 | 7 | layout(location = 0) out vec3 v_position; 8 | layout(location = 1) out vec3 v_normal; 9 | layout(location = 2) out vec2 v_uv; 10 | layout(location = 3) out vec3 v_color; 11 | layout(location = 4) out vec4 v_curPos; 12 | layout(location = 5) out vec4 v_oldPos; 13 | 14 | layout(binding = 0, std140) uniform GlobalUniforms 15 | { 16 | mat4 viewProj; 17 | mat4 oldViewProj; 18 | mat4 invViewProj; 19 | mat4 proj; 20 | vec4 cameraPos; 21 | }; 22 | 23 | struct ObjectUniforms 24 | { 25 | mat4 model; 26 | vec4 color; 27 | }; 28 | 29 | layout(binding = 1, std430) readonly buffer SSBO0 30 | { 31 | ObjectUniforms objects[]; 32 | }; 33 | 34 | void main() 35 | { 36 | int i = gl_InstanceID; 37 | v_position = (objects[i].model * vec4(a_pos, 1.0)).xyz; 38 | v_normal = normalize(inverse(transpose(mat3(objects[i].model))) * a_normal); 39 | v_uv = a_uv; 40 | v_color = objects[i].color.rgb; 41 | gl_Position = viewProj * vec4(v_position, 1.0); 42 | v_curPos = gl_Position; 43 | v_oldPos = oldViewProj * vec4(v_position, 1.0); 44 | } -------------------------------------------------------------------------------- /example/shaders/SceneDeferredPbr.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(location = 0) out vec3 o_color; 4 | layout(location = 1) out vec3 o_normal; 5 | layout(location = 2) out vec2 o_motion; 6 | 7 | layout(location = 0) in vec3 v_position; 8 | layout(location = 1) in vec3 v_normal; 9 | layout(location = 2) in vec2 v_uv; 10 | layout(location = 3) in vec4 v_curPos; 11 | layout(location = 4) in vec4 v_oldPos; 12 | 13 | layout(binding = 0) uniform sampler2D s_baseColor; 14 | 15 | #define HAS_BASE_COLOR_TEXTURE (1 << 0) 16 | layout(binding = 2, std140) uniform MaterialUniforms 17 | { 18 | uint flags; 19 | float alphaCutoff; 20 | uint pad01; 21 | uint pad02; 22 | vec4 baseColorFactor; 23 | }u_material; 24 | 25 | void main() 26 | { 27 | vec4 color = u_material.baseColorFactor.rgba; 28 | if ((u_material.flags & HAS_BASE_COLOR_TEXTURE) != 0) 29 | { 30 | color *= texture(s_baseColor, v_uv).rgba; 31 | } 32 | 33 | if (color.a < u_material.alphaCutoff) 34 | { 35 | discard; 36 | } 37 | 38 | o_color = color.rgb; 39 | o_normal = normalize(v_normal); 40 | // motion in uv space [0, 1] 41 | o_motion = ((v_oldPos.xy / v_oldPos.w) - (v_curPos.xy / v_curPos.w)) * 0.5; 42 | } -------------------------------------------------------------------------------- /example/shaders/SceneDeferredPbr.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(location = 0) in vec3 a_pos; 4 | layout(location = 1) in vec2 a_normal; 5 | layout(location = 2) in vec2 a_uv; 6 | 7 | layout(location = 0) out vec3 v_position; 8 | layout(location = 1) out vec3 v_normal; 9 | layout(location = 2) out vec2 v_uv; 10 | layout(location = 3) out vec4 v_curPos; 11 | layout(location = 4) out vec4 v_oldPos; 12 | 13 | layout(binding = 0, std140) uniform UBO0 14 | { 15 | mat4 viewProj; 16 | mat4 oldViewProjUnjittered; 17 | mat4 viewProjUnjittered; 18 | mat4 invViewProj; 19 | mat4 proj; 20 | vec4 cameraPos; 21 | }; 22 | 23 | struct ObjectUniforms 24 | { 25 | mat4 model; 26 | }; 27 | 28 | layout(binding = 1, std430) readonly buffer SSBO0 29 | { 30 | ObjectUniforms objects[]; 31 | }; 32 | 33 | vec2 signNotZero(vec2 v) 34 | { 35 | return vec2((v.x >= 0.0) ? +1.0 : -1.0, (v.y >= 0.0) ? +1.0 : -1.0); 36 | } 37 | 38 | vec3 oct_to_float32x3(vec2 e) 39 | { 40 | vec3 v = vec3(e.xy, 1.0 - abs(e.x) - abs(e.y)); 41 | if (v.z < 0) v.xy = (1.0 - abs(v.yx)) * signNotZero(v.xy); 42 | return normalize(v); 43 | } 44 | 45 | void main() 46 | { 47 | int i = gl_InstanceID + gl_BaseInstance; 48 | v_position = (objects[i].model * vec4(a_pos, 1.0)).xyz; 49 | v_normal = normalize(inverse(transpose(mat3(objects[i].model))) * oct_to_float32x3(a_normal)); 50 | v_uv = a_uv; 51 | gl_Position = viewProj * vec4(v_position, 1.0); 52 | v_curPos = viewProjUnjittered * vec4(v_position, 1.0); 53 | v_oldPos = oldViewProjUnjittered * vec4(v_position, 1.0); 54 | } -------------------------------------------------------------------------------- /example/shaders/SceneDeferredSimple.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(location = 0) out vec3 o_color; 4 | layout(location = 1) out vec3 o_normal; 5 | 6 | layout(location = 0) in vec3 v_position; 7 | layout(location = 1) in vec3 v_normal; 8 | layout(location = 2) in vec2 v_uv; 9 | 10 | layout(binding = 0) uniform sampler2D s_baseColor; 11 | 12 | #define HAS_BASE_COLOR_TEXTURE (1 << 0) 13 | layout(binding = 2, std140) uniform MaterialUniforms 14 | { 15 | uint flags; 16 | float alphaCutoff; 17 | uint pad01; 18 | uint pad02; 19 | vec4 baseColorFactor; 20 | }u_material; 21 | 22 | void main() 23 | { 24 | vec4 color = u_material.baseColorFactor.rgba; 25 | if ((u_material.flags & HAS_BASE_COLOR_TEXTURE) != 0) 26 | { 27 | color *= texture(s_baseColor, v_uv).rgba; 28 | } 29 | 30 | if (color.a < u_material.alphaCutoff) 31 | { 32 | discard; 33 | } 34 | 35 | o_color = color.rgb; 36 | o_normal = normalize(v_normal); 37 | } -------------------------------------------------------------------------------- /example/shaders/SceneDeferredSimple.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(location = 0) in vec3 a_pos; 4 | layout(location = 1) in vec2 a_normal; 5 | layout(location = 2) in vec2 a_uv; 6 | 7 | layout(location = 0) out vec3 v_position; 8 | layout(location = 1) out vec3 v_normal; 9 | layout(location = 2) out vec2 v_uv; 10 | 11 | layout(binding = 0, std140) uniform UBO0 12 | { 13 | mat4 viewProj; 14 | }; 15 | 16 | struct ObjectUniforms 17 | { 18 | mat4 model; 19 | }; 20 | 21 | layout(binding = 1, std430) readonly buffer SSBO0 22 | { 23 | ObjectUniforms objects[]; 24 | }; 25 | 26 | vec2 signNotZero(vec2 v) 27 | { 28 | return vec2((v.x >= 0.0) ? +1.0 : -1.0, (v.y >= 0.0) ? +1.0 : -1.0); 29 | } 30 | 31 | vec3 oct_to_float32x3(vec2 e) 32 | { 33 | vec3 v = vec3(e.xy, 1.0 - abs(e.x) - abs(e.y)); 34 | if (v.z < 0) v.xy = (1.0 - abs(v.yx)) * signNotZero(v.xy); 35 | return normalize(v); 36 | } 37 | 38 | void main() 39 | { 40 | int i = gl_InstanceID + gl_BaseInstance; 41 | v_position = (objects[i].model * vec4(a_pos, 1.0)).xyz; 42 | v_normal = normalize(inverse(transpose(mat3(objects[i].model))) * oct_to_float32x3(a_normal)); 43 | v_uv = a_uv; 44 | gl_Position = viewProj * vec4(v_position, 1.0); 45 | } -------------------------------------------------------------------------------- /example/shaders/SceneShadow.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(location = 0) in vec3 a_pos; 4 | 5 | layout(binding = 0, std140) uniform UBO0 6 | { 7 | mat4 viewProj; 8 | }; 9 | 10 | struct ObjectUniforms 11 | { 12 | mat4 model; 13 | }; 14 | 15 | layout(binding = 1, std430) readonly buffer SSBO0 16 | { 17 | ObjectUniforms objects[]; 18 | }; 19 | 20 | void main() 21 | { 22 | int i = gl_InstanceID + gl_BaseInstance; 23 | gl_Position = viewProj * objects[i].model * vec4(a_pos, 1.0); 24 | } -------------------------------------------------------------------------------- /example/shaders/ShadeDeferred.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(binding = 0) uniform sampler2D s_gAlbedo; 4 | layout(binding = 1) uniform sampler2D s_gNormal; 5 | layout(binding = 2) uniform sampler2D s_gDepth; 6 | layout(binding = 3) uniform sampler2D s_rsmIndirect; 7 | layout(binding = 4) uniform sampler2D s_rsmDepthShadow; 8 | 9 | layout(location = 0) in vec2 v_uv; 10 | 11 | layout(location = 0) out vec3 o_color; 12 | 13 | layout(binding = 0, std140) uniform GlobalUniforms 14 | { 15 | mat4 viewProj; 16 | mat4 oldViewProj; 17 | mat4 invViewProj; 18 | mat4 proj; 19 | vec4 cameraPos; 20 | }; 21 | 22 | layout(binding = 1, std140) uniform ShadingUniforms 23 | { 24 | mat4 sunViewProj; 25 | vec4 sunDir; 26 | vec4 sunStrength; 27 | }shadingUniforms; 28 | 29 | vec3 UnprojectUV(float depth, vec2 uv, mat4 invXProj) 30 | { 31 | float z = depth * 2.0 - 1.0; // OpenGL Z convention 32 | vec4 ndc = vec4(uv * 2.0 - 1.0, z, 1.0); 33 | vec4 world = invXProj * ndc; 34 | return world.xyz / world.w; 35 | } 36 | 37 | float Shadow(vec4 clip, vec3 normal, vec3 lightDir) 38 | { 39 | vec2 uv = clip.xy * .5 + .5; 40 | if (uv.x < 0 || uv.x > 1 || uv.y < 0 || uv.y > 1) 41 | { 42 | return 0; 43 | } 44 | 45 | float viewDepth = clip.z * .5 + .5; 46 | float lightDepth = textureLod(s_rsmDepthShadow, uv, 0).x; 47 | 48 | // Analytically compute slope-scaled bias 49 | const float maxBias = 0.0018; 50 | const float quantize = 2.0 / (1 << 23); 51 | ivec2 res = textureSize(s_rsmDepthShadow, 0); 52 | float b = 1.0 / max(res.x, res.y) / 2.0; 53 | float NoD = clamp(-dot(shadingUniforms.sunDir.xyz, normal), 0.0, 1.0); 54 | float bias = quantize + b * length(cross(-shadingUniforms.sunDir.xyz, normal)) / NoD; 55 | bias = min(bias, maxBias); 56 | 57 | lightDepth += bias; 58 | 59 | float lightOcclusion = 0.0; 60 | if (lightDepth >= viewDepth) 61 | { 62 | lightOcclusion += 1.0; 63 | } 64 | 65 | return lightOcclusion; 66 | } 67 | 68 | void main() 69 | { 70 | vec3 albedo = textureLod(s_gAlbedo, v_uv, 0.0).rgb; 71 | vec3 normal = textureLod(s_gNormal, v_uv, 0.0).xyz; 72 | float depth = textureLod(s_gDepth, v_uv, 0.0).x; 73 | 74 | if (depth == 1.0) 75 | { 76 | discard; 77 | } 78 | 79 | vec3 worldPos = UnprojectUV(depth, v_uv, invViewProj); 80 | 81 | vec3 incidentDir = -shadingUniforms.sunDir.xyz; 82 | float cosTheta = max(0.0, dot(incidentDir, normal)); 83 | vec3 diffuse = albedo * cosTheta * shadingUniforms.sunStrength.rgb; 84 | diffuse *= Shadow(shadingUniforms.sunViewProj * vec4(worldPos, 1.0), normal, shadingUniforms.sunDir.xyz); 85 | 86 | //vec3 ambient = vec3(.03) * albedo; 87 | vec3 ambient = textureLod(s_rsmIndirect, v_uv, 0).rgb; 88 | vec3 finalColor = diffuse + ambient; 89 | 90 | // tone mapping (optional) 91 | //finalColor = finalColor / (1.0 + finalColor); 92 | o_color = finalColor; 93 | } -------------------------------------------------------------------------------- /example/shaders/ShadeDeferredSimple.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(binding = 0) uniform sampler2D s_gAlbedo; 4 | layout(binding = 1) uniform sampler2D s_gNormal; 5 | layout(binding = 2) uniform sampler2D s_gDepth; 6 | layout(binding = 3) uniform sampler2DShadow s_shadowDepth; 7 | //layout(binding = 3) uniform sampler2D s_exponentialShadowDepth; 8 | 9 | layout(location = 0) in vec2 v_uv; 10 | 11 | layout(location = 0) out vec3 o_color; 12 | 13 | layout(binding = 0, std140) uniform GlobalUniforms 14 | { 15 | mat4 viewProj; 16 | mat4 invViewProj; 17 | vec4 cameraPos; 18 | }; 19 | 20 | layout(binding = 1, std140) uniform ShadingUniforms 21 | { 22 | mat4 sunViewProj; 23 | vec4 sunDir; 24 | vec4 sunStrength; 25 | }shadingUniforms; 26 | 27 | // layout(binding = 2, std140) uniform ESM_UNIFORMS 28 | // { 29 | // float depthExponent; 30 | // }esmUniforms; 31 | 32 | struct Light 33 | { 34 | vec4 position; 35 | vec3 intensity; 36 | float invRadius; 37 | }; 38 | 39 | layout(binding = 0, std430) readonly buffer LightBuffer 40 | { 41 | Light lights[]; 42 | }lightBuffer; 43 | 44 | vec3 UnprojectUV(float depth, vec2 uv, mat4 invXProj) 45 | { 46 | vec4 ndc = vec4(uv * 2.0 - 1.0, depth, 1.0); 47 | vec4 world = invXProj * ndc; 48 | return world.xyz / world.w; 49 | } 50 | 51 | float Shadow(vec4 clip) 52 | { 53 | // with ZO projections, Z is already in [0, 1] 54 | clip.xy = clip.xy * .5 + .5; 55 | return textureProjLod(s_shadowDepth, clip, 0); 56 | } 57 | 58 | // float ShadowESM(vec4 clip) 59 | // { 60 | // vec4 unorm = clip; 61 | // unorm.xy = unorm.xy * .5 + .5; 62 | // float lightDepth = textureLod(s_exponentialShadowDepth, unorm.xy, 0.0).x; 63 | // float eyeDepth = unorm.z; 64 | // return clamp(lightDepth * exp(-esmUniforms.depthExponent * eyeDepth), 0.0, 1.0); 65 | // } 66 | 67 | float GetSquareFalloffAttenuation(vec3 posToLight, float lightInvRadius) 68 | { 69 | float distanceSquared = dot(posToLight, posToLight); 70 | float factor = distanceSquared * lightInvRadius * lightInvRadius; 71 | float smoothFactor = max(1.0 - factor * factor, 0.0); 72 | return (smoothFactor * smoothFactor) / max(distanceSquared, 1e-4); 73 | } 74 | 75 | vec3 LocalLightIntensity(vec3 fragWorldPos, vec3 N, vec3 V, vec3 albedo) 76 | { 77 | vec3 color = { 0, 0, 0 }; 78 | 79 | for (int i = 0; i < lightBuffer.lights.length(); i++) 80 | { 81 | Light light = lightBuffer.lights[i]; 82 | vec3 L = normalize(light.position.xyz - fragWorldPos); 83 | float NoL = max(dot(N, L), 0.0); 84 | vec3 diffuse = albedo * NoL * light.intensity; 85 | 86 | vec3 H = normalize(V + L); 87 | float spec = pow(max(dot(N, H), 0.0), 64.0); 88 | vec3 specular = albedo * spec * light.intensity; 89 | 90 | vec3 localColor = diffuse + specular; 91 | localColor *= GetSquareFalloffAttenuation(light.position.xyz - fragWorldPos, light.invRadius); 92 | 93 | color += localColor; 94 | } 95 | 96 | return color; 97 | } 98 | 99 | void main() 100 | { 101 | vec3 albedo = textureLod(s_gAlbedo, v_uv, 0.0).rgb; 102 | vec3 normal = textureLod(s_gNormal, v_uv, 0.0).xyz; 103 | float depth = textureLod(s_gDepth, v_uv, 0.0).x; 104 | 105 | if (depth == 0.0) 106 | { 107 | discard; 108 | } 109 | 110 | vec3 fragWorldPos = UnprojectUV(depth, v_uv, invViewProj); 111 | 112 | vec3 incidentDir = -shadingUniforms.sunDir.xyz; 113 | float cosTheta = max(0.0, dot(incidentDir, normal)); 114 | vec3 diffuse = albedo * cosTheta * shadingUniforms.sunStrength.rgb; 115 | float shadow = Shadow(shadingUniforms.sunViewProj * vec4(fragWorldPos, 1.0)); 116 | 117 | vec3 viewDir = normalize(cameraPos.xyz - fragWorldPos); 118 | vec3 halfDir = normalize(viewDir + incidentDir); 119 | float spec = pow(max(dot(normal, halfDir), 0.0), 64.0); 120 | vec3 specular = albedo * spec * shadingUniforms.sunStrength.rgb; 121 | 122 | vec3 ambient = vec3(.1) * albedo; 123 | vec3 finalColor = shadow * (diffuse + specular) + ambient; 124 | 125 | finalColor += LocalLightIntensity(fragWorldPos, normal, viewDir, albedo); 126 | 127 | // tone mapping (optional) 128 | //finalColor = finalColor / (1.0 + finalColor); 129 | o_color = finalColor; 130 | } -------------------------------------------------------------------------------- /example/shaders/Texture.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(location = 0) in vec2 v_uv; 4 | 5 | layout(location = 0) uniform sampler2D s_texture; 6 | 7 | layout(location = 0) out vec4 o_color; 8 | 9 | void main() 10 | { 11 | o_color = texture(s_texture, v_uv); 12 | } -------------------------------------------------------------------------------- /example/shaders/TonemapAndDither.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | 3 | layout(binding = 0) uniform sampler2D s_sceneColor; 4 | layout(binding = 1) uniform sampler2D s_noise; 5 | 6 | layout(location = 0) in vec2 v_uv; 7 | layout(location = 0) out vec4 o_finalColor; 8 | 9 | // AgX implementation from here: https://www.shadertoy.com/view/Dt3XDr 10 | vec3 xyYToXYZ(vec3 xyY) 11 | { 12 | float Y = xyY.z; 13 | float X = (xyY.x * Y) / xyY.y; 14 | float Z = ((1.0f - xyY.x - xyY.y) * Y) / xyY.y; 15 | 16 | return vec3(X, Y, Z); 17 | } 18 | 19 | vec3 Unproject(vec2 xy) 20 | { 21 | return xyYToXYZ(vec3(xy.x, xy.y, 1)); 22 | } 23 | 24 | mat3 PrimariesToMatrix(vec2 xy_red, vec2 xy_green, vec2 xy_blue, vec2 xy_white) 25 | { 26 | vec3 XYZ_red = Unproject(xy_red); 27 | vec3 XYZ_green = Unproject(xy_green); 28 | vec3 XYZ_blue = Unproject(xy_blue); 29 | vec3 XYZ_white = Unproject(xy_white); 30 | 31 | mat3 temp = mat3(XYZ_red.x, 1.0f, XYZ_red.z, 32 | XYZ_green.x, 1.0f, XYZ_green.z, 33 | XYZ_blue.x, 1.0f, XYZ_blue.z); 34 | vec3 scale = inverse(temp) * XYZ_white; 35 | 36 | return mat3(XYZ_red * scale.x, XYZ_green * scale.y, XYZ_blue * scale.z); 37 | } 38 | 39 | mat3 ComputeCompressionMatrix(vec2 xyR, vec2 xyG, vec2 xyB, vec2 xyW, float compression) 40 | { 41 | float scale_factor = 1.0f / (1.0f - compression); 42 | vec2 R = mix(xyW, xyR, scale_factor); 43 | vec2 G = mix(xyW, xyG, scale_factor); 44 | vec2 B = mix(xyW, xyB, scale_factor); 45 | vec2 W = xyW; 46 | 47 | return PrimariesToMatrix(R, G, B, W); 48 | } 49 | 50 | float DualSection(float x, float linear, float peak) 51 | { 52 | // Length of linear section 53 | float S = (peak * linear); 54 | if (x < S) { 55 | return x; 56 | } else { 57 | float C = peak / (peak - S); 58 | return peak - (peak - S) * exp((-C * (x - S)) / peak); 59 | } 60 | } 61 | 62 | vec3 DualSection(vec3 x, float linear, float peak) 63 | { 64 | x.x = DualSection(x.x, linear, peak); 65 | x.y = DualSection(x.y, linear, peak); 66 | x.z = DualSection(x.z, linear, peak); 67 | return x; 68 | } 69 | 70 | vec3 AgX_DS(vec3 color_srgb, float exposure, float saturation, float linear, float peak, float compression) 71 | { 72 | vec3 workingColor = max(color_srgb, 0.0f) * pow(2.0, exposure); 73 | 74 | mat3 sRGB_to_XYZ = PrimariesToMatrix(vec2(0.64,0.33), 75 | vec2(0.3,0.6), 76 | vec2(0.15,0.06), 77 | vec2(0.3127, 0.3290)); 78 | mat3 adjusted_to_XYZ = ComputeCompressionMatrix(vec2(0.64,0.33), 79 | vec2(0.3,0.6), 80 | vec2(0.15,0.06), 81 | vec2(0.3127, 0.3290), compression); 82 | mat3 XYZ_to_adjusted = inverse(adjusted_to_XYZ); 83 | mat3 sRGB_to_adjusted = sRGB_to_XYZ * XYZ_to_adjusted; 84 | 85 | workingColor = sRGB_to_adjusted * workingColor; 86 | workingColor = clamp(DualSection(workingColor, linear, peak), 0.0, 1.0); 87 | 88 | vec3 luminanceWeight = vec3(0.2126729, 0.7151522, 0.0721750); 89 | vec3 desaturation = vec3(dot(workingColor, luminanceWeight)); 90 | workingColor = mix(desaturation, workingColor, saturation); 91 | workingColor = clamp(workingColor, 0.f, 1.f); 92 | 93 | workingColor = inverse(sRGB_to_adjusted) * workingColor; 94 | 95 | return workingColor; 96 | } 97 | 98 | // ACES fitted 99 | // from https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl 100 | 101 | const mat3 ACESInputMat = mat3( 102 | 0.59719, 0.35458, 0.04823, 103 | 0.07600, 0.90834, 0.01566, 104 | 0.02840, 0.13383, 0.83777 105 | ); 106 | 107 | // ODT_SAT => XYZ => D60_2_D65 => sRGB 108 | const mat3 ACESOutputMat = mat3( 109 | 1.60475, -0.53108, -0.07367, 110 | -0.10208, 1.10813, -0.00605, 111 | -0.00327, -0.07276, 1.07602 112 | ); 113 | 114 | vec3 RRTAndODTFit(vec3 v) 115 | { 116 | vec3 a = v * (v + 0.0245786) - 0.000090537; 117 | vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081; 118 | return a / b; 119 | } 120 | 121 | vec3 ACESFitted(vec3 color) 122 | { 123 | color = color * ACESInputMat; 124 | 125 | // Apply RRT and ODT 126 | color = RRTAndODTFit(color); 127 | 128 | color = color * ACESOutputMat; 129 | 130 | // Clamp to [0, 1] 131 | color = clamp(color, 0.0, 1.0); 132 | 133 | return color; 134 | } 135 | 136 | // sRGB OETF 137 | vec3 linear_to_nonlinear_srgb(vec3 linearColor) 138 | { 139 | bvec3 cutoff = lessThan(linearColor, vec3(0.0031308)); 140 | vec3 higher = vec3(1.055) * pow(linearColor, vec3(1.0 / 2.4)) - vec3(0.055); 141 | vec3 lower = linearColor * vec3(12.92); 142 | 143 | return mix(higher, lower, cutoff); 144 | } 145 | 146 | vec3 apply_dither(vec3 color, vec2 uv) 147 | { 148 | vec2 uvNoise = uv * (vec2(textureSize(s_sceneColor, 0)) / vec2(textureSize(s_noise, 0))); 149 | vec3 noiseSample = textureLod(s_noise, uvNoise, 0).rgb; 150 | return color + vec3((noiseSample - 0.5) / 255.0); 151 | } 152 | 153 | void main() 154 | { 155 | vec3 hdrColor = textureLod(s_sceneColor, v_uv, 0).rgb; 156 | //vec3 ldrColor = ACESFitted(hdrColor); 157 | vec3 ldrColor = AgX_DS(hdrColor, -3, 1.0, 0.18, 1, 0.15); 158 | vec3 srgbColor = linear_to_nonlinear_srgb(ldrColor); 159 | vec3 ditheredColor = apply_dither(srgbColor, v_uv); 160 | 161 | o_finalColor = vec4(ditheredColor, 1.0); 162 | } -------------------------------------------------------------------------------- /example/shaders/clustered/CompactVisibleClusters.comp.glsl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Fwog/ca0317d3cf0f8b15656ed7e6508df4e2e4ac27fe/example/shaders/clustered/CompactVisibleClusters.comp.glsl -------------------------------------------------------------------------------- /example/shaders/clustered/CullLights.comp.glsl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Fwog/ca0317d3cf0f8b15656ed7e6508df4e2e4ac27fe/example/shaders/clustered/CullLights.comp.glsl -------------------------------------------------------------------------------- /example/shaders/clustered/MarkVisibleClusters.comp.glsl: -------------------------------------------------------------------------------- 1 | layout(local_size_x = 64) in; 2 | 3 | void main() 4 | { 5 | 6 | } -------------------------------------------------------------------------------- /example/shaders/gpu_driven/BoundingBox.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #extension GL_GOOGLE_include_directive : enable 3 | 4 | #include "Common.h" 5 | 6 | layout(location = 0) out uint v_drawID; 7 | 8 | // 14-vertex CCW triangle strip 9 | vec3 CreateCube(in uint vertexID) 10 | { 11 | uint b = 1 << vertexID; 12 | return vec3((0x287a & b) != 0, (0x02af & b) != 0, (0x31e3 & b) != 0); 13 | } 14 | 15 | void main() 16 | { 17 | uint i = objectIndices.array[gl_BaseInstance + gl_InstanceID]; 18 | v_drawID = gl_BaseInstance + gl_InstanceID; 19 | vec3 a_pos = CreateCube(gl_VertexID) - .5; // gl_VertexIndex for Vulkan 20 | ObjectUniforms obj = objects[i]; 21 | a_pos *= boundingBoxes[i].halfExtent * 2.0 + 1e-1; 22 | a_pos += boundingBoxes[i].offset; 23 | vec3 position = (obj.model * vec4(a_pos, 1.0)).xyz; 24 | gl_Position = globalUniforms.viewProj * vec4(position, 1.0); 25 | } -------------------------------------------------------------------------------- /example/shaders/gpu_driven/Common.h: -------------------------------------------------------------------------------- 1 | #ifndef GPU_COMMON_H 2 | #define GPU_COMMON_H 3 | 4 | #define HAS_BASE_COLOR_TEXTURE (1 << 0) 5 | 6 | struct ObjectUniforms 7 | { 8 | mat4 model; 9 | uint materialIdx; 10 | }; 11 | 12 | struct Material 13 | { 14 | uint flags; 15 | float alphaCutoff; 16 | uvec2 baseColorTextureHandle; 17 | vec4 baseColorFactor; 18 | }; 19 | 20 | struct BoundingBox 21 | { 22 | vec3 offset; 23 | vec3 halfExtent; 24 | }; 25 | 26 | struct DrawIndexedIndirectCommand 27 | { 28 | uint indexCount; 29 | uint instanceCount; 30 | uint firstIndex; 31 | int vertexOffset; 32 | uint firstInstance; 33 | }; 34 | 35 | layout(binding = 0, std140) uniform GlobalUniforms 36 | { 37 | mat4 viewProj; 38 | mat4 invViewProj; 39 | vec4 cameraPos; 40 | }globalUniforms; 41 | 42 | // Uniforms for each object. 43 | layout(binding = 0, std430) readonly restrict buffer ObjectUniformsBuffer 44 | { 45 | ObjectUniforms objects[]; 46 | }; 47 | 48 | // List of materials. Indexed with object.materialIdx 49 | layout(binding = 1, std430) readonly restrict buffer MaterialUniforms 50 | { 51 | Material materials[]; 52 | }; 53 | 54 | // One bounding box for every object. 55 | layout(binding = 2, std430) readonly restrict buffer BoundingBoxesBuffer 56 | { 57 | BoundingBox boundingBoxes[]; 58 | }; 59 | 60 | // The indices of objects that were not culled in the initial frustum culling pass. 61 | // They should be used to index 'objects' and 'boundingBoxes' 62 | layout(binding = 3, std430) readonly restrict buffer ObjectIndicesBuffer 63 | { 64 | uint count; 65 | uint array[]; 66 | }objectIndices; 67 | 68 | // The draw commands generated by the frustum culling pass. 69 | layout(binding = 4, std430) writeonly restrict buffer DrawCommandsBuffer 70 | { 71 | DrawIndexedIndirectCommand drawCommands[]; 72 | }; 73 | 74 | #endif // GPU_COMMON_H -------------------------------------------------------------------------------- /example/shaders/gpu_driven/CullVisibility.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #extension GL_GOOGLE_include_directive : enable 3 | 4 | #include "Common.h" 5 | 6 | layout(location = 0) in flat uint v_drawID; 7 | 8 | layout (early_fragment_tests) in; 9 | void main() 10 | { 11 | drawCommands[v_drawID].instanceCount = 1; 12 | } -------------------------------------------------------------------------------- /example/shaders/gpu_driven/SceneForward.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #extension GL_ARB_bindless_texture : require 3 | #extension GL_GOOGLE_include_directive : enable 4 | 5 | #include "Common.h" 6 | 7 | layout(location = 0) in vec3 v_position; 8 | layout(location = 1) in vec3 v_normal; 9 | layout(location = 2) in vec2 v_uv; 10 | layout(location = 3) in flat uint v_materialIdx; 11 | 12 | layout(location = 0) out vec4 o_color; 13 | 14 | void main() 15 | { 16 | Material material = materials[v_materialIdx]; 17 | 18 | vec4 color = material.baseColorFactor.rgba; 19 | if ((material.flags & HAS_BASE_COLOR_TEXTURE) != 0) 20 | { 21 | sampler2D samp = sampler2D(material.baseColorTextureHandle); 22 | color *= texture(samp, v_uv).rgba; 23 | } 24 | 25 | if (color.a < material.alphaCutoff) 26 | { 27 | discard; 28 | } 29 | 30 | vec3 albedo = color.rgb; 31 | vec3 normal = normalize(v_normal); 32 | 33 | vec3 viewDir = normalize(globalUniforms.cameraPos.xyz - v_position); 34 | float VoN = max(0.0, dot(viewDir, normal)); 35 | vec3 diffuse = albedo * VoN; 36 | 37 | vec3 ambient = vec3(.1) * albedo; 38 | vec3 finalColor = diffuse + ambient; 39 | 40 | o_color = vec4(finalColor, 1.0); 41 | } -------------------------------------------------------------------------------- /example/shaders/gpu_driven/SceneForward.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #extension GL_GOOGLE_include_directive : enable 3 | 4 | #include "Common.h" 5 | 6 | layout(location = 0) in vec3 a_pos; 7 | layout(location = 1) in vec2 a_normal; 8 | layout(location = 2) in vec2 a_uv; 9 | 10 | layout(location = 0) out vec3 v_position; 11 | layout(location = 1) out vec3 v_normal; 12 | layout(location = 2) out vec2 v_uv; 13 | layout(location = 3) out uint v_materialIdx; 14 | 15 | vec2 signNotZero(vec2 v) 16 | { 17 | return vec2((v.x >= 0.0) ? +1.0 : -1.0, (v.y >= 0.0) ? +1.0 : -1.0); 18 | } 19 | 20 | vec3 oct_to_float32x3(vec2 e) 21 | { 22 | vec3 v = vec3(e.xy, 1.0 - abs(e.x) - abs(e.y)); 23 | if (v.z < 0) v.xy = (1.0 - abs(v.yx)) * signNotZero(v.xy); 24 | return normalize(v); 25 | } 26 | 27 | void main() 28 | { 29 | uint i = objectIndices.array[gl_DrawID]; 30 | v_materialIdx = objects[i].materialIdx; 31 | v_position = (objects[i].model * vec4(a_pos, 1.0)).xyz; 32 | v_normal = normalize(inverse(transpose(mat3(objects[i].model))) * oct_to_float32x3(a_normal)); 33 | v_uv = a_uv; 34 | gl_Position = globalUniforms.viewProj * vec4(v_position, 1.0); 35 | } -------------------------------------------------------------------------------- /example/shaders/gpu_driven/SolidColor.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(location = 0) out vec4 o_color; 4 | 5 | void main() 6 | { 7 | o_color = vec4(1.0); 8 | } -------------------------------------------------------------------------------- /example/shaders/rsm/Bilateral.h.glsl: -------------------------------------------------------------------------------- 1 | #include "Kernels.h.glsl" 2 | #include "Common.h.glsl" 3 | 4 | // Input 5 | layout(binding = 0) uniform sampler2D s_illuminance; 6 | layout(binding = 1) uniform sampler2D s_gBufferNormal; 7 | layout(binding = 2) uniform sampler2D s_gBufferDepth; 8 | layout(binding = 3) uniform usampler2D s_historyLength; 9 | 10 | layout(binding = 0, std140) uniform FilterUniforms 11 | { 12 | mat4 proj; 13 | mat4 invViewProj; 14 | vec3 viewPos; 15 | float stepWidth; 16 | ivec2 targetDim; 17 | ivec2 direction; // either (1, 0) or (0, 1) 18 | float phiNormal; 19 | float phiDepth; 20 | }uniforms; 21 | 22 | // Output 23 | layout(binding = 0) uniform writeonly restrict image2D i_filteredIlluminance; 24 | 25 | void AddFilterContribution(inout vec3 accumIlluminance, 26 | inout float accumWeight, 27 | vec3 cColor, 28 | vec3 cNormal, 29 | float cDepth, 30 | vec3 rayDir, 31 | ivec2 baseOffset, 32 | ivec2 offset, 33 | ivec2 kernelStep, 34 | float kernelWeight, 35 | ivec2 id, 36 | ivec2 gid) 37 | { 38 | vec3 oColor = texelFetch(s_illuminance, id, 0).rgb; 39 | vec3 oNormal = texelFetch(s_gBufferNormal, id, 0).xyz; 40 | float oDepth = texelFetch(s_gBufferDepth, id, 0).x; 41 | float phiDepth = offset == ivec2(0) ? 1.0 : length(vec2(baseOffset)); 42 | phiDepth *= uniforms.phiDepth; 43 | 44 | float normalWeight = NormalWeight(oNormal, cNormal, uniforms.phiNormal); 45 | float depthWeight = DepthWeight(oDepth, cDepth, cNormal, rayDir, uniforms.proj, phiDepth); 46 | 47 | float weight = normalWeight * depthWeight; 48 | accumIlluminance += oColor * weight * kernelWeight; 49 | accumWeight += weight * kernelWeight; 50 | } 51 | 52 | layout(local_size_x = 8, local_size_y = 8) in; 53 | void main() 54 | { 55 | ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 56 | if (any(greaterThanEqual(gid, uniforms.targetDim))) 57 | { 58 | return; 59 | } 60 | 61 | vec3 cColor = texelFetch(s_illuminance, gid, 0).rgb; 62 | vec3 cNormal = texelFetch(s_gBufferNormal, gid, 0).xyz; 63 | float cDepth = texelFetch(s_gBufferDepth, gid, 0).x; 64 | 65 | vec2 uv = (vec2(gid) + 0.5) / uniforms.targetDim; 66 | vec3 point = UnprojectUV(0.1, uv, uniforms.invViewProj); 67 | vec3 rayDir = normalize(point - uniforms.viewPos); 68 | 69 | vec3 accumIlluminance = vec3(0); 70 | float accumWeight = 0; 71 | 72 | // Increase the blur width on recently disoccluded areas 73 | uint historyLength = 1 + texelFetch(s_historyLength, gid, 0).x; 74 | ivec2 stepFactor = ivec2(max(1.0, 4.0 / historyLength)); 75 | 76 | if (uniforms.direction == ivec2(0)) 77 | { 78 | for (int col = 0; col < kWidth; col++) 79 | { 80 | for (int row = 0; row < kWidth; row++) 81 | { 82 | ivec2 kernelStep = stepFactor * ivec2(uniforms.stepWidth); 83 | ivec2 baseOffset = ivec2(row - kRadius, col - kRadius); 84 | ivec2 offset = baseOffset * kernelStep; 85 | ivec2 id = gid + offset; 86 | 87 | if (any(greaterThanEqual(id, uniforms.targetDim)) || any(lessThan(id, ivec2(0)))) 88 | { 89 | continue; 90 | } 91 | 92 | float kernelWeight = kernel[row][col]; 93 | AddFilterContribution(accumIlluminance, 94 | accumWeight, 95 | cColor, 96 | cNormal, 97 | cDepth, 98 | rayDir, 99 | baseOffset, 100 | offset, 101 | kernelStep, 102 | kernelWeight, 103 | id, 104 | gid); 105 | } 106 | } 107 | } 108 | else 109 | { 110 | // Separable bilateral filter. Cheaper, but worse quality on edges 111 | for (int i = 0; i < kWidth; i++) 112 | { 113 | ivec2 kernelStep = stepFactor * ivec2(uniforms.stepWidth); 114 | ivec2 baseOffset = ivec2(i - kRadius) * uniforms.direction; 115 | ivec2 offset = baseOffset * kernelStep; 116 | ivec2 id = gid + offset; 117 | 118 | if (any(greaterThanEqual(id, uniforms.targetDim)) || any(lessThan(id, ivec2(0)))) 119 | { 120 | continue; 121 | } 122 | 123 | float kernelWeight = kernel1D[i]; 124 | AddFilterContribution(accumIlluminance, 125 | accumWeight, 126 | cColor, 127 | cNormal, 128 | cDepth, 129 | rayDir, 130 | baseOffset, 131 | offset, 132 | kernelStep, 133 | kernelWeight, 134 | id, 135 | gid); 136 | } 137 | } 138 | 139 | imageStore(i_filteredIlluminance, gid, vec4(accumIlluminance / accumWeight, 0.0)); 140 | } -------------------------------------------------------------------------------- /example/shaders/rsm/Bilateral5x5.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #extension GL_GOOGLE_include_directive : enable 3 | 4 | #define KERNEL_5x5 5 | #include "Bilateral.h.glsl" 6 | -------------------------------------------------------------------------------- /example/shaders/rsm/BlitTexture.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | 3 | layout(binding = 0) uniform sampler2D s_in; 4 | 5 | layout(binding = 0) uniform writeonly image2D i_out; 6 | 7 | layout(local_size_x = 8, local_size_y = 8) in; 8 | void main() 9 | { 10 | ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 11 | vec2 uv = (ivec2(gid) + 0.5) / imageSize(i_out); 12 | imageStore(i_out, gid, textureLod(s_in, uv, 0)); 13 | } -------------------------------------------------------------------------------- /example/shaders/rsm/Common.h.glsl: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_H 2 | #define COMMON_H 3 | 4 | vec3 UnprojectUV(float depth, vec2 uv, mat4 invXProj) 5 | { 6 | float z = depth * 2.0 - 1.0; // OpenGL Z convention 7 | vec4 ndc = vec4(uv * 2.0 - 1.0, z, 1.0); 8 | vec4 world = invXProj * ndc; 9 | return world.xyz / world.w; 10 | } 11 | 12 | float GetViewDepth(float depth, mat4 proj) 13 | { 14 | // Returns linear depth in [near, far] 15 | return proj[3][2] / (proj[2][2] + (depth * 2.0 - 1.0)); 16 | } 17 | 18 | float DepthWeight(float depthPrev, float depthCur, vec3 normalCur, vec3 viewDir, mat4 proj, float phi) 19 | { 20 | float linearDepthPrev = GetViewDepth(depthPrev, proj); 21 | 22 | float linearDepth = GetViewDepth(depthCur, proj); 23 | 24 | float angleFactor = max(0.25, -dot(normalCur, viewDir)); 25 | 26 | float diff = abs(linearDepthPrev - linearDepth); 27 | return exp(-diff * angleFactor / phi); 28 | } 29 | 30 | float NormalWeight(vec3 normalPrev, vec3 normalCur, float phi) 31 | { 32 | //float d = max(0, dot(normalCur, normalPrev)); 33 | //return d * d; 34 | vec3 dd = normalPrev - normalCur; 35 | float d = dot(dd, dd); 36 | return exp(-d / phi); 37 | } 38 | 39 | float LuminanceWeight(float luminanceOther, float luminanceCenter, float variance, float phi) 40 | { 41 | variance = max(variance, 0.0); 42 | float num = abs(luminanceOther - luminanceCenter); 43 | float den = phi * sqrt(variance) + 0.01; 44 | return exp(-num / den); 45 | } 46 | 47 | float Luminance(vec3 c) 48 | { 49 | return dot(c, vec3(0.213, 0.715, 0.072)); 50 | } 51 | 52 | vec3 Bilerp(vec3 _00, vec3 _01, vec3 _10, vec3 _11, vec2 weight) 53 | { 54 | vec3 bottom = mix(_00, _10, weight.x); 55 | vec3 top = mix(_01, _11, weight.x); 56 | return mix(bottom, top, weight.y); 57 | } 58 | 59 | vec2 Bilerp(vec2 _00, vec2 _01, vec2 _10, vec2 _11, vec2 weight) 60 | { 61 | vec2 bottom = mix(_00, _10, weight.x); 62 | vec2 top = mix(_01, _11, weight.x); 63 | return mix(bottom, top, weight.y); 64 | } 65 | 66 | float Bilerp(float _00, float _01, float _10, float _11, vec2 weight) 67 | { 68 | float bottom = mix(_00, _10, weight.x); 69 | float top = mix(_01, _11, weight.x); 70 | return mix(bottom, top, weight.y); 71 | } 72 | 73 | #endif // COMMON_H -------------------------------------------------------------------------------- /example/shaders/rsm/Indirect.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | #define PI 3.14159265 4 | #define TWO_PI (2.0 * PI) 5 | 6 | layout(binding = 0) uniform sampler2D s_inIndirect; 7 | layout(binding = 1) uniform sampler2D s_gAlbedo; 8 | layout(binding = 2) uniform sampler2D s_gNormal; 9 | layout(binding = 3) uniform sampler2D s_gDepth; 10 | layout(binding = 4) uniform sampler2D s_rsmFlux; 11 | layout(binding = 5) uniform sampler2D s_rsmNormal; 12 | layout(binding = 6) uniform sampler2D s_rsmDepth; 13 | 14 | layout(binding = 0) uniform writeonly image2D i_outIndirect; 15 | 16 | layout(binding = 0, std140) uniform GlobalUniforms 17 | { 18 | mat4 viewProj; 19 | mat4 invViewProj; 20 | mat4 proj; 21 | vec4 cameraPos; 22 | }; 23 | 24 | layout(binding = 1, std140) uniform RSMUniforms 25 | { 26 | mat4 sunViewProj; 27 | mat4 invSunViewProj; 28 | ivec2 targetDim; // input and output texture dimensions 29 | float rMax; // max radius for which indirect lighting will be considered 30 | uint currentPass; // used to determine which pixels to shade 31 | uint samples; 32 | } rsm; 33 | 34 | vec3 UnprojectUV(float depth, vec2 uv, mat4 invXProj) 35 | { 36 | float z = depth * 2.0 - 1.0; // OpenGL Z convention 37 | vec4 ndc = vec4(uv * 2.0 - 1.0, z, 1.0); 38 | vec4 world = invXProj * ndc; 39 | return world.xyz / world.w; 40 | } 41 | 42 | vec2 Hammersley(uint i, uint N) 43 | { 44 | return vec2(float(i) / float(N), float(bitfieldReverse(i)) * 2.3283064365386963e-10); 45 | } 46 | 47 | vec3 ComputePixelLight(vec3 surfaceWorldPos, vec3 surfaceNormal, vec3 rsmFlux, vec3 rsmWorldPos, vec3 rsmNormal) 48 | { 49 | // move rsmPos in negative of normal by small constant amount(?) 50 | // rsmWorldPos -= rsmNormal * .01; 51 | float geometry = max(0.0, dot(rsmNormal, surfaceWorldPos - rsmWorldPos)) * 52 | max(0.0, dot(surfaceNormal, rsmWorldPos - surfaceWorldPos)); 53 | 54 | // Clamp distance to prevent singularity. 55 | float d = max(distance(surfaceWorldPos, rsmWorldPos), 0.01); 56 | 57 | return rsmFlux * geometry / (d * d * d * d); 58 | } 59 | 60 | vec3 ComputeIndirectIrradiance(vec3 surfaceAlbedo, vec3 surfaceNormal, vec3 surfaceWorldPos) 61 | { 62 | vec3 sumC = {0, 0, 0}; 63 | 64 | // Compute the position of this surface point projected into the RSM's UV space 65 | const vec4 rsmClip = rsm.sunViewProj * vec4(surfaceWorldPos, 1.0); 66 | const vec2 rsmUV = (rsmClip.xy / rsmClip.w) * .5 + .5; 67 | 68 | // Calculate the area of the world-space disk we are integrating over. 69 | float rMaxWorld = distance(UnprojectUV(0.0, rsmUV, rsm.invSunViewProj), 70 | UnprojectUV(0.0, vec2(rsmUV.x + rsm.rMax, rsmUV.y), rsm.invSunViewProj)); 71 | 72 | // Samples need to be normalized based on the radius that is sampled, otherwise changing rMax will affect the brightness. 73 | float normalizationFactor = 2.0 * rMaxWorld * rMaxWorld; 74 | 75 | for (int i = 0; i < rsm.samples; i++) 76 | { 77 | // Get two random numbers. 78 | vec2 xi = Hammersley(i, rsm.samples); 79 | float r = xi.x; 80 | float theta = xi.y * TWO_PI; 81 | vec2 pixelLightUV = rsmUV + vec2(r * cos(theta), r * sin(theta)) * rsm.rMax; 82 | float weight = xi.x; 83 | 84 | float rsmDepth = textureLod(s_rsmDepth, pixelLightUV, 0.0).x; 85 | if (rsmDepth == 1.0) continue; 86 | vec3 rsmFlux = textureLod(s_rsmFlux, pixelLightUV, 0.0).rgb; 87 | vec3 rsmNormal = textureLod(s_rsmNormal, pixelLightUV, 0.0).xyz; 88 | vec3 rsmWorldPos = UnprojectUV(rsmDepth, pixelLightUV, rsm.invSunViewProj); 89 | 90 | sumC += ComputePixelLight(surfaceWorldPos, surfaceNormal, rsmFlux, rsmWorldPos, rsmNormal) * weight; 91 | } 92 | 93 | return normalizationFactor * sumC * surfaceAlbedo / rsm.samples; 94 | } 95 | 96 | layout(local_size_x = 8, local_size_y = 8) in; 97 | void main() 98 | { 99 | ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 100 | 101 | gid *= 2; 102 | 103 | if (rsm.currentPass == 1) 104 | gid++; 105 | if (rsm.currentPass == 2) 106 | gid.x++; 107 | if (rsm.currentPass == 3) 108 | gid.y++; 109 | 110 | if (any(greaterThanEqual(gid, rsm.targetDim))) 111 | return; 112 | vec2 texel = 1.0 / rsm.targetDim; 113 | vec2 uv = (vec2(gid) + 0.5) / rsm.targetDim; 114 | 115 | vec3 albedo = texelFetch(s_gAlbedo, gid, 0).rgb; 116 | vec3 normal = texelFetch(s_gNormal, gid, 0).xyz; 117 | float depth = texelFetch(s_gDepth, gid, 0).x; 118 | vec3 worldPos = UnprojectUV(depth, uv, invViewProj); 119 | 120 | if (depth == 1.0) 121 | { 122 | imageStore(i_outIndirect, gid, vec4(0.0)); 123 | return; 124 | } 125 | 126 | vec3 ambient = vec3(0); 127 | 128 | if (rsm.currentPass == 0) 129 | { 130 | ambient = ComputeIndirectIrradiance(albedo, normal, worldPos); 131 | } 132 | else 133 | { 134 | // look at corners and identify if any two opposing ones can be interpolated 135 | ivec2 offsets[4]; 136 | if (rsm.currentPass == 1) 137 | { 138 | offsets = ivec2[4](ivec2(-1, 1), ivec2(1, 1), ivec2(-1, -1), ivec2(1, -1)); 139 | } 140 | else if (rsm.currentPass > 1) 141 | { 142 | offsets = ivec2[4](ivec2(0, 1), ivec2(0, -1), ivec2(-1, 0), ivec2(1, 0)); 143 | } 144 | 145 | // compute weights for each 146 | float accum_weight = 0; 147 | vec3 accum_color = vec3(0); 148 | for (int i = 0; i < 4; i++) 149 | { 150 | ivec2 corner = gid + offsets[i]; 151 | if (any(greaterThanEqual(corner, rsm.targetDim)) || any(lessThan(corner, ivec2(0)))) 152 | continue; 153 | 154 | vec3 c = texelFetch(s_inIndirect, corner, 0).rgb; 155 | 156 | vec3 n = texelFetch(s_gNormal, corner, 0).xyz; 157 | vec3 dn = normal - n; 158 | float n_weight = exp(-dot(dn, dn) / 1.0); 159 | 160 | vec3 p = UnprojectUV(texelFetch(s_gDepth, corner, 0).x, uv + vec2(offsets[i]) * texel, invViewProj); 161 | vec3 dp = worldPos - p; 162 | float p_weight = exp(-dot(dp, dp) / 0.4); 163 | 164 | float weight = n_weight * p_weight; 165 | accum_color += c * weight; 166 | accum_weight += weight; 167 | } 168 | 169 | ambient = accum_color / accum_weight; 170 | 171 | // if quality of neighbors is too low, instead compute indirect irradiance 172 | if (accum_weight <= 3.0) 173 | { 174 | ambient = ComputeIndirectIrradiance(albedo, normal, worldPos); 175 | // ambient = vec3(1, 0, 0); // debug 176 | } 177 | } 178 | 179 | imageStore(i_outIndirect, gid, vec4(ambient, 1.0)); 180 | } -------------------------------------------------------------------------------- /example/shaders/rsm/IndirectDitheredFiltered.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | #define PI 3.14159265 4 | #define TWO_PI (2.0 * PI) 5 | 6 | layout(binding = 0) uniform sampler2D s_inIndirect; 7 | layout(binding = 2) uniform sampler2D s_gNormal; 8 | layout(binding = 3) uniform sampler2D s_gDepth; 9 | layout(binding = 4) uniform sampler2D s_rsmFlux; 10 | layout(binding = 5) uniform sampler2D s_rsmNormal; 11 | layout(binding = 6) uniform sampler2D s_rsmDepth; 12 | layout(binding = 7) uniform sampler2D s_blueNoise; 13 | 14 | layout(binding = 0) uniform restrict writeonly image2D i_outIndirect; 15 | 16 | layout(binding = 0, std140) uniform GlobalUniforms 17 | { 18 | mat4 viewProj; 19 | mat4 invViewProj; 20 | mat4 proj; 21 | vec4 cameraPos; 22 | }; 23 | 24 | layout(binding = 1, std140) uniform RSMUniforms 25 | { 26 | mat4 sunViewProj; 27 | mat4 invSunViewProj; 28 | ivec2 targetDim; // input and output texture dimensions 29 | float rMax; // max radius for which indirect lighting will be considered 30 | uint currentPass; // used to determine which pixels to shade 31 | uint samples; 32 | vec2 random; 33 | } rsm; 34 | 35 | vec3 UnprojectUV(float depth, vec2 uv, mat4 invXProj) 36 | { 37 | float z = depth * 2.0 - 1.0; // OpenGL Z convention 38 | vec4 ndc = vec4(uv * 2.0 - 1.0, z, 1.0); 39 | vec4 world = invXProj * ndc; 40 | return world.xyz / world.w; 41 | } 42 | 43 | vec2 Hammersley(uint i, uint N) 44 | { 45 | return vec2(float(i) / float(N), float(bitfieldReverse(i)) * 2.3283064365386963e-10); 46 | } 47 | 48 | vec3 ComputePixelLight(vec3 surfaceWorldPos, vec3 surfaceNormal, vec3 rsmFlux, vec3 rsmWorldPos, vec3 rsmNormal) 49 | { 50 | // Move rsmPos in negative of normal by small constant amount(?). This is mentioned in the paper, 51 | // but does not seem useful. 52 | // rsmWorldPos -= rsmNormal * .01; 53 | float geometry = max(0.0, dot(rsmNormal, surfaceWorldPos - rsmWorldPos)) * 54 | max(0.0, dot(surfaceNormal, rsmWorldPos - surfaceWorldPos)); 55 | 56 | // Clamp distance to prevent singularity. 57 | float d = max(distance(surfaceWorldPos, rsmWorldPos), 0.03); 58 | 59 | // Inverse square attenuation. d^4 is due to us not normalizing the two ray directions in the numerator. 60 | return rsmFlux * geometry / (d * d * d * d); 61 | } 62 | 63 | vec3 ComputeIndirectIrradiance(vec3 surfaceNormal, vec3 surfaceWorldPos, vec2 noise) 64 | { 65 | vec3 sumC = {0, 0, 0}; 66 | 67 | const vec4 rsmClip = rsm.sunViewProj * vec4(surfaceWorldPos, 1.0); 68 | const vec2 rsmUV = (rsmClip.xy / rsmClip.w) * .5 + .5; 69 | 70 | float rMaxWorld = distance(UnprojectUV(0.0, rsmUV, rsm.invSunViewProj), 71 | UnprojectUV(0.0, vec2(rsmUV.x + rsm.rMax, rsmUV.y), rsm.invSunViewProj)); 72 | 73 | // Samples need to be normalized based on the radius that is sampled, otherwise changing rMax will affect the brightness. 74 | float normalizationFactor = 2.0 * rMaxWorld * rMaxWorld; 75 | 76 | for (int i = 0; i < rsm.samples; i++) 77 | { 78 | vec2 xi = Hammersley(i, rsm.samples); 79 | // xi can be randomly rotated based on screen position. The original paper does not use screen-space noise to 80 | // offset samples, but we do because it is important for the new filtering step. 81 | 82 | // Apply Cranley-Pattern rotation/toroidal shift with per-pixel noise 83 | xi = fract(xi + noise.xy); 84 | 85 | float r = xi.x; 86 | float theta = xi.y * TWO_PI; 87 | vec2 pixelLightUV = rsmUV + vec2(r * cos(theta), r * sin(theta)) * rsm.rMax; 88 | float weight = r; 89 | 90 | float rsmDepth = textureLod(s_rsmDepth, pixelLightUV, 0.0).x; 91 | if (rsmDepth == 1.0) continue; 92 | vec3 rsmFlux = textureLod(s_rsmFlux, pixelLightUV, 0.0).rgb; 93 | vec3 rsmNormal = textureLod(s_rsmNormal, pixelLightUV, 0.0).xyz; 94 | vec3 rsmWorldPos = UnprojectUV(rsmDepth, pixelLightUV, rsm.invSunViewProj); 95 | 96 | sumC += ComputePixelLight(surfaceWorldPos, surfaceNormal, rsmFlux, rsmWorldPos, rsmNormal) * weight; 97 | } 98 | 99 | return normalizationFactor * sumC / rsm.samples; 100 | } 101 | 102 | layout(local_size_x = 8, local_size_y = 8) in; 103 | void main() 104 | { 105 | ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 106 | 107 | if (any(greaterThanEqual(gid, rsm.targetDim))) 108 | { 109 | return; 110 | } 111 | 112 | vec2 texel = 1.0 / rsm.targetDim; 113 | vec2 uv = (vec2(gid) + 0.5) / rsm.targetDim; 114 | 115 | vec2 noise = rsm.random + textureLod(s_blueNoise, (vec2(gid) + 0.5) / textureSize(s_blueNoise, 0), 0).xy; 116 | 117 | vec3 normal = texelFetch(s_gNormal, gid, 0).xyz; 118 | float depth = texelFetch(s_gDepth, gid, 0).x; 119 | vec3 worldPos = UnprojectUV(depth, uv, invViewProj); 120 | 121 | if (depth == 1.0) 122 | { 123 | imageStore(i_outIndirect, gid, vec4(0.0)); 124 | return; 125 | } 126 | 127 | vec3 ambient = ComputeIndirectIrradiance(normal, worldPos, noise); 128 | 129 | imageStore(i_outIndirect, gid, vec4(ambient, 1.0)); 130 | } -------------------------------------------------------------------------------- /example/shaders/rsm/Kernels.h.glsl: -------------------------------------------------------------------------------- 1 | #ifndef KERNELS_H 2 | #define KERNELS_H 3 | 4 | #ifdef KERNEL_3x3 5 | const uint kRadius = 1; 6 | const uint kWidth = 1 + 2 * kRadius; 7 | const float kernel1D[kWidth] = {0.27901, 0.44198, 0.27901}; 8 | const float kernel[kWidth][kWidth] = { 9 | {kernel1D[0] * kernel1D[0], kernel1D[0] * kernel1D[1], kernel1D[0] * kernel1D[2]}, 10 | {kernel1D[1] * kernel1D[0], kernel1D[1] * kernel1D[1], kernel1D[1] * kernel1D[2]}, 11 | {kernel1D[2] * kernel1D[0], kernel1D[2] * kernel1D[1], kernel1D[2] * kernel1D[2]}, 12 | }; 13 | #elif defined(KERNEL_5x5) 14 | const uint kRadius = 2; 15 | const uint kWidth = 1 + 2 * kRadius; 16 | const float kernel1D[kWidth] = { 1. / 16, 1. / 4, 3. / 8, 1. / 4, 1. / 16 }; 17 | const float kernel[kWidth][kWidth] = 18 | { 19 | { kernel1D[0] * kernel1D[0], kernel1D[0] * kernel1D[1], kernel1D[0] * kernel1D[2], kernel1D[0] * kernel1D[3], kernel1D[0] * kernel1D[4] }, 20 | { kernel1D[1] * kernel1D[0], kernel1D[1] * kernel1D[1], kernel1D[1] * kernel1D[2], kernel1D[1] * kernel1D[3], kernel1D[1] * kernel1D[4] }, 21 | { kernel1D[2] * kernel1D[0], kernel1D[2] * kernel1D[1], kernel1D[2] * kernel1D[2], kernel1D[2] * kernel1D[3], kernel1D[2] * kernel1D[4] }, 22 | { kernel1D[3] * kernel1D[0], kernel1D[3] * kernel1D[1], kernel1D[3] * kernel1D[2], kernel1D[3] * kernel1D[3], kernel1D[3] * kernel1D[4] }, 23 | { kernel1D[4] * kernel1D[0], kernel1D[4] * kernel1D[1], kernel1D[4] * kernel1D[2], kernel1D[4] * kernel1D[3], kernel1D[4] * kernel1D[4] }, 24 | }; 25 | #elif defined(KERNEL_7x7) 26 | const uint kRadius = 3; 27 | const uint kWidth = 1 + 2 * kRadius; 28 | const float kernel1D[kWidth] = { 0.00598, 0.060626, 0.241843, 0.383103, 0.241843, 0.060626, 0.00598 }; 29 | #endif 30 | 31 | #endif // KERNELS_H -------------------------------------------------------------------------------- /example/shaders/rsm/Modulate.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | 3 | layout(binding = 0) uniform sampler2D s_illumination; 4 | layout(binding = 1) uniform sampler2D s_gAlbedo; 5 | 6 | layout(binding = 0) uniform restrict writeonly image2D i_outIndirect; 7 | 8 | layout(local_size_x = 8, local_size_y = 8) in; 9 | void main() 10 | { 11 | ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 12 | 13 | ivec2 targetDim = imageSize(i_outIndirect); 14 | if (any(greaterThanEqual(gid, targetDim))) 15 | { 16 | return; 17 | } 18 | 19 | vec2 uv = (vec2(gid) + 0.5) / targetDim; 20 | 21 | vec3 albedo = textureLod(s_gAlbedo, uv, 0).rgb; 22 | vec3 ambient = textureLod(s_illumination, uv, 0).rgb; 23 | 24 | imageStore(i_outIndirect, gid, vec4(ambient * albedo, 1.0)); 25 | } -------------------------------------------------------------------------------- /example/shaders/rsm/ModulateUpscale.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | #extension GL_GOOGLE_include_directive : enable 3 | 4 | #define KERNEL_3x3 5 | #include "Kernels.h.glsl" 6 | #include "Common.h.glsl" 7 | 8 | // Input 9 | layout(binding = 0) uniform sampler2D s_diffuseIrradiance; 10 | layout(binding = 1) uniform sampler2D s_gAlbedo; 11 | layout(binding = 2) uniform sampler2D s_gNormal; 12 | layout(binding = 3) uniform sampler2D s_gDepth; 13 | layout(binding = 4) uniform sampler2D s_gNormalSmall; 14 | layout(binding = 5) uniform sampler2D s_gDepthSmall; 15 | 16 | layout(binding = 0, std140) uniform FilterUniforms 17 | { 18 | mat4 proj; 19 | mat4 invViewProj; 20 | vec3 viewPos; 21 | float stepWidth; 22 | ivec2 targetDim; 23 | ivec2 direction; 24 | float phiNormal; 25 | float phiDepth; 26 | }uniforms; 27 | 28 | // Output 29 | layout(binding = 0) uniform writeonly restrict image2D i_modulatedIlluminance; 30 | 31 | layout(local_size_x = 8, local_size_y = 8) in; 32 | void main() 33 | { 34 | ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 35 | if (any(greaterThanEqual(gid, uniforms.targetDim))) 36 | { 37 | return; 38 | } 39 | 40 | ivec2 sourceDim = textureSize(s_diffuseIrradiance, 0); 41 | 42 | vec3 cNormal = texelFetch(s_gNormal, gid, 0).xyz; 43 | float cDepth = texelFetch(s_gDepth, gid, 0).x; 44 | 45 | vec2 uv = (vec2(gid) + 0.5) / uniforms.targetDim; 46 | vec3 point = UnprojectUV(0.1, uv, uniforms.invViewProj); 47 | vec3 rayDir = normalize(point - uniforms.viewPos); 48 | 49 | vec2 ratio = vec2(sourceDim) / uniforms.targetDim; 50 | ivec2 sourcePos = ivec2(gid * ratio); 51 | 52 | vec3 accumIlluminance = vec3(0); 53 | float accumWeight = 0; 54 | 55 | // Do a widdle 3x3 bilateral filter to find valid samples 56 | for (int col = 0; col < kWidth; col++) 57 | { 58 | for (int row = 0; row < kWidth; row++) 59 | { 60 | ivec2 offset = ivec2(row - kRadius, col - kRadius); 61 | ivec2 pos = sourcePos + offset; 62 | 63 | if (any(greaterThanEqual(pos, sourceDim)) || any(lessThan(pos, ivec2(0)))) 64 | { 65 | continue; 66 | } 67 | 68 | float kernelWeight = kernel[row][col]; 69 | 70 | vec3 oColor = texelFetch(s_diffuseIrradiance, pos, 0).rgb; 71 | vec3 oNormal = texelFetch(s_gNormalSmall, pos, 0).xyz; 72 | float oDepth = texelFetch(s_gDepthSmall, pos, 0).x; 73 | 74 | float normalWeight = NormalWeight(oNormal, cNormal, uniforms.phiNormal); 75 | float depthWeight = DepthWeight(oDepth, cDepth, cNormal, rayDir, uniforms.proj, uniforms.phiDepth); 76 | 77 | float weight = normalWeight * depthWeight; 78 | accumIlluminance += oColor * weight * kernelWeight; 79 | accumWeight += weight * kernelWeight; 80 | } 81 | } 82 | 83 | vec3 albedo = texelFetch(s_gAlbedo, gid, 0).rgb; 84 | if (accumWeight >= 0.0001) 85 | { 86 | imageStore(i_modulatedIlluminance, gid, vec4(albedo * accumIlluminance / accumWeight, 0.0)); 87 | } 88 | else 89 | { 90 | vec3 center = texelFetch(s_diffuseIrradiance, sourcePos, 0).rgb; 91 | imageStore(i_modulatedIlluminance, gid, vec4(albedo * center, 0.0)); 92 | } 93 | } -------------------------------------------------------------------------------- /example/shaders/rsm/Reproject.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #extension GL_GOOGLE_include_directive : enable 3 | 4 | #define DEBUG 0 5 | 6 | #define KERNEL_3x3 7 | #include "Kernels.h.glsl" 8 | #include "Common.h.glsl" 9 | 10 | layout(binding = 0) uniform sampler2D s_indirectCurrent; 11 | layout(binding = 1) uniform sampler2D s_indirectPrevious; 12 | layout(binding = 2) uniform sampler2D s_gDepth; 13 | layout(binding = 3) uniform sampler2D s_gDepthPrev; 14 | layout(binding = 4) uniform sampler2D s_gNormal; 15 | layout(binding = 5) uniform sampler2D s_gNormalPrev; 16 | layout(binding = 6) uniform sampler2D s_gMotion; 17 | 18 | layout(binding = 0) uniform restrict writeonly image2D i_outIndirect; 19 | layout(binding = 1, r8ui) uniform restrict uimage2D i_historyLength; 20 | 21 | layout(binding = 0, std140) uniform ReprojectionUniforms 22 | { 23 | mat4 invViewProjCurrent; 24 | mat4 viewProjPrevious; 25 | mat4 invViewProjPrevious; 26 | mat4 proj; 27 | vec3 viewPos; 28 | float temporalWeightFactor; 29 | ivec2 targetDim; 30 | float alphaIlluminance; 31 | float phiDepth; 32 | float phiNormal; 33 | vec2 jitterOffset; 34 | vec2 lastFrameJitterOffset; 35 | }uniforms; 36 | 37 | bool InBounds(ivec2 pos) 38 | { 39 | return all(lessThan(pos, uniforms.targetDim)) && all(greaterThanEqual(pos, ivec2(0))); 40 | } 41 | 42 | void Accumulate(vec3 prevColor, vec3 curColor, ivec2 gid) 43 | { 44 | uint historyLength = min(1 + imageLoad(i_historyLength, gid).x, 255); 45 | imageStore(i_historyLength, gid, uvec4(historyLength)); 46 | float alphaIlluminance = max(uniforms.alphaIlluminance, 1.0 / historyLength); 47 | 48 | vec3 outColor = mix(prevColor, curColor, alphaIlluminance); 49 | imageStore(i_outIndirect, gid, vec4(outColor, 0.0)); 50 | } 51 | 52 | layout(local_size_x = 8, local_size_y = 8) in; 53 | void main() 54 | { 55 | ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 56 | 57 | if (!InBounds(gid)) 58 | { 59 | return; 60 | } 61 | 62 | vec2 uv = (vec2(gid) + 0.5) / uniforms.targetDim; 63 | 64 | // Reproject this pixel 65 | float depthCur = texelFetch(s_gDepth, gid, 0).x; 66 | 67 | // NDC_curFrame -> world 68 | vec3 worldPosCur = UnprojectUV(depthCur, uv, uniforms.invViewProjCurrent); 69 | 70 | // Reprojection with old camera 71 | // // world -> NDC_prevFrame 72 | // vec4 clipPosPrev = uniforms.viewProjPrevious * vec4(worldPosCur, 1.0); 73 | // vec3 ndcPosPrev = clipPosPrev.xyz / clipPosPrev.w; 74 | // vec3 reprojectedUV = ndcPosPrev; 75 | // reprojectedUV.xy = ndcPosPrev.xy * .5 + .5; 76 | // // From OpenGL Z convention [-1, 1] -> [0, 1]. 77 | // // In other APIs (or with glClipControl(..., GL_ZERO_TO_ONE)) you would not do this. 78 | // reprojectedUV.z = ndcPosPrev.z * .5 + .5; 79 | 80 | // According to my math, this is how you account for jitter when reprojecting 81 | vec2 reprojectedUV = uv + textureLod(s_gMotion, uv, 0.0).xy - uniforms.jitterOffset + uniforms.lastFrameJitterOffset; 82 | 83 | //ivec2 centerPos = ivec2(reprojectedUV.xy * uniforms.targetDim); 84 | 85 | vec3 rayDir = normalize(worldPosCur - uniforms.viewPos); 86 | 87 | vec3 normalCur = texelFetch(s_gNormal, gid, 0).xyz; 88 | 89 | // Locate consistent samples to interpolate between in 2x2 area 90 | ivec2 bottomLeftPos = ivec2(reprojectedUV.xy * uniforms.targetDim - 0.5); 91 | vec3 colors[2][2] = vec3[2][2](vec3[2](vec3(0), vec3(0)), vec3[2](vec3(0), vec3(0))); 92 | float valid[2][2] = float[2][2](float[2](0, 0), float[2](0, 0)); 93 | int validCount = 0; 94 | for (int y = 0; y <= 1; y++) 95 | { 96 | for (int x = 0; x <= 1; x++) 97 | { 98 | ivec2 pos = bottomLeftPos + ivec2(x, y); 99 | if (!InBounds(pos)) 100 | { 101 | continue; 102 | } 103 | 104 | float depthPrev = texelFetch(s_gDepthPrev, pos, 0).x; 105 | if (DepthWeight(depthPrev, depthCur, normalCur, rayDir, uniforms.proj, uniforms.phiDepth) < 0.75) 106 | { 107 | continue; 108 | } 109 | 110 | vec3 normalPrev = texelFetch(s_gNormalPrev, pos, 0).xyz; 111 | if (NormalWeight(normalPrev, normalCur, uniforms.phiNormal) < 0.75) 112 | { 113 | continue; 114 | } 115 | 116 | validCount++; 117 | valid[x][y] = 1.0; 118 | colors[x][y] = texelFetch(s_indirectPrevious, pos, 0).rgb; 119 | } 120 | } 121 | 122 | vec2 weight = fract(reprojectedUV.xy * uniforms.targetDim - 0.5); 123 | vec3 curColor = texelFetch(s_indirectCurrent, gid, 0).rgb; 124 | float lum = Luminance(curColor); 125 | vec2 curMoments = { lum, lum * lum }; 126 | 127 | // Requiring at least 3 valid samples (instead of 1) is a hack to deal with a jitter bug (no pun intended) 128 | if (validCount > 2) 129 | { 130 | // Use weighted bilinear filter if any of its samples are valid 131 | float factor = max(0.01, Bilerp(valid[0][0], valid[0][1], valid[1][0], valid[1][1], weight)); 132 | vec3 prevColor = Bilerp(colors[0][0], colors[0][1], colors[1][0], colors[1][1], weight) / factor; 133 | 134 | Accumulate(prevColor, curColor, gid); 135 | } 136 | else 137 | { 138 | // Search for valid samples in a 3x3 area with a bilateral filter 139 | ivec2 centerPos = ivec2(reprojectedUV.xy * uniforms.targetDim); 140 | vec3 accumIlluminance = vec3(0); 141 | float accumWeight = 0; 142 | 143 | for (int col = 0; col < kWidth; col++) 144 | { 145 | for (int row = 0; row < kWidth; row++) 146 | { 147 | ivec2 offset = ivec2(row - kRadius, col - kRadius); 148 | ivec2 pos = centerPos + offset; 149 | 150 | if (!InBounds(pos)) 151 | { 152 | continue; 153 | } 154 | 155 | float kernelWeight = kernel[row][col]; 156 | 157 | vec3 oColor = texelFetch(s_indirectPrevious, pos, 0).rgb; 158 | vec3 oNormal = texelFetch(s_gNormalPrev, pos, 0).xyz; 159 | float oDepth = texelFetch(s_gDepthPrev, pos, 0).x; 160 | float phiDepth = offset == ivec2(0) ? 1.0 : length(vec2(offset)); 161 | phiDepth *= uniforms.phiDepth; 162 | 163 | float normalWeight = NormalWeight(oNormal, normalCur, uniforms.phiNormal); 164 | float depthWeight = DepthWeight(oDepth, depthCur, normalCur, rayDir, uniforms.proj, phiDepth); 165 | 166 | float weight = normalWeight * depthWeight; 167 | accumIlluminance += oColor * weight * kernelWeight; 168 | accumWeight += weight * kernelWeight; 169 | } 170 | } 171 | 172 | if (accumWeight >= 0.15) 173 | { 174 | // Consider bilateral filter a success if accumulated weight is above an arbitrary threshold 175 | vec3 prevColor = accumIlluminance / accumWeight; 176 | Accumulate(prevColor, curColor, gid); 177 | } 178 | else 179 | { 180 | // Disocclusion occurred 181 | imageStore(i_outIndirect, gid, vec4(curColor, 0.0)); 182 | imageStore(i_historyLength, gid, uvec4(0)); 183 | 184 | #if DEBUG 185 | imageStore(i_outIndirect, gid, vec4(1, 0, 0, 0)); 186 | return; 187 | #endif 188 | } 189 | } 190 | } -------------------------------------------------------------------------------- /example/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 | // Draw lines on froxel borders. 91 | // if (any(lessThan(fract(uv * vec2(textureSize(s_volume, 0).xy)), vec2(0.1)))) 92 | // { 93 | // finalColor = vec3(1); 94 | // } 95 | 96 | imageStore(i_target, gid, vec4(finalColor, 1.0)); 97 | } -------------------------------------------------------------------------------- /example/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 -------------------------------------------------------------------------------- /example/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 | } -------------------------------------------------------------------------------- /example/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 -------------------------------------------------------------------------------- /example/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 | } -------------------------------------------------------------------------------- /example/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 | } -------------------------------------------------------------------------------- /example/shaders/volumetric/TonemapAndDither.comp.glsl: -------------------------------------------------------------------------------- 1 | #version 450 core 2 | 3 | layout(binding = 0) uniform sampler2D s_sceneColor; 4 | layout(binding = 1) uniform sampler2D s_noise; 5 | 6 | layout(binding = 0) uniform writeonly restrict image2D i_finalColor; 7 | 8 | // ACES fitted 9 | // from https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl 10 | 11 | const mat3 ACESInputMat = mat3( 12 | 0.59719, 0.35458, 0.04823, 13 | 0.07600, 0.90834, 0.01566, 14 | 0.02840, 0.13383, 0.83777 15 | ); 16 | 17 | // ODT_SAT => XYZ => D60_2_D65 => sRGB 18 | const mat3 ACESOutputMat = mat3( 19 | 1.60475, -0.53108, -0.07367, 20 | -0.10208, 1.10813, -0.00605, 21 | -0.00327, -0.07276, 1.07602 22 | ); 23 | 24 | vec3 RRTAndODTFit(vec3 v) 25 | { 26 | vec3 a = v * (v + 0.0245786) - 0.000090537; 27 | vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081; 28 | return a / b; 29 | } 30 | 31 | vec3 ACESFitted(vec3 color) 32 | { 33 | color = color * ACESInputMat; 34 | 35 | // Apply RRT and ODT 36 | color = RRTAndODTFit(color); 37 | 38 | color = color * ACESOutputMat; 39 | 40 | // Clamp to [0, 1] 41 | color = clamp(color, 0.0, 1.0); 42 | 43 | return color; 44 | } 45 | 46 | vec3 linear_to_srgb(vec3 linearColor) 47 | { 48 | bvec3 cutoff = lessThan(linearColor, vec3(0.0031308)); 49 | vec3 higher = vec3(1.055) * pow(linearColor, vec3(1.0 / 2.4)) - vec3(0.055); 50 | vec3 lower = linearColor * vec3(12.92); 51 | 52 | return mix(higher, lower, cutoff); 53 | } 54 | 55 | vec3 apply_dither(vec3 color, vec2 uv) 56 | { 57 | vec2 uvNoise = uv * (vec2(textureSize(s_sceneColor, 0)) / vec2(textureSize(s_noise, 0))); 58 | vec3 noiseSample = textureLod(s_noise, uvNoise, 0).rgb; 59 | return color + vec3((noiseSample - 0.5) / 255.0); 60 | } 61 | 62 | layout(local_size_x = 8, local_size_y = 8) in; 63 | void main() 64 | { 65 | ivec2 gid = ivec2(gl_GlobalInvocationID.xy); 66 | ivec2 targetDim = imageSize(i_finalColor); 67 | if (any(greaterThanEqual(gid, targetDim))) 68 | return; 69 | vec2 uv = (vec2(gid) + 0.5) / targetDim; 70 | 71 | vec3 hdrColor = textureLod(s_sceneColor, uv, 0).rgb; 72 | vec3 ldrColor = ACESFitted(hdrColor); 73 | vec3 srgbColor = linear_to_srgb(ldrColor); 74 | vec3 ditheredColor = apply_dither(srgbColor, uv); 75 | 76 | imageStore(i_finalColor, gid, vec4(ditheredColor, 1.0)); 77 | } -------------------------------------------------------------------------------- /example/textures/RobotoCondensed-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Fwog/ca0317d3cf0f8b15656ed7e6508df4e2e4ac27fe/example/textures/RobotoCondensed-Regular.ttf -------------------------------------------------------------------------------- /example/textures/bluenoise16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Fwog/ca0317d3cf0f8b15656ed7e6508df4e2e4ac27fe/example/textures/bluenoise16.png -------------------------------------------------------------------------------- /example/textures/bluenoise256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Fwog/ca0317d3cf0f8b15656ed7e6508df4e2e4ac27fe/example/textures/bluenoise256.png -------------------------------------------------------------------------------- /example/textures/bluenoise32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Fwog/ca0317d3cf0f8b15656ed7e6508df4e2e4ac27fe/example/textures/bluenoise32.png -------------------------------------------------------------------------------- /example/vendor/stb_image.cpp: -------------------------------------------------------------------------------- 1 | #define STB_IMAGE_IMPLEMENTATION 2 | #include "stb_image.h" -------------------------------------------------------------------------------- /external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # external content definitions 2 | add_library(lib_glad glad/src/gl.c) 3 | target_include_directories(lib_glad PUBLIC glad/include) -------------------------------------------------------------------------------- /include/Fwog/Buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace Fwog 10 | { 11 | /// @brief Used to constrain the types accepted by Buffer 12 | class TriviallyCopyableByteSpan : public std::span 13 | { 14 | public: 15 | template 16 | requires std::is_trivially_copyable_v 17 | TriviallyCopyableByteSpan(const T& t) 18 | : std::span(std::as_bytes(std::span{&t, static_cast(1)})) 19 | { 20 | } 21 | 22 | template 23 | requires std::is_trivially_copyable_v 24 | TriviallyCopyableByteSpan(std::span t) : std::span(std::as_bytes(t)) 25 | { 26 | } 27 | 28 | template 29 | requires std::is_trivially_copyable_v 30 | TriviallyCopyableByteSpan(std::span t) : std::span(std::as_bytes(t)) 31 | { 32 | } 33 | }; 34 | 35 | /// @brief Parameters for Buffer::FillData 36 | struct BufferFillInfo 37 | { 38 | uint64_t offset = 0; 39 | uint64_t size = WHOLE_BUFFER; 40 | uint32_t data = 0; 41 | }; 42 | 43 | enum class BufferStorageFlag : uint32_t 44 | { 45 | NONE = 0, 46 | 47 | /// @brief Allows the user to update the buffer's contents with Buffer::UpdateData 48 | DYNAMIC_STORAGE = 1 << 0, 49 | 50 | /// @brief Hints to the implementation to place the buffer storage in host memory 51 | CLIENT_STORAGE = 1 << 1, 52 | 53 | /// @brief Maps the buffer (persistently and coherently) upon creation 54 | MAP_MEMORY = 1 << 2, 55 | }; 56 | FWOG_DECLARE_FLAG_TYPE(BufferStorageFlags, BufferStorageFlag, uint32_t) 57 | 58 | /// @brief Encapsulates an OpenGL buffer 59 | class Buffer 60 | { 61 | public: 62 | explicit Buffer(size_t size, BufferStorageFlags storageFlags = BufferStorageFlag::NONE, std::string_view name = ""); 63 | explicit Buffer(TriviallyCopyableByteSpan data, BufferStorageFlags storageFlags = BufferStorageFlag::NONE, std::string_view name = ""); 64 | 65 | Buffer(Buffer&& old) noexcept; 66 | Buffer& operator=(Buffer&& old) noexcept; 67 | Buffer(const Buffer&) = delete; 68 | Buffer& operator=(const Buffer&) = delete; 69 | ~Buffer(); 70 | 71 | void UpdateData(TriviallyCopyableByteSpan data, size_t destOffsetBytes = 0); 72 | 73 | void FillData(const BufferFillInfo& clear = {}); 74 | 75 | /// @brief Gets a pointer that is mapped to the buffer's data store 76 | /// @return A pointer to mapped memory if the buffer was created with BufferStorageFlag::MAP_MEMORY, otherwise nullptr 77 | [[nodiscard]] void* GetMappedPointer() noexcept 78 | { 79 | return mappedMemory_; 80 | } 81 | 82 | [[nodiscard]] const void* GetMappedPointer() const noexcept 83 | { 84 | return mappedMemory_; 85 | } 86 | 87 | [[nodiscard]] auto Handle() const noexcept 88 | { 89 | return id_; 90 | } 91 | 92 | [[nodiscard]] auto Size() const noexcept 93 | { 94 | return size_; 95 | } 96 | 97 | [[nodiscard]] bool IsMapped() const noexcept 98 | { 99 | return mappedMemory_ != nullptr; 100 | } 101 | 102 | /// @brief Invalidates the content of the buffer's data store 103 | /// 104 | /// This call can be used to optimize driver synchronization in certain cases. 105 | void Invalidate(); 106 | 107 | protected: 108 | Buffer(const void* data, size_t size, BufferStorageFlags storageFlags, std::string_view name); 109 | 110 | void UpdateData(const void* data, size_t size, size_t offset = 0); 111 | 112 | size_t size_{}; 113 | BufferStorageFlags storageFlags_{}; 114 | uint32_t id_{}; 115 | void* mappedMemory_{}; 116 | }; 117 | 118 | /// @brief A buffer that provides type-safe operations 119 | /// @tparam T A trivially copyable type 120 | template 121 | requires(std::is_trivially_copyable_v) 122 | class TypedBuffer : public Buffer 123 | { 124 | public: 125 | explicit TypedBuffer(BufferStorageFlags storageFlags = BufferStorageFlag::NONE, std::string_view name = "") 126 | : Buffer(sizeof(T), storageFlags, name) 127 | { 128 | } 129 | explicit TypedBuffer(size_t count, BufferStorageFlags storageFlags = BufferStorageFlag::NONE, std::string_view name = "") 130 | : Buffer(sizeof(T) * count, storageFlags, name) 131 | { 132 | } 133 | explicit TypedBuffer(std::span data, BufferStorageFlags storageFlags = BufferStorageFlag::NONE, std::string_view name = "") 134 | : Buffer(data, storageFlags, name) 135 | { 136 | } 137 | explicit TypedBuffer(const T& data, BufferStorageFlags storageFlags = BufferStorageFlag::NONE, std::string_view name = "") 138 | : Buffer(&data, sizeof(T), storageFlags, name) 139 | { 140 | } 141 | 142 | TypedBuffer(TypedBuffer&& other) noexcept = default; 143 | TypedBuffer& operator=(TypedBuffer&& other) noexcept = default; 144 | TypedBuffer(const TypedBuffer& other) = delete; 145 | TypedBuffer& operator=(const TypedBuffer&) = delete; 146 | 147 | void UpdateData(const T& data, size_t startIndex = 0) 148 | { 149 | Buffer::UpdateData(data, sizeof(T) * startIndex); 150 | } 151 | 152 | void UpdateData(std::span data, size_t startIndex = 0) 153 | { 154 | Buffer::UpdateData(data, sizeof(T) * startIndex); 155 | } 156 | 157 | void UpdateData(TriviallyCopyableByteSpan data, size_t destOffsetBytes = 0) = delete; 158 | 159 | [[nodiscard]] T* GetMappedPointer() noexcept 160 | { 161 | return static_cast(mappedMemory_); 162 | } 163 | 164 | [[nodiscard]] const T* GetMappedPointer() const noexcept 165 | { 166 | return static_cast(mappedMemory_); 167 | } 168 | 169 | private: 170 | }; 171 | } // namespace Fwog 172 | -------------------------------------------------------------------------------- /include/Fwog/Config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #if (!defined(FWOG_DEBUG) && !defined(NDEBUG)) || defined(FWOG_FORCE_DEBUG) 5 | #define FWOG_DEBUG 6 | #endif 7 | 8 | #ifndef FWOG_ASSERT 9 | #ifdef FWOG_DEBUG 10 | #define FWOG_ASSERT(x) assert(x) 11 | #else 12 | #define FWOG_ASSERT(x) (void)(x) 13 | #endif 14 | #endif 15 | 16 | #ifndef FWOG_UNREACHABLE 17 | #define FWOG_UNREACHABLE FWOG_ASSERT(0) 18 | #endif 19 | 20 | #ifndef FWOG_OPENGL_HEADER 21 | #define FWOG_OPENGL_HEADER 22 | #endif 23 | 24 | #ifndef FWOG_DEFAULT_CLIP_DEPTH_RANGE_ZERO_TO_ONE 25 | #define FWOG_DEFAULT_CLIP_DEPTH_RANGE_NEGATIVE_ONE_TO_ONE 26 | #endif -------------------------------------------------------------------------------- /include/Fwog/DebugMarker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace Fwog 5 | { 6 | /// @brief Use to demarcate a scope for viewing in a graphics debugger 7 | class ScopedDebugMarker 8 | { 9 | public: 10 | ScopedDebugMarker(const char* message); 11 | ~ScopedDebugMarker(); 12 | 13 | ScopedDebugMarker(const ScopedDebugMarker&) = delete; 14 | }; 15 | } // namespace Fwog -------------------------------------------------------------------------------- /include/Fwog/Exception.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Fwog 8 | { 9 | /// @brief Base type for all exceptions 10 | class Exception : public std::exception 11 | { 12 | public: 13 | Exception() = default; 14 | explicit Exception(std::string message) : message_(std::move(message)) {} 15 | 16 | [[nodiscard]] const char* what() const noexcept override 17 | { 18 | return message_.c_str(); 19 | } 20 | 21 | protected: 22 | std::string message_; 23 | }; 24 | 25 | /// @brief Exception type thrown when a shader encounters a compilation error 26 | /// 27 | /// The exception string will contain the error message 28 | class ShaderCompilationException : public Exception 29 | { 30 | using Exception::Exception; 31 | }; 32 | 33 | /// @brief Exception type thrown when a pipeline encounters a compilation error 34 | /// 35 | /// These can be thrown if OpenGL encounters a linker error when linking two or more shaders into a program. 36 | class PipelineCompilationException : public Exception 37 | { 38 | using Exception::Exception; 39 | }; 40 | } // namespace Fwog -------------------------------------------------------------------------------- /include/Fwog/Fence.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace Fwog 6 | { 7 | /// @brief An object used for CPU-GPU synchronization 8 | class Fence 9 | { 10 | public: 11 | explicit Fence(); 12 | Fence(Fence&& old) noexcept; 13 | Fence& operator=(Fence&& old) noexcept; 14 | Fence(const Fence&) = delete; 15 | Fence& operator=(const Fence&) = delete; 16 | ~Fence(); 17 | 18 | /// @brief Inserts a fence into the command stream 19 | void Signal(); 20 | 21 | /// @brief Waits for the fence to be signaled and returns 22 | /// @return How long (in nanoseconds) the fence blocked 23 | /// @todo Add timeout parameter 24 | uint64_t Wait(); 25 | 26 | private: 27 | void DeleteSync(); 28 | 29 | void* sync_{}; 30 | }; 31 | } // namespace Fwog -------------------------------------------------------------------------------- /include/Fwog/Shader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Fwog 8 | { 9 | enum class PipelineStage 10 | { 11 | VERTEX_SHADER, 12 | TESSELLATION_CONTROL_SHADER, 13 | TESSELLATION_EVALUATION_SHADER, 14 | FRAGMENT_SHADER, 15 | COMPUTE_SHADER, 16 | }; 17 | 18 | #if FWOG_VCC_ENABLE 19 | struct ShaderCppInfo 20 | { 21 | // Shady doesn't properly support non-main entry points for GLSL, so this is omitted for now 22 | //const char* entryPoint = "main"; 23 | std::string_view source; 24 | }; 25 | #endif 26 | 27 | struct SpecializationConstant 28 | { 29 | uint32_t index; 30 | uint32_t value; 31 | }; 32 | 33 | struct ShaderSpirvInfo 34 | { 35 | const char* entryPoint = "main"; 36 | std::span code; 37 | std::span specializationConstants; 38 | }; 39 | 40 | /// @brief A shader object to be used in one or more GraphicsPipeline or ComputePipeline objects 41 | class Shader 42 | { 43 | public: 44 | /// @brief Constructs a shader from GLSL 45 | /// @param stage A pipeline stage 46 | /// @param source A GLSL source string 47 | /// @param name An optional debug identifier 48 | /// @throws ShaderCompilationException if the shader is malformed 49 | explicit Shader(PipelineStage stage, std::string_view source, std::string_view name = ""); 50 | #if FWOG_VCC_ENABLE 51 | /// @brief Constructs a shader from C++ 52 | explicit Shader(PipelineStage stage, const ShaderCppInfo& cppInfo, std::string_view name = ""); 53 | #endif 54 | /// @brief Constructs a shader from SPIR-V 55 | explicit Shader(PipelineStage stage, const ShaderSpirvInfo& spirvInfo, std::string_view name = ""); 56 | Shader(const Shader&) = delete; 57 | Shader(Shader&& old) noexcept; 58 | Shader& operator=(const Shader&) = delete; 59 | Shader& operator=(Shader&& old) noexcept; 60 | ~Shader(); 61 | 62 | /// @brief Gets the handle of the underlying OpenGL shader object 63 | /// @return The shader 64 | [[nodiscard]] uint32_t Handle() const 65 | { 66 | return id_; 67 | } 68 | 69 | private: 70 | uint32_t id_{}; 71 | }; 72 | 73 | namespace detail 74 | { 75 | // Checks shader compile status and throws if it failed 76 | void ValidateShader(uint32_t id); 77 | } 78 | } // namespace Fwog -------------------------------------------------------------------------------- /include/Fwog/Timer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Fwog 8 | { 9 | /// @brief Synchronous single-buffered GPU-timeline timer. Querying the timer will result in a stall 10 | /// as commands are flushed and waited on to complete 11 | /// 12 | /// Use sparingly, and only if detailed perf data is needed for a particular draw. 13 | /// 14 | /// @todo This class is in desparate need of an update 15 | class TimerQuery 16 | { 17 | public: 18 | TimerQuery(); 19 | ~TimerQuery(); 20 | 21 | TimerQuery(const TimerQuery&) = delete; 22 | TimerQuery& operator=(const TimerQuery&) = delete; 23 | TimerQuery(TimerQuery&& old) noexcept 24 | { 25 | queries[0] = std::exchange(old.queries[0], 0); 26 | queries[1] = std::exchange(old.queries[1], 0); 27 | } 28 | TimerQuery& operator=(TimerQuery&& old) noexcept 29 | { 30 | if (&old == this) 31 | return *this; 32 | this->~TimerQuery(); 33 | return *new (this) TimerQuery(std::move(old)); 34 | } 35 | 36 | // returns how (in ns) we blocked for (BLOCKS) 37 | uint64_t GetTimestamp(); 38 | 39 | private: 40 | uint32_t queries[2]; 41 | }; 42 | 43 | /// @brief Async N-buffered timer query that does not induce pipeline stalls 44 | /// 45 | /// Useful for measuring performance of passes every frame without causing stalls. 46 | /// However, the results returned may be from multiple frames ago, 47 | /// and results are not guaranteed to be available. 48 | /// In practice, setting N to 5 should allow at least one query to be available every frame. 49 | class TimerQueryAsync 50 | { 51 | public: 52 | TimerQueryAsync(uint32_t N); 53 | ~TimerQueryAsync(); 54 | 55 | TimerQueryAsync(const TimerQueryAsync&) = delete; 56 | TimerQueryAsync& operator=(const TimerQueryAsync&) = delete; 57 | TimerQueryAsync(TimerQueryAsync&& old) noexcept 58 | : start_(std::exchange(old.start_, 0)), 59 | count_(std::exchange(old.count_, 0)), 60 | capacity_(std::exchange(old.capacity_, 0)), 61 | queries(std::exchange(old.queries, nullptr)) 62 | { 63 | } 64 | TimerQueryAsync& operator=(TimerQueryAsync&& old) noexcept 65 | { 66 | if (&old == this) 67 | return *this; 68 | this->~TimerQueryAsync(); 69 | return *new (this) TimerQueryAsync(std::move(old)); 70 | } 71 | 72 | /// @brief Begins a query zone 73 | /// 74 | /// @note EndZone must be called before another zone can begin 75 | void BeginZone(); 76 | 77 | /// @brief Ends a query zone 78 | /// 79 | /// @note BeginZone must be called before a zone can end 80 | void EndZone(); 81 | 82 | /// @brief Gets the latest available query 83 | /// @return The latest query, if available. Otherwise, std::nullopt is returned 84 | [[nodiscard]] std::optional PopTimestamp(); 85 | 86 | private: 87 | uint32_t start_{}; // next timer to be used for measurement 88 | uint32_t count_{}; // number of timers 'buffered', ie measurement was started by result not read yet 89 | uint32_t capacity_{}; 90 | uint32_t* queries{}; 91 | }; 92 | 93 | /// @brief RAII wrapper for TimerQueryAsync 94 | template 95 | class TimerScoped 96 | { 97 | public: 98 | TimerScoped(T& zone) : zone_(zone) 99 | { 100 | zone_.BeginZone(); 101 | } 102 | 103 | ~TimerScoped() 104 | { 105 | zone_.EndZone(); 106 | } 107 | 108 | private: 109 | T& zone_; 110 | }; 111 | } // namespace Fwog -------------------------------------------------------------------------------- /include/Fwog/detail/ApiToEnum.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Fwog::detail 8 | { 9 | ////////////////////////////////////////////////////////// framebuffer 10 | GLenum FilterToGL(Filter filter); 11 | 12 | GLbitfield AspectMaskToGL(AspectMask bits); 13 | 14 | ////////////////////////////////////////////////////////// buffer 15 | GLbitfield BufferStorageFlagsToGL(BufferStorageFlags flags); 16 | 17 | ////////////////////////////////////////////////////////// texture 18 | GLint ImageTypeToGL(ImageType imageType); 19 | 20 | GLint FormatToGL(Format format); 21 | 22 | GLint UploadFormatToGL(UploadFormat uploadFormat); 23 | 24 | GLint UploadTypeToGL(UploadType uploadType); 25 | 26 | GLint AddressModeToGL(AddressMode addressMode); 27 | 28 | GLsizei SampleCountToGL(SampleCount sampleCount); 29 | 30 | GLint ComponentSwizzleToGL(ComponentSwizzle swizzle); 31 | 32 | int ImageTypeToDimension(ImageType imageType); 33 | 34 | UploadFormat FormatToUploadFormat(Format format); 35 | 36 | bool IsBlockCompressedFormat(Format format); 37 | 38 | ////////////////////////////////////////////////////////// pipeline 39 | GLenum PipelineStageToGL(PipelineStage stage); 40 | GLenum CullModeToGL(CullMode mode); 41 | GLenum PolygonModeToGL(PolygonMode mode); 42 | GLenum FrontFaceToGL(FrontFace face); 43 | GLenum LogicOpToGL(LogicOp op); 44 | GLenum BlendFactorToGL(BlendFactor factor); 45 | GLenum BlendOpToGL(BlendOp op); 46 | GLenum DepthRangeToGL(ClipDepthRange depthRange); 47 | 48 | // arguments for glVertexArrayAttrib*Format 49 | enum class GlFormatClass 50 | { 51 | FLOAT, 52 | INT, 53 | LONG 54 | }; 55 | struct GlVertexFormat 56 | { 57 | GLenum type; // GL_FLOAT, etc. 58 | GLint size; // 1, 2, 3, 4 59 | GLboolean normalized; // GL_TRUE, GL_FALSE 60 | GlFormatClass formatClass; // whether to call Format, IFormat, or LFormat 61 | }; 62 | GLenum FormatToTypeGL(Format format); 63 | GLint FormatToSizeGL(Format format); 64 | GLboolean IsFormatNormalizedGL(Format format); 65 | GlFormatClass FormatToFormatClass(Format format); 66 | 67 | // for clearing color textures, we need to know which of these the texture holds 68 | enum class GlBaseTypeClass 69 | { 70 | FLOAT, 71 | SINT, 72 | UINT 73 | }; 74 | GlBaseTypeClass FormatToBaseTypeClass(Format format); 75 | 76 | ////////////////////////////////////////////////////////// drawing 77 | GLenum PrimitiveTopologyToGL(PrimitiveTopology topology); 78 | 79 | GLenum IndexTypeToGL(IndexType type); 80 | 81 | GLenum CompareOpToGL(CompareOp op); 82 | 83 | GLenum StencilOpToGL(StencilOp op); 84 | 85 | GLbitfield BarrierBitsToGL(MemoryBarrierBits bits); 86 | } // namespace Fwog::detail 87 | -------------------------------------------------------------------------------- /include/Fwog/detail/ContextState.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include FWOG_OPENGL_HEADER 15 | 16 | namespace Fwog::detail 17 | { 18 | constexpr int MAX_COLOR_ATTACHMENTS = 8; 19 | 20 | struct ContextState 21 | { 22 | DeviceProperties properties; 23 | 24 | void (*verboseMessageCallback)(std::string_view) = nullptr; 25 | void (*renderToSwapchainHook)(const SwapchainRenderInfo& renderInfo, const std::function& func) = nullptr; 26 | void (*renderHook)(const RenderInfo& renderInfo, const std::function& func) = nullptr; 27 | void (*renderNoAttachmentsHook)(const RenderNoAttachmentsInfo& renderInfo, const std::function& func) = nullptr; 28 | void (*computeHook)(std::string_view name, const std::function& func) = nullptr; 29 | 30 | // Used for scope error checking 31 | bool isComputeActive = false; 32 | bool isRendering = false; 33 | 34 | // Used for error checking for indexed draws 35 | bool isIndexBufferBound = false; 36 | 37 | // Currently unused 38 | bool isRenderingToSwapchain = false; 39 | 40 | // True during a render or compute scope that has a name. 41 | bool isScopedDebugGroupPushed = false; 42 | 43 | // True when a pipeline with a name is bound during a render or compute scope. 44 | bool isPipelineDebugGroupPushed = false; 45 | 46 | // True during SwapchainRendering scopes that disable sRGB. 47 | // This is needed since regular Rendering scopes always have framebuffer sRGB enabled 48 | // (the user uses framebuffer attachments to decide if they want the linear->sRGB conversion). 49 | bool srgbWasDisabled = false; 50 | 51 | // Stores a pointer to the previously bound graphics pipeline state. This is used for state deduplication. 52 | // A shared_ptr is needed as the user can delete pipelines at any time, but we need to ensure it stays alive until 53 | // the next pipeline is bound. 54 | std::shared_ptr lastGraphicsPipeline{}; 55 | bool lastPipelineWasCompute = false; 56 | 57 | std::shared_ptr lastComputePipeline{}; 58 | 59 | // Currently unused (and probably shouldn't be used) 60 | const RenderInfo* lastRenderInfo{}; 61 | 62 | // These can be set at the start of rendering, so they need to be tracked separately from the other pipeline state. 63 | std::array lastColorMask = {}; 64 | bool lastDepthMask = true; 65 | uint32_t lastStencilMask[2] = {static_cast(-1), static_cast(-1)}; 66 | bool initViewport = true; 67 | Viewport lastViewport = {}; 68 | Rect2D lastScissor = {}; 69 | bool scissorEnabled = false; 70 | 71 | // Potentially used for state deduplication. 72 | GLuint currentVao = 0; 73 | GLuint currentFbo = 0; 74 | 75 | // These persist until another Pipeline is bound. 76 | // They are not used for state deduplication, as they are arguments for GL draw calls. 77 | PrimitiveTopology currentTopology{}; 78 | IndexType currentIndexType{}; 79 | 80 | detail::FramebufferCache fboCache; 81 | detail::VertexArrayCache vaoCache; 82 | detail::SamplerCache samplerCache; 83 | } inline* context = nullptr; 84 | 85 | // Clears all resource bindings. 86 | // This is called at the beginning of rendering/compute scopes 87 | // or when the pipeline state has been invalidated, but only in debug mode. 88 | void ZeroResourceBindings(); 89 | 90 | // Prints a formatted message to a stringstream, then 91 | // invokes the message callback with the formatted message 92 | template 93 | void InvokeVerboseMessageCallback(Args&&... args) 94 | { 95 | if (context->verboseMessageCallback != nullptr) 96 | { 97 | // This probably allocates, but at least it works 98 | std::stringstream stream; 99 | ((stream << args), ...); 100 | context->verboseMessageCallback(stream.str().c_str()); 101 | } 102 | } 103 | } // namespace Fwog::detail -------------------------------------------------------------------------------- /include/Fwog/detail/Flags.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Taken from https://github.com/cdgiessen/vk-module/blob/076baa98cba35cd93a6ab56c3fd1b1ea2313f806/codegen_text.py#L53 4 | // Thanks Charles! 5 | #define FWOG_DECLARE_FLAG_TYPE(FLAG_TYPE, FLAG_BITS, BASE_TYPE) \ 6 | \ 7 | struct FLAG_TYPE \ 8 | { \ 9 | BASE_TYPE flags = static_cast(0); \ 10 | \ 11 | constexpr FLAG_TYPE() noexcept = default; \ 12 | constexpr explicit FLAG_TYPE(BASE_TYPE in) noexcept : flags(in) {} \ 13 | constexpr FLAG_TYPE(FLAG_BITS in) noexcept : flags(static_cast(in)) {} \ 14 | constexpr bool operator==(FLAG_TYPE const& right) const \ 15 | { \ 16 | return flags == right.flags; \ 17 | } \ 18 | constexpr bool operator!=(FLAG_TYPE const& right) const \ 19 | { \ 20 | return flags != right.flags; \ 21 | } \ 22 | constexpr explicit operator BASE_TYPE() const \ 23 | { \ 24 | return flags; \ 25 | } \ 26 | constexpr explicit operator bool() const noexcept \ 27 | { \ 28 | return flags != 0; \ 29 | } \ 30 | }; \ 31 | constexpr FLAG_TYPE operator|(FLAG_TYPE a, FLAG_TYPE b) noexcept \ 32 | { \ 33 | return static_cast(a.flags | b.flags); \ 34 | } \ 35 | constexpr FLAG_TYPE operator&(FLAG_TYPE a, FLAG_TYPE b) noexcept \ 36 | { \ 37 | return static_cast(a.flags & b.flags); \ 38 | } \ 39 | constexpr FLAG_TYPE operator^(FLAG_TYPE a, FLAG_TYPE b) noexcept \ 40 | { \ 41 | return static_cast(a.flags ^ b.flags); \ 42 | } \ 43 | constexpr FLAG_TYPE operator~(FLAG_TYPE a) noexcept \ 44 | { \ 45 | return static_cast(~a.flags); \ 46 | } \ 47 | constexpr FLAG_TYPE& operator|=(FLAG_TYPE& a, FLAG_TYPE b) noexcept \ 48 | { \ 49 | a.flags = (a.flags | b.flags); \ 50 | return a; \ 51 | } \ 52 | constexpr FLAG_TYPE& operator&=(FLAG_TYPE& a, FLAG_TYPE b) noexcept \ 53 | { \ 54 | a.flags = (a.flags & b.flags); \ 55 | return a; \ 56 | } \ 57 | constexpr FLAG_TYPE operator^=(FLAG_TYPE& a, FLAG_TYPE b) noexcept \ 58 | { \ 59 | a.flags = (a.flags ^ b.flags); \ 60 | return a; \ 61 | } \ 62 | constexpr FLAG_TYPE operator|(FLAG_BITS a, FLAG_BITS b) noexcept \ 63 | { \ 64 | return static_cast(static_cast(a) | static_cast(b)); \ 65 | } \ 66 | constexpr FLAG_TYPE operator&(FLAG_BITS a, FLAG_BITS b) noexcept \ 67 | { \ 68 | return static_cast(static_cast(a) & static_cast(b)); \ 69 | } \ 70 | constexpr FLAG_TYPE operator~(FLAG_BITS key) noexcept \ 71 | { \ 72 | return static_cast(~static_cast(key)); \ 73 | } \ 74 | constexpr FLAG_TYPE operator^(FLAG_BITS a, FLAG_BITS b) noexcept \ 75 | { \ 76 | return static_cast(static_cast(a) ^ static_cast(b)); \ 77 | } 78 | -------------------------------------------------------------------------------- /include/Fwog/detail/FramebufferCache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Fwog/Rendering.h" 3 | #include "Fwog/Texture.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace Fwog::detail 10 | { 11 | struct TextureProxy 12 | { 13 | TextureCreateInfo createInfo; 14 | uint32_t id; 15 | 16 | bool operator==(const TextureProxy&) const noexcept = default; 17 | }; 18 | 19 | struct RenderAttachments 20 | { 21 | std::vector colorAttachments{}; 22 | std::optional depthAttachment{}; 23 | std::optional stencilAttachment{}; 24 | 25 | bool operator==(const RenderAttachments& rhs) const; 26 | }; 27 | 28 | class FramebufferCache 29 | { 30 | public: 31 | FramebufferCache() = default; 32 | FramebufferCache(const FramebufferCache&) = delete; 33 | FramebufferCache& operator=(const FramebufferCache&) = delete; 34 | FramebufferCache(FramebufferCache&&) noexcept = default; 35 | FramebufferCache& operator=(FramebufferCache&&) noexcept = default; 36 | 37 | uint32_t CreateOrGetCachedFramebuffer(const RenderInfo& renderInfo); 38 | 39 | [[nodiscard]] std::size_t Size() const 40 | { 41 | return framebufferCacheKey_.size(); 42 | } 43 | 44 | void Clear(); 45 | 46 | ~FramebufferCache() 47 | { 48 | Clear(); 49 | } 50 | 51 | void RemoveTexture(const Texture& texture); 52 | 53 | private: 54 | std::vector framebufferCacheKey_; 55 | std::vector framebufferCacheValue_; 56 | }; 57 | } // namespace Fwog::detail 58 | -------------------------------------------------------------------------------- /include/Fwog/detail/Hash.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace Fwog::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 Fwog::detail::hashing 47 | -------------------------------------------------------------------------------- /include/Fwog/detail/PipelineManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Fwog::detail 8 | { 9 | // owning versions of pipeline info structs so we don't lose references 10 | struct VertexInputStateOwning 11 | { 12 | std::vector vertexBindingDescriptions; 13 | }; 14 | 15 | struct ColorBlendStateOwning 16 | { 17 | bool logicOpEnable; 18 | LogicOp logicOp; 19 | std::vector attachments; 20 | float blendConstants[4]; 21 | }; 22 | 23 | struct GraphicsPipelineInfoOwning 24 | { 25 | std::string name; 26 | InputAssemblyState inputAssemblyState; 27 | VertexInputStateOwning vertexInputState; 28 | TessellationState tessellationState; 29 | RasterizationState rasterizationState; 30 | MultisampleState multisampleState; 31 | DepthState depthState; 32 | StencilState stencilState; 33 | ColorBlendStateOwning colorBlendState; 34 | std::vector> uniformBlocks; 35 | std::vector> storageBlocks; 36 | std::vector> samplersAndImages; 37 | }; 38 | 39 | struct ComputePipelineInfoOwning 40 | { 41 | std::string name; 42 | Extent3D workgroupSize; 43 | std::vector> uniformBlocks; 44 | std::vector> storageBlocks; 45 | std::vector> samplersAndImages; 46 | }; 47 | 48 | uint64_t CompileGraphicsPipelineInternal(const GraphicsPipelineInfo& info); 49 | std::shared_ptr GetGraphicsPipelineInternal(uint64_t pipeline); 50 | void DestroyGraphicsPipelineInternal(uint64_t pipeline); 51 | 52 | uint64_t CompileComputePipelineInternal(const ComputePipelineInfo& info); 53 | std::shared_ptr GetComputePipelineInternal(uint64_t pipeline); 54 | void DestroyComputePipelineInternal(uint64_t pipeline); 55 | } // namespace Fwog::detail -------------------------------------------------------------------------------- /include/Fwog/detail/SamplerCache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Fwog/BasicTypes.h" 3 | #include "Fwog/Texture.h" 4 | #include 5 | 6 | template<> 7 | struct std::hash 8 | { 9 | std::size_t operator()(const Fwog::SamplerState& k) const noexcept; 10 | }; 11 | 12 | namespace Fwog::detail 13 | { 14 | class SamplerCache 15 | { 16 | public: 17 | SamplerCache() = default; 18 | SamplerCache(const SamplerCache&) = delete; 19 | SamplerCache& operator=(const SamplerCache&) = delete; 20 | SamplerCache(SamplerCache&&) noexcept = default; 21 | SamplerCache& operator=(SamplerCache&&) noexcept = default; 22 | ~SamplerCache() 23 | { 24 | Clear(); 25 | } 26 | 27 | Sampler CreateOrGetCachedTextureSampler(const SamplerState& samplerState); 28 | [[nodiscard]] size_t Size() const; 29 | void Clear(); 30 | 31 | private: 32 | std::unordered_map samplerCache_; 33 | }; 34 | } // namespace Fwog::detail -------------------------------------------------------------------------------- /include/Fwog/detail/ShaderCPP.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace Fwog::detail 7 | { 8 | [[nodiscard]] std::string CompileShaderCppToGlsl(const std::filesystem::path& path); 9 | [[nodiscard]] std::string CompileShaderCppToGlsl(std::string_view sourceCPP); 10 | } // namespace Fwog::detail -------------------------------------------------------------------------------- /include/Fwog/detail/ShaderGLSL.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace Fwog::detail 5 | { 6 | [[nodiscard]] uint32_t CompileShaderGLSL(PipelineStage stage, std::string_view sourceGLSL); 7 | } // namespace Fwog::detail -------------------------------------------------------------------------------- /include/Fwog/detail/ShaderSPIRV.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace Fwog::detail 5 | { 6 | [[nodiscard]] uint32_t CompileShaderSpirv(PipelineStage stage, const ShaderSpirvInfo& spirvInfo); 7 | } // namespace Fwog::detail -------------------------------------------------------------------------------- /include/Fwog/detail/VertexArrayCache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace Fwog::detail 7 | { 8 | struct VertexInputStateOwning; 9 | 10 | class VertexArrayCache 11 | { 12 | public: 13 | VertexArrayCache() = default; 14 | VertexArrayCache(const VertexArrayCache&) = delete; 15 | VertexArrayCache& operator=(const VertexArrayCache&) = delete; 16 | VertexArrayCache(VertexArrayCache&&) noexcept = default; 17 | VertexArrayCache& operator=(VertexArrayCache&&) noexcept = default; 18 | 19 | ~VertexArrayCache() 20 | { 21 | Clear(); 22 | } 23 | 24 | uint32_t CreateOrGetCachedVertexArray(const VertexInputStateOwning& inputState); 25 | 26 | [[nodiscard]] size_t Size() const 27 | { 28 | return vertexArrayCache_.size(); 29 | } 30 | 31 | void Clear(); 32 | 33 | private: 34 | std::unordered_map vertexArrayCache_; 35 | }; 36 | } // namespace Fwog::detail 37 | -------------------------------------------------------------------------------- /media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuanDiegoMontoya/Fwog/ca0317d3cf0f8b15656ed7e6508df4e2e4ac27fe/media/logo.png -------------------------------------------------------------------------------- /src/Buffer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include FWOG_OPENGL_HEADER 6 | 7 | namespace Fwog 8 | { 9 | constexpr size_t RoundUp(size_t numberToRoundUp, size_t multipleOf) 10 | { 11 | assert(multipleOf); 12 | return ((numberToRoundUp + multipleOf - 1) / multipleOf) * multipleOf; 13 | } 14 | 15 | Buffer::Buffer(const void* data, size_t size, BufferStorageFlags storageFlags, std::string_view name) 16 | : size_(RoundUp(size, 16)), storageFlags_(storageFlags) 17 | { 18 | GLbitfield glflags = detail::BufferStorageFlagsToGL(storageFlags); 19 | glCreateBuffers(1, &id_); 20 | glNamedBufferStorage(id_, size_, data, glflags); 21 | if (storageFlags & BufferStorageFlag::MAP_MEMORY) 22 | { 23 | // GL_MAP_UNSYNCHRONIZED_BIT should be used if the user can map and unmap buffers at their own will 24 | constexpr GLenum access = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT; 25 | mappedMemory_ = glMapNamedBufferRange(id_, 0, size_, access); 26 | } 27 | 28 | if (!name.empty()) 29 | { 30 | glObjectLabel(GL_BUFFER, id_, static_cast(name.length()), name.data()); 31 | } 32 | 33 | detail::InvokeVerboseMessageCallback("Created buffer with handle ", id_); 34 | } 35 | 36 | Buffer::Buffer(size_t size, BufferStorageFlags storageFlags, std::string_view name) 37 | : Buffer(nullptr, size, storageFlags, name) 38 | { 39 | } 40 | 41 | Buffer::Buffer(TriviallyCopyableByteSpan data, BufferStorageFlags storageFlags, std::string_view name) 42 | : Buffer(data.data(), data.size_bytes(), storageFlags, name) 43 | { 44 | } 45 | 46 | Buffer::Buffer(Buffer&& old) noexcept 47 | : size_(std::exchange(old.size_, 0)), 48 | storageFlags_(std::exchange(old.storageFlags_, BufferStorageFlag::NONE)), 49 | id_(std::exchange(old.id_, 0)), 50 | mappedMemory_(std::exchange(old.mappedMemory_, nullptr)) 51 | { 52 | } 53 | 54 | Buffer& Buffer::operator=(Buffer&& old) noexcept 55 | { 56 | if (&old == this) 57 | return *this; 58 | this->~Buffer(); 59 | return *new (this) Buffer(std::move(old)); 60 | } 61 | 62 | Buffer::~Buffer() 63 | { 64 | if (id_) 65 | { 66 | detail::InvokeVerboseMessageCallback("Destroyed buffer with handle ", id_); 67 | 68 | if (mappedMemory_) 69 | { 70 | glUnmapNamedBuffer(id_); 71 | } 72 | glDeleteBuffers(1, &id_); 73 | } 74 | } 75 | 76 | void Buffer::UpdateData(TriviallyCopyableByteSpan data, size_t destOffsetBytes) 77 | { 78 | UpdateData(data.data(), data.size_bytes(), destOffsetBytes); 79 | } 80 | 81 | void Buffer::UpdateData(const void* data, size_t size, size_t offset) 82 | { 83 | FWOG_ASSERT((storageFlags_ & BufferStorageFlag::DYNAMIC_STORAGE) && 84 | "UpdateData can only be called on buffers created with the DYNAMIC_STORAGE flag"); 85 | FWOG_ASSERT(size + offset <= Size()); 86 | glNamedBufferSubData(id_, static_cast(offset), static_cast(size), data); 87 | } 88 | 89 | void Buffer::FillData(const BufferFillInfo& clear) 90 | { 91 | const auto actualSize = clear.size == WHOLE_BUFFER ? size_ : clear.size; 92 | FWOG_ASSERT(actualSize % 4 == 0 && "Size must be a multiple of 4 bytes"); 93 | glClearNamedBufferSubData(id_, 94 | GL_R32UI, 95 | clear.offset, 96 | actualSize, 97 | GL_RED_INTEGER, 98 | GL_UNSIGNED_INT, 99 | &clear.data); 100 | } 101 | 102 | void Buffer::Invalidate() 103 | { 104 | glInvalidateBufferData(id_); 105 | } 106 | } // namespace Fwog 107 | -------------------------------------------------------------------------------- /src/DebugMarker.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include FWOG_OPENGL_HEADER 3 | 4 | namespace Fwog 5 | { 6 | ScopedDebugMarker::ScopedDebugMarker(const char* message) 7 | { 8 | glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, -1, message); 9 | } 10 | 11 | ScopedDebugMarker::~ScopedDebugMarker() 12 | { 13 | glPopDebugGroup(); 14 | } 15 | } // namespace Fwog -------------------------------------------------------------------------------- /src/Fence.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include FWOG_OPENGL_HEADER 6 | 7 | namespace Fwog 8 | { 9 | Fence::Fence() {} 10 | 11 | Fence::~Fence() 12 | { 13 | DeleteSync(); 14 | } 15 | 16 | Fence::Fence(Fence&& old) noexcept : sync_(std::exchange(old.sync_, nullptr)) {} 17 | 18 | Fence& Fence::operator=(Fence&& old) noexcept 19 | { 20 | if (this == &old) 21 | return *this; 22 | this->~Fence(); 23 | return *new (this) Fence(std::move(old)); 24 | } 25 | 26 | void Fence::Signal() 27 | { 28 | FWOG_ASSERT(sync_ == nullptr); 29 | sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); 30 | } 31 | 32 | uint64_t Fence::Wait() 33 | { 34 | FWOG_ASSERT(sync_ != nullptr); 35 | GLuint id; 36 | glGenQueries(1, &id); 37 | glBeginQuery(GL_TIME_ELAPSED, id); 38 | GLenum result = glClientWaitSync(reinterpret_cast(sync_), 39 | GL_SYNC_FLUSH_COMMANDS_BIT, 40 | std::numeric_limits::max()); 41 | FWOG_ASSERT(result == GL_CONDITION_SATISFIED); 42 | glEndQuery(GL_TIME_ELAPSED); 43 | uint64_t elapsed; 44 | glGetQueryObjectui64v(id, GL_QUERY_RESULT, &elapsed); 45 | glDeleteQueries(1, &id); 46 | DeleteSync(); 47 | return elapsed; 48 | } 49 | 50 | void Fence::DeleteSync() 51 | { 52 | glDeleteSync(reinterpret_cast(sync_)); 53 | sync_ = nullptr; 54 | } 55 | } // namespace Fwog -------------------------------------------------------------------------------- /src/Pipeline.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include FWOG_OPENGL_HEADER 8 | 9 | namespace Fwog 10 | { 11 | GraphicsPipeline::GraphicsPipeline(const GraphicsPipelineInfo& info) 12 | : id_(detail::CompileGraphicsPipelineInternal(info)) 13 | 14 | { 15 | detail::InvokeVerboseMessageCallback("Created graphics program with handle ", id_); 16 | } 17 | 18 | GraphicsPipeline::~GraphicsPipeline() 19 | { 20 | if (id_ != 0) 21 | { 22 | detail::InvokeVerboseMessageCallback("Destroyed graphics program with handle ", id_); 23 | detail::DestroyGraphicsPipelineInternal(id_); 24 | } 25 | } 26 | 27 | GraphicsPipeline::GraphicsPipeline(GraphicsPipeline&& old) noexcept : id_(std::exchange(old.id_, 0)) {} 28 | 29 | GraphicsPipeline& GraphicsPipeline::operator=(GraphicsPipeline&& old) noexcept 30 | { 31 | if (this == &old) 32 | { 33 | return *this; 34 | } 35 | 36 | id_ = std::exchange(old.id_, 0); 37 | return *this; 38 | } 39 | 40 | ComputePipeline::ComputePipeline(const ComputePipelineInfo& info) 41 | : id_(detail::CompileComputePipelineInternal(info)) 42 | { 43 | detail::InvokeVerboseMessageCallback("Created compute program with handle ", id_); 44 | } 45 | 46 | ComputePipeline::~ComputePipeline() 47 | { 48 | if (id_ != 0) 49 | { 50 | detail::InvokeVerboseMessageCallback("Destroyed compute program with handle ", id_); 51 | detail::DestroyComputePipelineInternal(id_); 52 | } 53 | } 54 | 55 | ComputePipeline::ComputePipeline(ComputePipeline&& old) noexcept 56 | : id_(std::exchange(old.id_, 0)) 57 | { 58 | } 59 | 60 | ComputePipeline& ComputePipeline::operator=(ComputePipeline&& old) noexcept 61 | { 62 | if (this == &old) 63 | { 64 | return *this; 65 | } 66 | 67 | id_ = std::exchange(old.id_, 0); 68 | return *this; 69 | } 70 | 71 | Extent3D ComputePipeline::WorkgroupSize() const 72 | { 73 | return detail::GetComputePipelineInternal(id_)->workgroupSize; 74 | } 75 | } // namespace Fwog -------------------------------------------------------------------------------- /src/Shader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #if FWOG_VCC_ENABLE 6 | #include 7 | #endif 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include FWOG_OPENGL_HEADER 14 | 15 | namespace Fwog 16 | { 17 | Shader::Shader(PipelineStage stage, std::string_view source, std::string_view name) 18 | { 19 | id_ = detail::CompileShaderGLSL(stage, source); 20 | 21 | detail::ValidateShader(id_); 22 | if (!name.empty()) 23 | { 24 | glObjectLabel(GL_SHADER, id_, static_cast(name.length()), name.data()); 25 | } 26 | detail::InvokeVerboseMessageCallback("Created shader with handle ", id_); 27 | } 28 | 29 | #if FWOG_VCC_ENABLE == 1 30 | Shader::Shader(PipelineStage stage, const ShaderCppInfo& cppInfo, std::string_view name) 31 | { 32 | const auto glsl = detail::CompileShaderCppToGlsl(cppInfo.source); 33 | id_ = detail::CompileShaderGLSL(stage, glsl); 34 | 35 | detail::ValidateShader(id_); 36 | if (!name.empty()) 37 | { 38 | glObjectLabel(GL_SHADER, id_, static_cast(name.length()), name.data()); 39 | } 40 | detail::InvokeVerboseMessageCallback("Created shader with handle ", id_); 41 | } 42 | #endif 43 | 44 | Shader::Shader(PipelineStage stage, const ShaderSpirvInfo& spirvInfo, std::string_view name) 45 | { 46 | id_ = detail::CompileShaderSpirv(stage, spirvInfo); 47 | 48 | detail::ValidateShader(id_); 49 | if (!name.empty()) 50 | { 51 | glObjectLabel(GL_SHADER, id_, static_cast(name.length()), name.data()); 52 | } 53 | detail::InvokeVerboseMessageCallback("Created shader with handle ", id_); 54 | } 55 | 56 | Shader::Shader(Shader&& old) noexcept : id_(std::exchange(old.id_, 0)) {} 57 | 58 | Shader& Shader::operator=(Shader&& old) noexcept 59 | { 60 | if (&old == this) 61 | return *this; 62 | this->~Shader(); 63 | return *new (this) Shader(std::move(old)); 64 | } 65 | 66 | Shader::~Shader() 67 | { 68 | detail::InvokeVerboseMessageCallback("Destroyed shader with handle ", id_); 69 | glDeleteShader(id_); 70 | } 71 | } // namespace Fwog 72 | 73 | void Fwog::detail::ValidateShader(uint32_t id) 74 | { 75 | GLint success; 76 | glGetShaderiv(id, GL_COMPILE_STATUS, &success); 77 | if (!success) 78 | { 79 | GLint infoLength = 512; 80 | glGetShaderiv(id, GL_INFO_LOG_LENGTH, &infoLength); 81 | auto infoLog = std::string(infoLength + 1, '\0'); 82 | glGetShaderInfoLog(id, infoLength, nullptr, infoLog.data()); 83 | glDeleteShader(id); 84 | throw ShaderCompilationException("Failed to compile shader source.\n" + infoLog); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Timer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include FWOG_OPENGL_HEADER 4 | 5 | namespace Fwog 6 | { 7 | TimerQuery::TimerQuery() 8 | { 9 | glGenQueries(2, queries); 10 | glQueryCounter(queries[0], GL_TIMESTAMP); 11 | } 12 | 13 | TimerQuery::~TimerQuery() 14 | { 15 | glDeleteQueries(2, queries); 16 | } 17 | 18 | uint64_t TimerQuery::GetTimestamp() 19 | { 20 | int complete = 0; 21 | glQueryCounter(queries[1], GL_TIMESTAMP); 22 | while (!complete) 23 | glGetQueryObjectiv(queries[1], GL_QUERY_RESULT_AVAILABLE, &complete); 24 | uint64_t startTime, endTime; 25 | glGetQueryObjectui64v(queries[0], GL_QUERY_RESULT, &startTime); 26 | glGetQueryObjectui64v(queries[1], GL_QUERY_RESULT, &endTime); 27 | std::swap(queries[0], queries[1]); 28 | return endTime - startTime; 29 | } 30 | 31 | TimerQueryAsync::TimerQueryAsync(uint32_t N) : capacity_(N) 32 | { 33 | FWOG_ASSERT(capacity_ > 0); 34 | queries = new uint32_t[capacity_ * 2]; 35 | glGenQueries(capacity_ * 2, queries); 36 | } 37 | 38 | TimerQueryAsync::~TimerQueryAsync() 39 | { 40 | glDeleteQueries(capacity_ * 2, queries); 41 | delete[] queries; 42 | } 43 | 44 | void TimerQueryAsync::BeginZone() 45 | { 46 | // begin a query if there is at least one inactive 47 | if (count_ < capacity_) 48 | { 49 | glQueryCounter(queries[start_], GL_TIMESTAMP); 50 | } 51 | } 52 | 53 | void TimerQueryAsync::EndZone() 54 | { 55 | // end a query if there is at least one inactive 56 | if (count_ < capacity_) 57 | { 58 | glQueryCounter(queries[start_ + capacity_], GL_TIMESTAMP); 59 | start_ = (start_ + 1) % capacity_; // wrap 60 | count_++; 61 | } 62 | } 63 | 64 | std::optional TimerQueryAsync::PopTimestamp() 65 | { 66 | // return nothing if there is no active query 67 | if (count_ == 0) 68 | { 69 | return std::nullopt; 70 | } 71 | 72 | // get the index of the oldest query 73 | uint32_t index = (start_ + capacity_ - count_) % capacity_; 74 | 75 | // getting the start result is a sanity check 76 | GLint startResultAvailable{}; 77 | GLint endResultAvailable{}; 78 | glGetQueryObjectiv(queries[index], GL_QUERY_RESULT_AVAILABLE, &startResultAvailable); 79 | glGetQueryObjectiv(queries[index + capacity_], GL_QUERY_RESULT_AVAILABLE, &endResultAvailable); 80 | 81 | // the oldest query's result is not available, abandon ship! 82 | if (startResultAvailable == GL_FALSE || endResultAvailable == GL_FALSE) 83 | { 84 | return std::nullopt; 85 | } 86 | 87 | // pop oldest timing and retrieve result 88 | count_--; 89 | uint64_t startTimestamp{}; 90 | uint64_t endTimestamp{}; 91 | glGetQueryObjectui64v(queries[index], GL_QUERY_RESULT, &startTimestamp); 92 | glGetQueryObjectui64v(queries[index + capacity_], GL_QUERY_RESULT, &endTimestamp); 93 | return endTimestamp - startTimestamp; 94 | } 95 | } // namespace Fwog -------------------------------------------------------------------------------- /src/detail/FramebufferCache.cpp: -------------------------------------------------------------------------------- 1 | #include "Fwog/detail/FramebufferCache.h" 2 | #include "Fwog/Texture.h" 3 | #include "Fwog/detail/ContextState.h" 4 | #include FWOG_OPENGL_HEADER 5 | 6 | namespace Fwog::detail 7 | { 8 | uint32_t FramebufferCache::CreateOrGetCachedFramebuffer(const RenderInfo& renderInfo) 9 | { 10 | RenderAttachments attachments; 11 | for (const auto& colorAttachment : renderInfo.colorAttachments) 12 | { 13 | attachments.colorAttachments.emplace_back(TextureProxy{ 14 | colorAttachment.texture.get().GetCreateInfo(), 15 | detail::GetHandle(colorAttachment.texture), 16 | }); 17 | } 18 | if (renderInfo.depthAttachment) 19 | { 20 | attachments.depthAttachment.emplace(TextureProxy{ 21 | renderInfo.depthAttachment->texture.get().GetCreateInfo(), 22 | detail::GetHandle(renderInfo.depthAttachment->texture), 23 | }); 24 | } 25 | if (renderInfo.stencilAttachment) 26 | { 27 | attachments.stencilAttachment.emplace(TextureProxy{ 28 | renderInfo.stencilAttachment->texture.get().GetCreateInfo(), 29 | detail::GetHandle(renderInfo.stencilAttachment->texture), 30 | }); 31 | } 32 | 33 | for (size_t i = 0; i < framebufferCacheKey_.size(); i++) 34 | { 35 | if (framebufferCacheKey_[i] == attachments) 36 | return framebufferCacheValue_[i]; 37 | } 38 | 39 | uint32_t fbo{}; 40 | glCreateFramebuffers(1, &fbo); 41 | std::vector drawBuffers; 42 | for (size_t i = 0; i < attachments.colorAttachments.size(); i++) 43 | { 44 | const auto& attachment = attachments.colorAttachments[i]; 45 | glNamedFramebufferTexture(fbo, static_cast(GL_COLOR_ATTACHMENT0 + i), attachment.id, 0); 46 | drawBuffers.push_back(static_cast(GL_COLOR_ATTACHMENT0 + i)); 47 | } 48 | glNamedFramebufferDrawBuffers(fbo, static_cast(drawBuffers.size()), drawBuffers.data()); 49 | 50 | if (attachments.depthAttachment && attachments.stencilAttachment && 51 | attachments.depthAttachment == attachments.stencilAttachment) 52 | { 53 | glNamedFramebufferTexture(fbo, GL_DEPTH_STENCIL_ATTACHMENT, attachments.depthAttachment->id, 0); 54 | } 55 | else 56 | { 57 | if (attachments.depthAttachment) 58 | { 59 | glNamedFramebufferTexture(fbo, GL_DEPTH_ATTACHMENT, attachments.depthAttachment->id, 0); 60 | } 61 | 62 | if (attachments.stencilAttachment) 63 | { 64 | glNamedFramebufferTexture(fbo, GL_STENCIL_ATTACHMENT, attachments.stencilAttachment->id, 0); 65 | } 66 | } 67 | 68 | detail::InvokeVerboseMessageCallback("Created framebuffer with handle ", fbo); 69 | 70 | framebufferCacheKey_.emplace_back(std::move(attachments)); 71 | return framebufferCacheValue_.emplace_back(fbo); 72 | } 73 | 74 | void FramebufferCache::Clear() 75 | { 76 | for (const auto& fbo : framebufferCacheValue_) 77 | { 78 | detail::InvokeVerboseMessageCallback("Destroyed framebuffer with handle ", fbo); 79 | glDeleteFramebuffers(1, &fbo); 80 | } 81 | 82 | framebufferCacheKey_.clear(); 83 | framebufferCacheValue_.clear(); 84 | } 85 | 86 | // Must be called when a texture is deleted, otherwise the cache becomes invalid. 87 | void FramebufferCache::RemoveTexture(const Texture& texture) 88 | { 89 | const TextureProxy texp = {texture.GetCreateInfo(), detail::GetHandle(texture)}; 90 | 91 | for (size_t i = 0; i < framebufferCacheKey_.size(); i++) 92 | { 93 | const auto attachments = framebufferCacheKey_[i]; 94 | 95 | for (const auto& ci : attachments.colorAttachments) 96 | { 97 | if (texp == ci) 98 | { 99 | framebufferCacheKey_.erase(framebufferCacheKey_.begin() + i); 100 | auto fboIt = framebufferCacheValue_.begin() + i; 101 | glDeleteFramebuffers(1, &*fboIt); 102 | framebufferCacheValue_.erase(fboIt); 103 | i--; 104 | break; 105 | } 106 | } 107 | 108 | if (texp == attachments.depthAttachment || texp == attachments.stencilAttachment) 109 | { 110 | framebufferCacheKey_.erase(framebufferCacheKey_.begin() + i); 111 | auto fboIt = framebufferCacheValue_.begin() + i; 112 | glDeleteFramebuffers(1, &*fboIt); 113 | framebufferCacheValue_.erase(fboIt); 114 | i--; 115 | continue; 116 | } 117 | } 118 | } 119 | 120 | bool RenderAttachments::operator==(const RenderAttachments& rhs) const 121 | { 122 | if (colorAttachments.size() != rhs.colorAttachments.size()) 123 | return false; 124 | 125 | // Crucially, two attachments with the same address are not necessarily the same. 126 | // The inverse is also true: two attachments with different addresses are not necessarily different. 127 | 128 | for (size_t i = 0; i < colorAttachments.size(); i++) 129 | { 130 | // Color attachments must be non-null 131 | if (colorAttachments[i] != rhs.colorAttachments[i]) 132 | return false; 133 | } 134 | 135 | // Nullity of the attachments differ 136 | if ((depthAttachment && !rhs.depthAttachment) || (!depthAttachment && rhs.depthAttachment)) 137 | return false; 138 | // Both attachments are non-null, but have different values 139 | if (depthAttachment && rhs.depthAttachment && (*depthAttachment != *rhs.depthAttachment)) 140 | return false; 141 | 142 | if ((stencilAttachment && !rhs.stencilAttachment) || (!stencilAttachment && rhs.stencilAttachment)) 143 | return false; 144 | if (stencilAttachment && rhs.stencilAttachment && (*stencilAttachment != *rhs.stencilAttachment)) 145 | return false; 146 | 147 | return true; 148 | } 149 | } // namespace Fwog::detail 150 | -------------------------------------------------------------------------------- /src/detail/SamplerCache.cpp: -------------------------------------------------------------------------------- 1 | #include "Fwog/detail/SamplerCache.h" 2 | #include "Fwog/detail/ApiToEnum.h" 3 | #include "Fwog/detail/Hash.h" 4 | #include "Fwog/detail/ContextState.h" 5 | #include FWOG_OPENGL_HEADER 6 | 7 | namespace Fwog::detail 8 | { 9 | Sampler SamplerCache::CreateOrGetCachedTextureSampler(const SamplerState& samplerState) 10 | { 11 | if (auto it = samplerCache_.find(samplerState); it != samplerCache_.end()) 12 | { 13 | return it->second; 14 | } 15 | 16 | uint32_t sampler{}; 17 | glCreateSamplers(1, &sampler); 18 | 19 | glSamplerParameteri(sampler, 20 | GL_TEXTURE_COMPARE_MODE, 21 | samplerState.compareEnable ? GL_COMPARE_REF_TO_TEXTURE : GL_NONE); 22 | 23 | glSamplerParameteri(sampler, GL_TEXTURE_COMPARE_FUNC, detail::CompareOpToGL(samplerState.compareOp)); 24 | 25 | GLint magFilter = samplerState.magFilter == Filter::LINEAR ? GL_LINEAR : GL_NEAREST; 26 | glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, magFilter); 27 | 28 | GLint minFilter{}; 29 | switch (samplerState.mipmapFilter) 30 | { 31 | case Filter::NONE: 32 | minFilter = samplerState.minFilter == Filter::LINEAR ? GL_LINEAR : GL_NEAREST; 33 | break; 34 | case Filter::NEAREST: 35 | minFilter = samplerState.minFilter == Filter::LINEAR ? GL_LINEAR_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_NEAREST; 36 | break; 37 | case Filter::LINEAR: 38 | minFilter = samplerState.minFilter == Filter::LINEAR ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR; 39 | break; 40 | default: FWOG_UNREACHABLE; 41 | } 42 | glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, minFilter); 43 | 44 | glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, detail::AddressModeToGL(samplerState.addressModeU)); 45 | glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, detail::AddressModeToGL(samplerState.addressModeV)); 46 | glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, detail::AddressModeToGL(samplerState.addressModeW)); 47 | 48 | // TODO: determine whether int white values should be 1 or 255 49 | switch (samplerState.borderColor) 50 | { 51 | case BorderColor::FLOAT_TRANSPARENT_BLACK: 52 | { 53 | constexpr GLfloat color[4]{0, 0, 0, 0}; 54 | glSamplerParameterfv(sampler, GL_TEXTURE_BORDER_COLOR, color); 55 | break; 56 | } 57 | case BorderColor::INT_TRANSPARENT_BLACK: 58 | { 59 | constexpr GLint color[4]{0, 0, 0, 0}; 60 | glSamplerParameteriv(sampler, GL_TEXTURE_BORDER_COLOR, color); 61 | break; 62 | } 63 | case BorderColor::FLOAT_OPAQUE_BLACK: 64 | { 65 | constexpr GLfloat color[4]{0, 0, 0, 1}; 66 | glSamplerParameterfv(sampler, GL_TEXTURE_BORDER_COLOR, color); 67 | break; 68 | } 69 | case BorderColor::INT_OPAQUE_BLACK: 70 | { 71 | // constexpr GLint color[4]{ 0, 0, 0, 255 }; 72 | constexpr GLint color[4]{0, 0, 0, 1}; 73 | glSamplerParameteriv(sampler, GL_TEXTURE_BORDER_COLOR, color); 74 | break; 75 | } 76 | case BorderColor::FLOAT_OPAQUE_WHITE: 77 | { 78 | constexpr GLfloat color[4]{1, 1, 1, 1}; 79 | glSamplerParameterfv(sampler, GL_TEXTURE_BORDER_COLOR, color); 80 | break; 81 | } 82 | case BorderColor::INT_OPAQUE_WHITE: 83 | { 84 | // constexpr GLint color[4]{ 255, 255, 255, 255 }; 85 | constexpr GLint color[4]{1, 1, 1, 1}; 86 | glSamplerParameteriv(sampler, GL_TEXTURE_BORDER_COLOR, color); 87 | break; 88 | } 89 | default: FWOG_UNREACHABLE; break; 90 | } 91 | 92 | glSamplerParameterf(sampler, 93 | GL_TEXTURE_MAX_ANISOTROPY, 94 | static_cast(detail::SampleCountToGL(samplerState.anisotropy))); 95 | 96 | glSamplerParameterf(sampler, GL_TEXTURE_LOD_BIAS, samplerState.lodBias); 97 | 98 | glSamplerParameterf(sampler, GL_TEXTURE_MIN_LOD, samplerState.minLod); 99 | 100 | glSamplerParameterf(sampler, GL_TEXTURE_MAX_LOD, samplerState.maxLod); 101 | 102 | detail::InvokeVerboseMessageCallback("Created sampler with handle ", sampler); 103 | 104 | return samplerCache_.insert({samplerState, Sampler(sampler)}).first->second; 105 | } 106 | 107 | size_t SamplerCache::Size() const 108 | { 109 | return samplerCache_.size(); 110 | } 111 | 112 | void SamplerCache::Clear() 113 | { 114 | for (const auto& [_, sampler] : samplerCache_) 115 | { 116 | detail::InvokeVerboseMessageCallback("Destroyed sampler with handle ", sampler.id_); 117 | glDeleteSamplers(1, &sampler.id_); 118 | } 119 | 120 | samplerCache_.clear(); 121 | } 122 | } // namespace Fwog::detail 123 | 124 | std::size_t std::hash::operator()(const Fwog::SamplerState& k) const noexcept 125 | { 126 | auto rtup = std::make_tuple(k.minFilter, 127 | k.magFilter, 128 | k.mipmapFilter, 129 | k.addressModeU, 130 | k.addressModeV, 131 | k.addressModeW, 132 | k.borderColor, 133 | k.anisotropy, 134 | k.compareEnable, 135 | k.compareOp, 136 | k.lodBias, 137 | k.minLod, 138 | k.maxLod); 139 | return Fwog::detail::hashing::hash{}(rtup); 140 | } 141 | -------------------------------------------------------------------------------- /src/detail/ShaderCPP.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #if FWOG_VCC_ENABLE 11 | extern "C" 12 | { 13 | namespace shady 14 | { 15 | #include 16 | extern void set_log_level(int); 17 | } // namespace shady 18 | } 19 | #endif 20 | 21 | namespace Fwog::detail 22 | { 23 | namespace 24 | { 25 | // Helper for dealing with unsafe APIs 26 | template 27 | class Defer 28 | { 29 | public: 30 | constexpr Defer(Fn&& f) noexcept : f_(std::move(f)) {} 31 | constexpr Defer(const Fn& f) : f_(f) {} 32 | constexpr ~Defer() 33 | { 34 | if (!dismissed_) 35 | f_(); 36 | } 37 | 38 | constexpr void Cancel() noexcept 39 | { 40 | dismissed_ = true; 41 | } 42 | 43 | Defer(const Defer&) = delete; 44 | Defer(Defer&&) = delete; 45 | Defer& operator=(const Defer&) = delete; 46 | Defer& operator=(Defer&&) = delete; 47 | 48 | private: 49 | bool dismissed_{false}; 50 | Fn f_; 51 | }; 52 | 53 | std::string LoadFile(const std::filesystem::path& path) 54 | { 55 | std::ifstream file{path}; 56 | return {std::istreambuf_iterator(file), std::istreambuf_iterator()}; 57 | } 58 | } 59 | 60 | std::string CompileShaderCppToGlsl(const std::filesystem::path& path) 61 | { 62 | auto compilerConfig = shady::default_compiler_config(); 63 | compilerConfig.specialization.entry_point = "main"; 64 | 65 | auto tempPath = std::filesystem::temp_directory_path() / (path.filename().string() + ".ll"); 66 | 67 | // Since processes created with popen/system don't inherit our environment (and therefore the working directory), we 68 | // use an absolute path. This way, both absolute and relative paths should work when calling this function. 69 | auto absolutePath = std::filesystem::current_path() / path.string(); 70 | 71 | auto args = std::stringstream(); 72 | args << "clang++ -std=c++20 -c -emit-llvm -S -g -O0 -ffreestanding -Wno-main-return-type -Xclang " 73 | "-fpreserve-vec3-type --target=spir64-unknown-unknown "; 74 | args << "-I \"" FWOG_VCC_INCLUDE_DIR "\" "; 75 | args << "-D__SHADY__=1 -o " << tempPath.generic_string() << " " << absolutePath; 76 | 77 | if (auto err = std::system(args.str().c_str())) 78 | { 79 | throw Fwog::ShaderCompilationException("Clang error"); 80 | } 81 | 82 | auto llvm_ir = LoadFile(tempPath.generic_string()); 83 | 84 | auto targetConfig = shady::default_target_config(); 85 | auto arenaConfig = shady::default_arena_config(&targetConfig); 86 | //arenaConfig.address_spaces[shady::AsGlobal].allowed = false; 87 | auto arena = shady::new_ir_arena(arenaConfig); 88 | auto d1 = Defer([=] { shady::destroy_ir_arena(arena); }); 89 | auto module = shady::new_module(arena, path.string().c_str()); 90 | if (auto err = shady::driver_load_source_file(&compilerConfig, 91 | shady::SourceLanguage::SrcLLVM, 92 | llvm_ir.size(), 93 | llvm_ir.c_str(), 94 | nullptr, 95 | &module)) 96 | { 97 | throw Fwog::ShaderCompilationException("Shady driver error"); 98 | } 99 | 100 | if (auto err = shady::run_compiler_passes(&compilerConfig, &module)) 101 | { 102 | throw Fwog::ShaderCompilationException("Shady compiler error"); 103 | } 104 | 105 | const auto emitterConfig = shady::CEmitterConfig{ 106 | .dialect = shady::CDialect_GLSL, 107 | .explicitly_sized_types = false, 108 | .allow_compound_literals = false, 109 | .decay_unsized_arrays = false, 110 | }; 111 | 112 | //shady::set_log_level(0); // Uncomment to dump IR 113 | auto outputSize = size_t{}; 114 | char* outputBuffer = {}; 115 | shady::emit_c(compilerConfig, emitterConfig, module, &outputSize, &outputBuffer, nullptr); 116 | auto d2 = Defer([=] { shady::free_output(outputBuffer); }); 117 | 118 | return {outputBuffer, outputSize}; 119 | } 120 | 121 | std::string CompileShaderCppToGlsl(std::string_view sourceCPP) 122 | { 123 | static constexpr auto tempSourceName = "fwog_temp_shader_source.cpp"; 124 | auto tempPath = std::filesystem::temp_directory_path() / tempSourceName; 125 | { 126 | auto file = std::ofstream(tempPath); 127 | file << sourceCPP; 128 | } 129 | return CompileShaderCppToGlsl(tempPath); 130 | } 131 | } // namespace Fwog::detail -------------------------------------------------------------------------------- /src/detail/ShaderGLSL.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include FWOG_OPENGL_HEADER 5 | 6 | namespace Fwog::detail 7 | { 8 | uint32_t CompileShaderGLSL(PipelineStage stage, std::string_view sourceGLSL) 9 | { 10 | const GLchar* strings = sourceGLSL.data(); 11 | 12 | uint32_t id = glCreateShader(PipelineStageToGL(stage)); 13 | glShaderSource(id, 1, &strings, nullptr); 14 | glCompileShader(id); 15 | return id; 16 | } 17 | } // namespace Fwog::detail -------------------------------------------------------------------------------- /src/detail/ShaderSPIRV.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include FWOG_OPENGL_HEADER 8 | 9 | namespace Fwog::detail 10 | { 11 | uint32_t CompileShaderSpirv(PipelineStage stage, const ShaderSpirvInfo& spirvInfo) 12 | { 13 | uint32_t id = glCreateShader(PipelineStageToGL(stage)); 14 | glShaderBinary(1, &id, GL_SHADER_BINARY_FORMAT_SPIR_V, (const GLuint*)spirvInfo.code.data(), (GLsizei)spirvInfo.code.size_bytes()); 15 | 16 | // Unzip specialization constants into two streams to feed to OpenGL 17 | auto indices = std::vector(spirvInfo.specializationConstants.size()); 18 | auto values = std::vector(spirvInfo.specializationConstants.size()); 19 | for (size_t i = 0; i < spirvInfo.specializationConstants.size(); i++) 20 | { 21 | indices[i] = spirvInfo.specializationConstants[i].index; 22 | values[i] = spirvInfo.specializationConstants[i].value; 23 | } 24 | 25 | glSpecializeShader(id, spirvInfo.entryPoint, (GLuint)spirvInfo.specializationConstants.size(), indices.data(), values.data()); 26 | return id; 27 | } 28 | } // namespace Fwog::detail 29 | -------------------------------------------------------------------------------- /src/detail/VertexArrayCache.cpp: -------------------------------------------------------------------------------- 1 | #include "Fwog/detail/VertexArrayCache.h" 2 | #include "Fwog/Pipeline.h" 3 | #include "Fwog/detail/ApiToEnum.h" 4 | #include "Fwog/detail/ContextState.h" 5 | #include "Fwog/detail/Hash.h" 6 | #include "Fwog/detail/PipelineManager.h" 7 | #include FWOG_OPENGL_HEADER 8 | 9 | namespace Fwog::detail 10 | { 11 | namespace 12 | { 13 | size_t VertexInputStateHash(const VertexInputStateOwning& k) 14 | { 15 | size_t hashVal{}; 16 | 17 | for (const auto& desc : k.vertexBindingDescriptions) 18 | { 19 | auto cctup = std::make_tuple(desc.location, desc.binding, desc.format, desc.offset); 20 | auto chashVal = Fwog::detail::hashing::hash{}(cctup); 21 | Fwog::detail::hashing::hash_combine(hashVal, chashVal); 22 | } 23 | 24 | return hashVal; 25 | } 26 | } // namespace 27 | 28 | uint32_t VertexArrayCache::CreateOrGetCachedVertexArray(const VertexInputStateOwning& inputState) 29 | { 30 | auto inputHash = VertexInputStateHash(inputState); 31 | if (auto it = vertexArrayCache_.find(inputHash); it != vertexArrayCache_.end()) 32 | { 33 | return it->second; 34 | } 35 | 36 | uint32_t vao{}; 37 | glCreateVertexArrays(1, &vao); 38 | for (uint32_t i = 0; i < inputState.vertexBindingDescriptions.size(); i++) 39 | { 40 | const auto& desc = inputState.vertexBindingDescriptions[i]; 41 | glEnableVertexArrayAttrib(vao, desc.location); 42 | glVertexArrayAttribBinding(vao, desc.location, desc.binding); 43 | 44 | auto type = detail::FormatToTypeGL(desc.format); 45 | auto size = detail::FormatToSizeGL(desc.format); 46 | auto normalized = detail::IsFormatNormalizedGL(desc.format); 47 | auto internalType = detail::FormatToFormatClass(desc.format); 48 | switch (internalType) 49 | { 50 | case detail::GlFormatClass::FLOAT: glVertexArrayAttribFormat(vao, desc.location, size, type, normalized, desc.offset); break; 51 | case detail::GlFormatClass::INT: glVertexArrayAttribIFormat(vao, desc.location, size, type, desc.offset); break; 52 | case detail::GlFormatClass::LONG: glVertexArrayAttribLFormat(vao, desc.location, size, type, desc.offset); break; 53 | default: FWOG_UNREACHABLE; 54 | } 55 | } 56 | 57 | detail::InvokeVerboseMessageCallback("Created vertex array with handle ", vao); 58 | 59 | return vertexArrayCache_.insert({inputHash, vao}).first->second; 60 | } 61 | 62 | void VertexArrayCache::Clear() 63 | { 64 | for (auto [_, vao] : vertexArrayCache_) 65 | { 66 | detail::InvokeVerboseMessageCallback("Destroyed vertex array with handle ", vao); 67 | glDeleteVertexArrays(1, &vao); 68 | } 69 | 70 | vertexArrayCache_.clear(); 71 | } 72 | } // namespace Fwog::detail 73 | --------------------------------------------------------------------------------