master
MaNing 4 years ago
parent 6a8f19fb3e
commit b04bb1759e

18
.gitignore vendored

@ -0,0 +1,18 @@
# Output files
build*/*
cmake-*/*
doc/*
# Required libraries
*.dll
# Editor config
.idea/*
.vscode/*
# Internal notes
reference/*
# Debug output
*.il
*.isa

@ -0,0 +1,353 @@
# Project information
cmake_minimum_required(VERSION 3.20)
project(Minote
VERSION 0.0
DESCRIPTION "Modern action puzzle game"
LANGUAGES C CXX)
set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
set(CMAKE_POLICY_DEFAULT_CMP0117 NEW)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED)
set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED)
set(CMAKE_CXX_EXTENSIONS OFF)
# Build settings
add_compile_options("-march=x86-64;-mmmx;-msse;-msse2;-msse3;-mssse3;-msse4.1;-msse4.2;-mavx;-ffast-math")
add_compile_options("$<$<CONFIG:Debug,RelWithDebInfo>:-g;-Og>")
add_compile_options("$<$<CONFIG:RelWithDebInfo,Release>:-O3;-ffunction-sections;-Wl,--gc-sections>")
add_compile_options("$<$<CONFIG:Release>:-s>")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION "$<CONFIG:Release>")
if(WIN32)
add_compile_options("-mwindows")
endif()
set(BUILD_SHARED_LIBS OFF)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -static-libgcc -static-libstdc++")
# Main executable
add_executable(Minote
src/base/containers/hashmap.hpp
src/base/containers/vector.hpp
src/base/containers/string.hpp
src/base/containers/array.hpp
src/base/concepts.hpp
src/base/format.hpp
src/base/error.hpp
src/base/tween.hpp
src/base/types.hpp
src/base/math.hpp src/base/math.tpp
src/base/util.hpp
src/base/time.hpp
src/base/ease.hpp
src/base/log.hpp src/base/log.cpp
src/base/rng.hpp
src/base/id.hpp
src/tools/modelSchema.hpp
src/sys/window.hpp src/sys/window.cpp
src/sys/vulkan.hpp src/sys/vulkan.cpp
src/sys/system.hpp src/sys/system.cpp
src/gfx/resources/texture2dms.hpp src/gfx/resources/texture2dms.cpp
src/gfx/resources/texture3d.hpp src/gfx/resources/texture3d.cpp
src/gfx/resources/texture2d.hpp src/gfx/resources/texture2d.cpp
src/gfx/resources/cubemap.hpp src/gfx/resources/cubemap.cpp
src/gfx/resources/buffer.hpp src/gfx/resources/buffer.tpp
src/gfx/resources/pool.hpp
src/gfx/effects/cubeFilterCoeffs.hpp
src/gfx/effects/instanceList.hpp src/gfx/effects/instanceList.cpp
src/gfx/effects/quadBuffer.hpp src/gfx/effects/quadBuffer.cpp
src/gfx/effects/cubeFilter.hpp src/gfx/effects/cubeFilter.cpp
src/gfx/effects/visibility.hpp src/gfx/effects/visibility.cpp
src/gfx/effects/tonemap.hpp src/gfx/effects/tonemap.cpp
src/gfx/effects/bloom.hpp src/gfx/effects/bloom.cpp
src/gfx/effects/clear.hpp src/gfx/effects/clear.cpp
src/gfx/effects/bvh.hpp src/gfx/effects/bvh.cpp
src/gfx/effects/pbr.hpp src/gfx/effects/pbr.cpp
src/gfx/effects/sky.hpp src/gfx/effects/sky.cpp
src/gfx/effects/hiz.hpp src/gfx/effects/hiz.cpp
src/gfx/samplers.hpp
src/gfx/objects.hpp src/gfx/objects.cpp
src/gfx/models.hpp src/gfx/models.cpp
src/gfx/engine.hpp src/gfx/engine.cpp
src/gfx/camera.hpp src/gfx/camera.cpp
src/gfx/imgui.hpp src/gfx/imgui.cpp
src/gfx/world.hpp src/gfx/world.cpp
src/gfx/frame.hpp src/gfx/frame.cpp
src/gfx/util.hpp
#src/playstate.hpp src/playstate.cpp
src/assets.hpp src/assets.tpp src/assets.cpp
src/mapper.hpp src/mapper.cpp
src/config.hpp
src/game.hpp src/game.cpp
src/main.hpp src/main.cpp
src/mino.hpp src/mino.tpp)
target_precompile_headers(Minote PRIVATE
src/base/containers/hashmap.hpp
src/base/containers/vector.hpp
src/base/containers/string.hpp
src/base/containers/array.hpp
src/base/concepts.hpp
src/base/format.hpp
src/base/error.hpp
src/base/types.hpp
src/base/math.hpp
src/base/util.hpp
src/base/log.hpp
src/config.hpp)
target_include_directories(Minote PRIVATE src)
target_link_libraries(Minote PRIVATE winmm)
# Libraries
find_package(Vulkan REQUIRED)
include(FetchContent)
target_compile_definitions(Minote PRIVATE VK_NO_PROTOTYPES)
if(WIN32)
target_compile_definitions(Minote PRIVATE VK_USE_PLATFORM_WIN32_KHR)
else()
target_compile_definitions(Minote PRIVATE VK_USE_PLATFORM_XCB_KHR)
endif()
set(VOLK_PULL_IN_VULKAN OFF CACHE BOOL "")
FetchContent_Declare(volk
GIT_REPOSITORY https://github.com/zeux/volk
GIT_TAG 66169849935bd85f329c513dd01c28b0a02bfe33)
FetchContent_MakeAvailable(volk)
target_compile_definitions(volk PUBLIC VK_NO_PROTOTYPES)
target_include_directories(volk PUBLIC ${Vulkan_INCLUDE_DIRS})
target_link_libraries(Minote PRIVATE volk)
set(VUK_LINK_TO_LOADER OFF CACHE BOOL "")
set(VUK_USE_SHADERC OFF CACHE BOOL "")
FetchContent_Declare(vuk
GIT_REPOSITORY https://github.com/martty/vuk
GIT_TAG 4b842a633e3ccf1fea8af887991c96c62d2b4525)
FetchContent_MakeAvailable(vuk)
target_compile_definitions(vuk PUBLIC VUK_CUSTOM_VULKAN_HEADER="volk.h")
target_link_libraries(vuk PRIVATE volk)
target_link_libraries(Minote PRIVATE vuk)
FetchContent_Declare(vk-bootstrap
GIT_REPOSITORY https://github.com/charles-lunarg/vk-bootstrap
GIT_TAG 36eff8fa4222b2225e349a44dde0402f0f4a277b)
FetchContent_MakeAvailable(vk-bootstrap)
target_link_libraries(Minote PRIVATE vk-bootstrap)
FetchContent_Declare(pcg
GIT_REPOSITORY https://github.com/imneme/pcg-c-basic
GIT_TAG bc39cd76ac3d541e618606bcc6e1e5ba5e5e6aa3)
FetchContent_MakeAvailable(pcg)
add_library(pcg ${pcg_SOURCE_DIR}/pcg_basic.h ${pcg_SOURCE_DIR}/pcg_basic.c)
target_include_directories(pcg PUBLIC ${pcg_SOURCE_DIR})
target_link_libraries(Minote PRIVATE pcg)
set(OPT_DEF_LIBC ON CACHE BOOL "")
set(VIDEO_OPENGLES OFF CACHE BOOL "")
set(VIDEO_OPENGL OFF CACHE BOOL "")
set(SDL_DLOPEN OFF CACHE BOOL "")
set(RENDER_D3D OFF CACHE BOOL "")
set(DUMMYAUDIO OFF CACHE BOOL "")
set(DUMMYAUDIO OFF CACHE BOOL "")
set(DISKAUDIO OFF CACHE BOOL "")
set(DIRECTX OFF CACHE BOOL "")
set(WASAPI OFF CACHE BOOL "")
FetchContent_Declare(sdl
GIT_REPOSITORY https://github.com/libsdl-org/SDL
GIT_TAG release-2.0.14)
FetchContent_MakeAvailable(sdl)
target_link_libraries(Minote PRIVATE SDL2-static)
FetchContent_Declare(imgui
GIT_REPOSITORY https://github.com/ocornut/imgui
GIT_TAG 00d570e280533bca9cf6e5652d0f2eb131d032e7)
FetchContent_MakeAvailable(imgui)
add_library(imgui
${imgui_SOURCE_DIR}/imgui.h ${imgui_SOURCE_DIR}/imgui.cpp
${imgui_SOURCE_DIR}/imconfig.h ${imgui_SOURCE_DIR}/imgui_internal.h
${imgui_SOURCE_DIR}/imgui_draw.cpp ${imgui_SOURCE_DIR}/imgui_tables.cpp
${imgui_SOURCE_DIR}/imgui_widgets.cpp ${imgui_SOURCE_DIR}/imgui_demo.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_sdl.h ${imgui_SOURCE_DIR}/backends/imgui_impl_sdl.cpp)
target_compile_definitions(imgui PRIVATE IMGUI_DISABLE_WIN32_FUNCTIONS)
target_include_directories(imgui PUBLIC ${imgui_SOURCE_DIR})
target_link_libraries(imgui PRIVATE SDL2-static)
target_link_libraries(Minote PRIVATE imgui)
FetchContent_Declare(sqlite
URL https://www.sqlite.org/2021/sqlite-amalgamation-3350400.zip)
FetchContent_MakeAvailable(sqlite)
add_library(sqlite
${sqlite_SOURCE_DIR}/sqlite3.h ${sqlite_SOURCE_DIR}/sqlite3.c
${sqlite_SOURCE_DIR}/sqlite3ext.h)
target_include_directories(sqlite PUBLIC ${sqlite_SOURCE_DIR})
target_link_libraries(Minote PRIVATE sqlite)
add_executable(sqlite-shell
${sqlite_SOURCE_DIR}/shell.c)
target_link_libraries(sqlite-shell PRIVATE sqlite)
FetchContent_Declare(cgltf
GIT_REPOSITORY https://github.com/jkuhlmann/cgltf
GIT_TAG 1bdc84d3bb81fa69bcf71ed5cafe63e58df1448a)
FetchContent_MakeAvailable(cgltf)
add_library(cgltf INTERFACE ${cgltf_SOURCE_DIR}/cgltf.h ${cgltf_SOURCE_DIR}/cgltf_write.h)
target_include_directories(cgltf INTERFACE ${cgltf_SOURCE_DIR})
if(NOT TARGET robin_hood) # vuk already includes this
FetchContent_Declare(robin_hood
GIT_REPOSITORY https://github.com/martinus/robin-hood-hashing
GIT_TAG 3.11.3)
FetchContent_MakeAvailable(robin_hood)
endif()
target_link_libraries(Minote PRIVATE robin_hood)
FetchContent_Declare(itlib
GIT_REPOSITORY https://github.com/iboB/itlib
GIT_TAG v1.4.5)
FetchContent_MakeAvailable(itlib)
target_compile_definitions(itlib INTERFACE ITLIB_STATIC_VECTOR_ERROR_HANDLING=ITLIB_STATIC_VECTOR_ERROR_HANDLING_THROW)
target_compile_definitions(itlib INTERFACE ITLIB_SMALL_VECTOR_ERROR_HANDLING=ITLIB_SMALL_VECTOR_ERROR_HANDLING_THROW)
target_link_libraries(Minote PRIVATE itlib)
FetchContent_Declare(fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt
GIT_TAG 8.1.1)
FetchContent_MakeAvailable(fmt)
target_link_libraries(Minote PRIVATE fmt::fmt)
set(QUILL_FMT_EXTERNAL ON CACHE BOOL "")
FetchContent_Declare(quill
GIT_REPOSITORY https://github.com/Tearnote/quill
GIT_TAG 98b09cf8b824ea3f6c8d7ea93e9c37bb94709f9c)
FetchContent_MakeAvailable(quill)
target_link_libraries(quill PRIVATE fmt::fmt)
target_link_libraries(Minote PRIVATE quill::quill)
FetchContent_Declare(gcem
GIT_REPOSITORY https://github.com/kthohr/gcem
GIT_TAG 8d0e62d9c9c47dfad2cb2d800281908cf7ff16c6)
FetchContent_MakeAvailable(gcem)
target_link_libraries(Minote PRIVATE gcem)
FetchContent_Declare(mpack
URL https://github.com/ludocode/mpack/releases/download/v1.1/mpack-amalgamation-1.1.tar.gz)
FetchContent_MakeAvailable(mpack)
add_library(mpack
${mpack_SOURCE_DIR}/src/mpack/mpack.h ${mpack_SOURCE_DIR}/src/mpack/mpack.c)
# Force C++-style handling of inline functions. Fixes link-time error with GCC
set_property(SOURCE ${mpack_SOURCE_DIR}/src/mpack/mpack.c PROPERTY LANGUAGE CXX)
target_include_directories(mpack PUBLIC ${mpack_SOURCE_DIR}/src)
target_link_libraries(Minote PRIVATE mpack)
FetchContent_Declare(meshoptimizer
GIT_REPOSITORY https://github.com/zeux/meshoptimizer
GIT_TAG 70b6cc38a64eeb549567599418bc4ed3ebd607da)
FetchContent_MakeAvailable(meshoptimizer)
FetchContent_Declare(bvh
GIT_REPOSITORY https://github.com/madmann91/bvh
GIT_TAG 02749bca616260d2df1a8de69e76075b2c8a526b)
FetchContent_MakeAvailable(bvh)
target_link_libraries(Minote PRIVATE bvh)
# Shader compilation
set(SHADER_DIR_PREFIX src/glsl)
set(SHADER_SOURCES
instanceList/cullMeshlets.comp
instanceList/genIndices.comp
cubeFilter/post.comp
cubeFilter/pre.comp
visibility/worklist.comp
visibility/visbuf.vert visibility/visbuf.frag
bloom/downKaris.comp
bloom/down.comp
bloom/up.comp
quad/genBuffers.comp
quad/clusterize.comp
quad/resolve.comp
bvh/debugAABB.vert
bvh/debugAABB.frag
sky/genAerialPerspective.comp
sky/genMultiScattering.comp
sky/genTransmittance.comp
sky/genSunLuminance.comp
sky/drawCubemap.comp
sky/genSkyView.comp
sky/draw.comp
hiz/first.comp
hiz/mip.comp
tonemap.comp
imgui.vert imgui.frag
pbr.comp)
foreach(SHADER_PATH ${SHADER_SOURCES})
get_filename_component(SHADER_DIR ${SHADER_PATH} DIRECTORY)
set(SHADER_OUTPUT ${PROJECT_BINARY_DIR}/$<CONFIG>/generated/spv/${SHADER_PATH}.spv)
add_custom_command(
OUTPUT ${SHADER_OUTPUT}
COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/$<CONFIG>/generated/spv/${SHADER_DIR}
COMMAND $ENV{VULKAN_SDK}/Bin/glslc.exe --target-env=vulkan1.2 -mfmt=num $<$<CONFIG:Debug,RelWithDebInfo>:-g> -MD -MF ${SHADER_OUTPUT}.d -o ${SHADER_OUTPUT} ${PROJECT_SOURCE_DIR}/${SHADER_DIR_PREFIX}/${SHADER_PATH}
DEPENDS ${SHADER_DIR_PREFIX}/${SHADER_PATH}
DEPFILE ${SHADER_OUTPUT}.d
VERBATIM COMMAND_EXPAND_LISTS)
list(APPEND SHADER_OUTPUTS ${SHADER_OUTPUT})
endforeach()
add_custom_target(Compile_shaders DEPENDS ${SHADER_OUTPUTS})
target_include_directories(Minote PRIVATE ${PROJECT_BINARY_DIR}/$<CONFIG>/generated)
add_dependencies(Minote Compile_shaders)
# Asset pipeline
add_executable(Model_conv
src/tools/modelSchema.hpp
src/tools/modelConv.cpp)
target_include_directories(Model_conv PRIVATE src)
target_link_libraries(Model_conv PRIVATE meshoptimizer)
target_link_libraries(Model_conv PRIVATE quill::quill)
target_link_libraries(Model_conv PRIVATE itlib)
target_link_libraries(Model_conv PRIVATE cgltf)
target_link_libraries(Model_conv PRIVATE mpack)
target_link_libraries(Model_conv PRIVATE gcem)
set(ASSET_OUTPUT ${PROJECT_BINARY_DIR}/$<CONFIG>/assets.db)
add_custom_command(
OUTPUT ${ASSET_OUTPUT}
COMMAND sqlite-shell ${ASSET_OUTPUT} "VACUUM"
VERBATIM)
set(MODELS
models/block.glb
models/sphere.glb
models/testscene.glb)
add_custom_command(
OUTPUT ${ASSET_OUTPUT} APPEND
COMMAND sqlite-shell ${ASSET_OUTPUT} "CREATE TABLE IF NOT EXISTS models(name TEXT UNIQUE, data BLOB)"
VERBATIM)
foreach(MODEL_PATH ${MODELS})
get_filename_component(MODEL_NAME ${MODEL_PATH} NAME_WLE)
add_custom_command(
OUTPUT ${PROJECT_BINARY_DIR}/$<CONFIG>/models/${MODEL_NAME}.model
COMMAND Model_conv ${PROJECT_SOURCE_DIR}/${MODEL_PATH} ${PROJECT_BINARY_DIR}/$<CONFIG>/models/${MODEL_NAME}.model
DEPENDS ${PROJECT_SOURCE_DIR}/${MODEL_PATH}
DEPENDS Model_conv
VERBATIM COMMAND_EXPAND_LISTS)
add_custom_command(
OUTPUT ${ASSET_OUTPUT} APPEND
COMMAND sqlite-shell ${ASSET_OUTPUT} "INSERT OR REPLACE INTO models(name, data) VALUES('${MODEL_NAME}', readfile('${PROJECT_BINARY_DIR}/$<CONFIG>/models/${MODEL_NAME}.model'))"
DEPENDS ${PROJECT_BINARY_DIR}/$<CONFIG>/models/${MODEL_NAME}.model
DEPENDS sqlite-shell
VERBATIM COMMAND_EXPAND_LISTS)
endforeach()
add_custom_target(Package_assets DEPENDS ${ASSET_OUTPUT})
add_dependencies(Minote Package_assets)

@ -0,0 +1,13 @@
Tear♪ Speaks His Terms License
You are allowed to init and distribute derivative work under the following conditions:
1. You will release the derivative work's source code, and will do it under the same license.
2. You will include a copy of this license.
3. You will provide attribution to the original work on whatever page serves as the primary source of information about the derivative project, such as the project's website or the repository's README file.
4. You will not charge money for the work itself or any part of its functionality. A donation will not provide any functional benefit to the donator outside of a purely visual badge or role. The badge or role will not provide any sort of preferential treatment. In the name of fairness, the copyright holder(s) also guarantee to adhere to this rule.
This work contains both original code and code taken from other sources. Any code that is not original has its origin stated in a comment that precedes the code, and all licensing obligations of the original code are carried out to the best of the author's knowledge and ability. The terms of this license apply only to entirely original code; no attempt is made to re-license the derivative code.
An additional exception is made for snippets of code that are not comparable to the scope of the project as a whole. This, for example, would include code that implements a single rendering technique, or allocation scheme. Such snippets, taken verbatim or derived from, can be used under a permissive MIT license. The spirit of this clause is to encourage mutual knowledge sharing in the computer graphics and game development communities.
Except as stated in this license, the work is provided "AS IS". No other guarantees are provided or implied.

@ -1,2 +1,43 @@
# Minote
A work-in-progress experimental hobbyist renderer for personal videogame and
digital art projects.
More details about current state and future plans on
[Trello](https://trello.com/b/LsFEM6Vw/minote)!
## Goals
- Performant rendering of models with high instance counts
- Artifact-free shading and post-processing, approaching path-tracing quality
when possible
- Flexibility to rapidly iterate on interesting rendering techniques
- Compatibility with most of the modern gaming-grade hardware
- Portable code, open-source libraries and toolchain
## Non-goals
- Texture mapping
- Vertex animation
- Being an engine
## Building
Developed on Windows in a MSYS2 UCRT environment. Requires CMake and the Vulkan
SDK installed; no other external dependencies. Standard CMake build process,
the Ninja Multi-Config generator is recommended.
## Libraries used
- [`volk`](https://github.com/zeux/volk) (MIT)
- [`vuk`](https://github.com/martty/vuk) (MIT)
- [`vk-bootstrap`](https://github.com/charles-lunarg/vk-bootstrap) (MIT)
- [`PCG`](https://github.com/imneme/pcg-c-basic) (Apache-2.0)
- [`SDL`](https://github.com/libsdl-org/SDL) (Zlib)
- [`Dear ImGui`](https://github.com/ocornut/imgui) (MIT)
- [`SQLite`](https://www.sqlite.org/) (Public Domain)
- [`cgltf`](https://github.com/jkuhlmann/cgltf) (MIT)
- [`robin_hood`](https://github.com/martinus/robin-hood-hashing) (MIT)
- [`itlib`](https://github.com/iboB/itlib) (MIT)
- [`{fmt}`](https://github.com/fmtlib/fmt) (MIT)
- [`Quill`](https://github.com/odygrd/quill) (MIT)
- [`GCE-Math`](https://github.com/kthohr/gcem) (Apache-2.0)
- [`MPack`](https://github.com/ludocode/mpack) (MIT)
- [`meshoptimizer`](https://github.com/zeux/meshoptimizer) (MIT)
- [`bvh`](https://github.com/madmann91/bvh) (MIT)
- Smaller code snippets are attributed inline

Binary file not shown.

@ -0,0 +1,79 @@
SIL OPEN FONT LICENSE
=====================
Version 1.1 - 26 February 2007
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting — in part or in whole — any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
4) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

@ -0,0 +1,72 @@
[Jost*](http://indestructible-type.github.io/Jost.html)
========
![Jost* Picture](https://indestructibletype.com/assets/Jost-h.svg)
Jost* is an original font created by indestructible type*. It is inspired by
the 1920s German sans-serifs. This is version 3.5.
Jost* is designed and maintained by [Owen Earl](https://ewonrael.github.io/), who is the creator of the font foundry [indestructible type*](http://indestructible-type.github.io).
About
-----
During the 1920s there was a radical shift in German sans serif letterforms. A marriage between the
precise geometry of earlier German sans serifs with the clean legibility of the humanists coming out of
England, yielded a geometric sans serif that is so iconic that it still looks as futuristic and forward-
thinking as it did almost 100 years ago. Jost* is an ode to this era of type design. Instead of trying to
recreate it precisely, Jost* aims to capture the spirit, using the technologies of today.
Jost* has 9 weights, ranging from Hairline to Black. It can support many languages, includes stylistic
alternatives, and both tabular and proportional numbers. Using Jost* on the web is as easy as copying a
line of code into your webpage and includes automatic updates. As of version 3.0, Jost* supports OpenType
Variable Font technology that allows users to control the exact weight and italic of the font. Best of all
Jost* is still being actively developed meaning it will continue to improve and future updates may include
features requested by you! [Visit the website for more information!](http://indestructibletype.com/Jost)
![Jost* weights Picture](https://indestructibletype.com/Jost/weight.svg)
Changelog
---------
<b>2.0</b>
Rebuilt from the ground up.<br>
<b>2.1</b>
Added oe ligatures to support French, fixed some italics<br>
<b>2.2 </b>
Added alternative a, fixed some kerning, fixed overshoot in bolder weights, added thin version!<br>
<b>2.3 </b>
Fixed missing kerning information on Book, Medium, and Bold weights<br>
<b>3.0 </b>
Redesigned and rebuilt to work as an OpenType Variable Font. Added Hairline, Semi, and Heavy weights. Added tabular number alternatives. Improved language support. General fixing of things.<br>
<b>3.1 </b>
Fixed metadata and metric issues<br>
<b>3.2 </b>
Renamed font from "Renner\*" to "Jost\*" due to international intellectual property rights concerns. Fixed some kerning issues, added capital sharp S, added Polish glyphs. Minor design fixes.<br>
<b>3.3 </b>
Added optical compensation to italics. Added Czech support. Minor tweaks and fixes.<br>
<b>3.4 </b>
Added Cyrillic alphabet (Russian support). Fixed metadata issues and added Romanian support.<br>
<b>3.5 </b>
Added some math symbols. Improved letterforms. Improved GitHub repo. Added automated build.sh script.
Web Use
-------
To use on your webpage, simply put the following line of code into the `<head>` of the webpage you'd like to display Jost* and use `font-family: 'Jost';` in your css.<br>
`<link rel="stylesheet" href="https://indestructibletype.com/fonts/Jost.css" type="text/css" charset="utf-8" />`
Building the Variable Font.
---------------------------
Jost* is licensed under the [SIL open type license](http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), meaning that it is free to use and modify. However, because of the difficulty
associated with creating the OpenType Variable Font features, the compliled, functoning version of this font
requires a payment of $10 or more on the website. However, if you'd like to build this font yourself using
the source code, the follow the following instructions.
1. Download the complete source code, either by cloning this repository or downloading the .zip file.
2. Download and install:<br>
**fontmake** which can be found [here](https://github.com/googlei18n/fontmake)<br>
**gftools** which can be found [here](https://github.com/googlefonts/gftools)<br>
**ttfautohint** which can be found [here](https://www.freetype.org/ttfautohint/)<br>
3. Run the build.sh script located in the "scripts" folder. This should make both the variable and non-variable versions of the font.
Contact
-------
If you have questions or want to help out, send me and email at indestructibletype@gmail.com

@ -0,0 +1,174 @@
# Apache License
Version 2.0, January 2004<br />
[http://www.apache.org/licenses/](http://www.apache.org/licenses/)
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

@ -0,0 +1,31 @@
#include "assets.hpp"
#include "base/error.hpp"
#include "base/log.hpp"
namespace minote {
using namespace base;
Assets::Assets(string_view _path) {
m_path = string(_path);
if (auto result = sqlite3_open_v2(m_path.c_str(), &m_db, SQLITE_OPEN_READONLY, nullptr); result != SQLITE_OK) {
sqlite3_close(m_db);
throw runtime_error_fmt("Failed to open database {}: {}", m_path, sqlite3_errstr(result));
}
L_INFO("Opened assets file {}", m_path);
}
Assets::~Assets() {
if (auto result = sqlite3_close(m_db); result != SQLITE_OK)
L_WARN("Failed to close database {}: {}", m_path, sqlite3_errstr(result));
}
}

@ -0,0 +1,40 @@
#pragma once
#include <concepts>
#include <span>
#include "sqlite3.h"
#include "base/containers/string.hpp"
namespace minote {
using namespace base;
struct Assets {
// Open the sqlite database containing game assets. File remains open
// until this object is destroyed.
explicit Assets(string_view path);
~Assets();
// Iterate over all rows in the models table, and call the provided function
// on each model's data.
template<typename F>
requires std::invocable<F, string_view, std::span<char const>>
void loadModels(F func);
// Not moveable, not copyable
Assets(Assets const&) = delete;
auto operator=(Assets const&) -> Assets& = delete;
private:
static constexpr auto Models_n = "models";
sqlite3* m_db = nullptr;
string m_path;
};
}
#include "assets.tpp"

@ -0,0 +1,44 @@
#include "base/error.hpp"
#include "base/util.hpp"
#include "base/log.hpp"
namespace minote {
using namespace base;
template<typename F>
requires std::invocable<F, string_view, std::span<char const>>
void Assets::loadModels(F _func) {
auto modelsQueryStr = fmt::format("SELECT * FROM {}", Models_n);
auto modelsQuery = static_cast<sqlite3_stmt*>(nullptr);
if (auto result = sqlite3_prepare_v2(m_db, modelsQueryStr.c_str(), -1, &modelsQuery, nullptr); result != SQLITE_OK)
throw runtime_error_fmt("Failed to query database {}: {}", m_path, sqlite3_errstr(result));
defer { sqlite3_finalize(modelsQuery); };
if (sqlite3_column_count(modelsQuery) != 2)
throw runtime_error_fmt("Invalid number of columns in table {} in database {}", Models_n, m_path);
auto result = SQLITE_OK;
while (result = sqlite3_step(modelsQuery), result != SQLITE_DONE) {
if (result != SQLITE_ROW)
throw runtime_error_fmt("Failed to query database {}: {}", m_path, sqlite3_errstr(result));
if (sqlite3_column_type(modelsQuery, 0) != SQLITE_TEXT)
throw runtime_error_fmt("Invalid type in column 0 of table models in database {}", m_path);
if (sqlite3_column_type(modelsQuery, 1) != SQLITE_BLOB)
throw runtime_error_fmt("Invalid type in column 1 of table models in database {}", m_path);
auto name = reinterpret_cast<char const*>(sqlite3_column_text(modelsQuery, 0));
auto nameLen = sqlite3_column_bytes(modelsQuery, 0);
auto model = static_cast<char const*>(sqlite3_column_blob(modelsQuery, 1));
auto modelLen = sqlite3_column_bytes(modelsQuery, 1);
_func(string_view(name, nameLen), std::span(model, modelLen));
}
L_INFO("All models loaded from asset file {}", m_path);
}
}

@ -0,0 +1,33 @@
#pragma once
#include <type_traits>
#include <tuple>
namespace minote::base {
// Any enum or enum struct
template<typename T>
concept enum_type = std::is_enum_v<T>;
// Built-in type with defined arithmetic operations (+, -, *, /)
template<typename T>
concept arithmetic = std::is_arithmetic_v<T>;
using std::integral;
// Objects that can be safely copied with memcpy.
template<typename T>
concept trivially_copyable = std::is_trivially_copyable_v<T>;
// Constrain to a type that can be held within a tuple/variant/etc.
template <typename T, typename Tuple>
struct contains_type;
template <typename T, typename... Us>
struct contains_type<T, std::tuple<Us...>>: std::disjunction<std::is_same<T, Us>...> {};
template<typename T, typename U>
concept contains = contains_type<U, T>::value;
}

@ -0,0 +1,15 @@
#pragma once
#include <array>
#include "base/types.hpp"
namespace minote::base {
// Static array. Stored entirely on stack, with size provided at runtime.
// Elements are not initialized if value-initializable.
template<typename T, usize N>
using array = std::array<T, N>;
using std::to_array;
}

@ -0,0 +1,12 @@
#pragma once
#include <utility>
#include "robin_hood.h"
namespace minote::base {
// Unordered hash map. Pointers are stable.
template<typename Key, typename T>
using hashmap = robin_hood::unordered_node_map<Key, T>;
}

@ -0,0 +1,24 @@
#pragma once
#include <string_view>
#include <string>
namespace minote::base {
// Standard string, stored on stack by default and relocated to heap if large.
using std::string;
// Read-only view into a string, used for function arguments. Pass by value when
// used as function argument.
using std::string_view;
using std::to_string;
namespace literals {
// Import sv literal
using namespace std::string_view_literals;
}
}

@ -0,0 +1,23 @@
#pragma once
#include "itlib/static_vector.hpp"
#include "itlib/small_vector.hpp"
#include "itlib/pod_vector.hpp"
#include "base/types.hpp"
namespace minote::base {
// Static vector. Stored entirely on stack, throws if capacity is exceeded.
template<typename T, usize N>
using svector = itlib::static_vector<T, N>;
// Inlined vector. Stored on stack initially, switches to heap above N elements.
template<typename T, usize N = 16>
using ivector = itlib::small_vector<T, N>;
// POD vector. Only usable with POD types, but greatly optimized - raw memory
// operations can be used, and construction/destruction is avoided.
template<typename T>
using pvector = itlib::pod_vector<T>;
}

@ -0,0 +1,345 @@
#pragma once
#include "base/math.hpp"
namespace minote::base {
// Type matching all the below functions
template<floating_point T>
using EasingFunction = auto (*)(T) -> T;
// Modeled after the line y = x
template<floating_point T>
constexpr auto linearInterpolation(T p) -> T {
return p;
}
// Modeled after the parabola y = x^2
template<floating_point T>
constexpr auto quadraticEaseIn(T p) -> T {
return p * p;
}
// Modeled after the parabola y = -x^2 + 2x
template<floating_point T>
constexpr auto quadraticEaseOut(T p) -> T {
return -(p * (p - 2));
}
// Modeled after the piecewise quadratic
// y = (1/2)((2x)^2) ; [0, 0.5)
// y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1]
template<floating_point T>
constexpr auto quadraticEaseInOut(T p) -> T {
if (p < 0.5)
return 2 * p * p;
else
return (-2 * p * p) + (4 * p) - 1;
}
// Modeled after the cubic y = x^3
template<floating_point T>
constexpr auto cubicEaseIn(T p) -> T {
return p * p * p;
}
// Modeled after the cubic y = (x - 1)^3 + 1
template<floating_point T>
constexpr auto cubicEaseOut(T p) -> T {
T f = p - 1;
return f * f * f + 1;
}
// Modeled after the piecewise cubic
// y = (1/2)((2x)^3) ; [0, 0.5)
// y = (1/2)((2x-2)^3 + 2) ; [0.5, 1]
template<floating_point T>
constexpr auto cubicEaseInOut(T p) -> T {
if (p < 0.5) {
return 4 * p * p * p;
} else {
T f = (2 * p) - 2;
return 0.5 * f * f * f + 1;
}
}
// Modeled after the quartic x^4
template<floating_point T>
constexpr auto quarticEaseIn(T p) -> T {
return p * p * p * p;
}
// Modeled after the quartic y = 1 - (x - 1)^4
template<floating_point T>
constexpr auto quarticEaseOut(T p) -> T {
T f = p - 1;
return f * f * f * (1 - p) + 1;
}
// Modeled after the piecewise quartic
// y = (1/2)((2x)^4) ; [0, 0.5)
// y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1]
template<floating_point T>
constexpr auto quarticEaseInOut(T p) -> T {
if (p < 0.5) {
return 8 * p * p * p * p;
} else {
T f = p - 1;
return -8 * f * f * f * f + 1;
}
}
// Modeled after the quintic y = x^5
template<floating_point T>
constexpr auto quinticEaseIn(T p) -> T {
return p * p * p * p * p;
}
// Modeled after the quintic y = (x - 1)^5 + 1
template<floating_point T>
constexpr auto quinticEaseOut(T p) -> T {
T f = p - 1;
return f * f * f * f * f + 1;
}
// Modeled after the piecewise quintic
// y = (1/2)((2x)^5) ; [0, 0.5)
// y = (1/2)((2x-2)^5 + 2) ; [0.5, 1]
template<floating_point T>
constexpr auto quinticEaseInOut(T p) -> T {
if (p < 0.5) {
return 16 * p * p * p * p * p;
} else {
T f = (2 * p) - 2;
return 0.5 * f * f * f * f * f + 1;
}
}
// Modeled after quarter-cycle of sine wave
template<floating_point T>
constexpr auto sineEaseIn(T p) -> T {
return sin((p - 1) * Pi_v<T> / 2) + 1;
}
// Modeled after quarter-cycle of sine wave (different phase)
template<floating_point T>
constexpr auto sineEaseOut(T p) -> T {
return sin(p * Pi_v<T> / 2);
}
// Modeled after half sine wave
template<floating_point T>
constexpr auto sineEaseInOut(T p) -> T {
return 0.5 * (1 - cos(p * Pi_v<T>));
}
// Modeled after shifted quadrant IV of unit circle
template<floating_point T>
constexpr auto circularEaseIn(T p) -> T {
return 1 - sqrt(1 - (p * p));
}
// Modeled after shifted quadrant II of unit circle
template<floating_point T>
constexpr auto circularEaseOut(T p) -> T {
return sqrt((2 - p) * p);
}
// Modeled after the piecewise circular function
// y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5)
// y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1]
template<floating_point T>
constexpr auto circularEaseInOut(T p) -> T {
if (p < 0.5)
return 0.5 * (1 - sqrt(1 - 4 * (p * p)));
else
return 0.5 * (sqrt(-((2 * p) - 3) * ((2 * p) - 1)) + 1);
}
// Modeled after the exponential function y = 2^(10(x - 1))
template<floating_point T>
constexpr auto exponentialEaseIn(T p) -> T {
return (p == 0.0)? p : pow(2, 10 * (p - 1));
}
// Modeled after the exponential function y = -2^(-10x) + 1
template<floating_point T>
constexpr auto exponentialEaseOut(T p) -> T {
return (p == 1.0)? p : 1 - pow(2, -10 * p);
}
// Modeled after the piecewise exponential
// y = (1/2)2^(10(2x - 1)) ; [0,0.5)
// y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1]
template<floating_point T>
constexpr auto exponentialEaseInOut(T p) -> T {
if (p == 0.0 || p == 1.0)
return p;
if (p < 0.5)
return 0.5 * pow(2, (20 * p) - 10);
else
return -0.5 * pow(2, (-20 * p) + 10) + 1;
}
// Modeled after the damped sine wave y = sin(13pi/2*x)*pow(2, 10 * (x - 1))
template<floating_point T>
constexpr auto elasticEaseIn(T p) -> T {
return sin(13 * (Pi_v<T> / 2) * p) * pow(2, 10 * (p - 1));
}
// Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) + 1
template<floating_point T>
constexpr auto elasticEaseOut(T p) -> T {
return sin(-13 * (Pi_v<T> / 2) * (p + 1)) * pow(2, -10 * p) + 1;
}
// Modeled after the piecewise exponentially-damped sine wave:
// y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5)
// y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1]
template<floating_point T>
constexpr auto elasticEaseInOut(T p) -> T {
if (p < 0.5) {
return 0.5 * sin(13 * (Pi_v<T> / 2) * (2 * p)) *
pow(2, 10 * ((2 * p) - 1));
} else {
return 0.5 * (sin(-13 * (Pi_v<T> / 2) * ((2 * p - 1) + 1)) *
pow(2, -10 * (2 * p - 1)) + 2);
}
}
// Modeled after the overshooting cubic y = x^3-x*sin(x*pi)
template<floating_point T>
constexpr auto backEaseIn(T p) -> T {
return p * p * p - p * sin(p * Pi_v<T>);
}
// Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi))
template<floating_point T>
constexpr auto backEaseOut(T p) -> T {
T f = 1 - p;
return 1 - (f * f * f - f * sin(f * Pi_v<T>));
}
// Modeled after the piecewise overshooting cubic function:
// y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5)
// y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1]
template<floating_point T>
constexpr auto backEaseInOut(T p) -> T {
if (p < 0.5) {
T f = 2 * p;
return 0.5 * (f * f * f - f * sin(f * Pi_v<T>));
} else {
T f = (1 - (2 * p - 1));
return 0.5 * (1 - (f * f * f - f * sin(f * Pi_v<T>))) + 0.5;
}
}
template<floating_point T>
constexpr auto bounceEaseIn(T p) -> T {
return 1 - bounceEaseOut(1 - p);
}
template<floating_point T>
constexpr auto bounceEaseOut(T p) -> T {
if (p < 4 / 11.0)
return (121 * p * p) / 16.0;
else if (p < 8 / 11.0)
return (363 / 40.0 * p * p) - (99 / 10.0 * p) + 17 / 5.0;
else if (p < 9 / 10.0)
return (4356 / 361.0 * p * p) - (35442 / 1805.0 * p) + 16061 / 1805.0;
else
return (54 / 5.0 * p * p) - (513 / 25.0 * p) + 268 / 25.0;
}
template<floating_point T>
constexpr auto bounceEaseInOut(T p) -> T {
if (p < 0.5)
return 0.5 * bounceEaseIn(p * 2);
else
return 0.5 * bounceEaseOut(p * 2 - 1) + 0.5;
}
}

@ -0,0 +1,31 @@
#pragma once
#include <string_view> // base/containers/string.hpp would cause a circular dependency
#include <functional>
#include <stdexcept>
#include <utility>
#include "base/format.hpp"
namespace minote::base {
using std::runtime_error;
using std::logic_error;
template<typename Err, typename... Args>
inline auto typed_error_fmt(fmt::format_string<Args...> fmt, Args&&... args) -> Err {
return Err(format(fmt, std::forward<Args>(args)...));
}
template<typename... Args>
inline auto runtime_error_fmt(fmt::format_string<Args...> fmt, Args&&... args) {
return typed_error_fmt<runtime_error>(fmt, std::forward<Args>(args)...);
};
template<typename... Args>
inline auto logic_error_fmt(fmt::format_string<Args...> fmt, Args&&... args) {
return typed_error_fmt<logic_error>(fmt, std::forward<Args>(args)...);
};
}

@ -0,0 +1,9 @@
#pragma once
#include "fmt/core.h"
namespace minote::base {
using fmt::format;
}

@ -0,0 +1,70 @@
#pragma once
#include <type_traits>
#include "base/containers/string.hpp"
#include "base/types.hpp"
namespace minote::base {
// Resource ID. Created from a string, hashed at compile-time if possible.
struct ID {
// Trivial default constructor
constexpr ID() = default;
// Hash string with FNV-1a
explicit constexpr ID(string_view str): m_id(Basis) {
for (auto ch: str) {
m_id ^= ch;
m_id *= Prime;
}
}
// Zero-initializing constructor
static constexpr auto make_default() -> ID { return ID(0u); }
constexpr auto operator==(ID const&) const -> bool = default;
constexpr auto operator!=(ID const&) const -> bool = default;
constexpr auto operator+() const { return m_id; }
private:
static constexpr auto Prime = 16777619u;
static constexpr auto Basis = 2166136261u;
u32 m_id;
// Construct with specified hash
explicit constexpr ID(u32 id): m_id(id) {}
};
static_assert(std::is_trivially_constructible_v<ID>);
namespace literals {
// Guaranteed-constexpr string literal hash
consteval auto operator ""_id(char const* str, usize len) {
return ID(string_view(str, len));
}
}
}
namespace std {
// Providing std::hash<ID>. The ID is already hashed, so this is an identity function
template<>
struct hash<minote::base::ID> {
constexpr auto operator()(minote::base::ID const& id) const -> std::size_t { return +id; }
};
}

@ -0,0 +1,35 @@
#include "base/log.hpp"
#include <string> // std::string needed by quill API
#include "quill/Quill.h"
#include "base/containers/string.hpp"
#include "base/containers/array.hpp"
#include "base/util.hpp"
namespace minote::base {
using namespace literals;
void Log::init(string_view _filename, quill::LogLevel _level) {
quill::enable_console_colours();
quill::start(true);
auto file = quill::file_handler(std::string(_filename), "w");
auto console = quill::stdout_handler();
file->set_pattern(
QUILL_STRING("%(ascii_time) [%(level_name)] %(message)"),
"%H:%M:%S.%Qns",
quill::Timezone::LocalTime);
console->set_pattern(
QUILL_STRING("%(ascii_time) %(message)"),
"%H:%M:%S",
quill::Timezone::LocalTime);
m_logger = quill::create_logger("main", {file, console});
m_logger->set_log_level(_level);
}
}

@ -0,0 +1,35 @@
#pragma once
#include "base/containers/string.hpp"
#include "quill/Quill.h"
namespace minote::base {
// Logging facility. Basic set of features, but threaded and non-blocking - it's
// safe to log even very rapid streams of messages with no performance penalty.
// Features color in console and {fmt} formatting.
struct Log {
// Start logging to console and specified logfile. All messages below
// the provided log level will be dropped.
static void init(string_view filename, quill::LogLevel level);
// Do not call directly; for use with macros below
static auto logger() -> quill::Logger* { return m_logger; }
private:
inline static quill::Logger* m_logger;
};
// Logging functions, fmtlib formatting is supported.
#define L_TRACE(fmt, ...) LOG_TRACE_L1(Log::logger(), fmt, ##__VA_ARGS__)
#define L_DEBUG(fmt, ...) LOG_DEBUG(Log::logger(), fmt, ##__VA_ARGS__)
#define L_INFO(fmt, ...) LOG_INFO(Log::logger(), fmt, ##__VA_ARGS__)
#define L_WARN(fmt, ...) LOG_WARNING(Log::logger(), fmt, ##__VA_ARGS__)
#define L_ERROR(fmt, ...) LOG_ERROR(Log::logger(), fmt, ##__VA_ARGS__)
#define L_CRIT(fmt, ...) LOG_CRITICAL(Log::logger(), fmt, ##__VA_ARGS__)
}

@ -0,0 +1,499 @@
#pragma once
#include <initializer_list>
#include <type_traits>
#include <concepts>
#include <numbers>
#include "gcem.hpp"
#include "base/containers/array.hpp"
#include "base/concepts.hpp"
#include "base/types.hpp"
namespace minote::base {
//=== Constants
template<std::floating_point T>
constexpr auto Pi_v = std::numbers::pi_v<T>;
constexpr auto Pi = Pi_v<f32>;
template<std::floating_point T>
constexpr auto Tau_v = Pi_v<T> * T(2.0);
constexpr auto Tau = Tau_v<f32>;
//=== Scalar operations
using gcem::min;
using gcem::max;
using gcem::abs;
using gcem::round;
using gcem::floor;
using gcem::ceil;
using gcem::sgn;
using gcem::pow;
using gcem::sqrt;
using gcem::log2;
using gcem::sin;
using gcem::cos;
using gcem::tan;
// Degrees to radians conversion
template<arithmetic T, std::floating_point Prec = f32>
constexpr auto radians(T deg) -> Prec { return Prec(deg) * Tau_v<Prec> / Prec(360); }
// True modulo operation (as opposed to remainder, which is operator% in C++.)
// The result is always positive and does not flip direction at zero.
// Example:
// 5 mod 4 = 1
// 4 mod 4 = 0
// 3 mod 4 = 3
// 2 mod 4 = 2
// 1 mod 4 = 1
// 0 mod 4 = 0
// -1 mod 4 = 3
// -2 mod 4 = 2
// -3 mod 4 = 1
// -4 mod 4 = 0
// -5 mod 4 = 3
template<std::integral T>
constexpr auto tmod(T num, T div) { return num % div + (num % div < 0) * div; }
// Classic GLSL-style scalar clamp
template<arithmetic T>
constexpr auto clamp(T val, T vmin, T vmax) -> T { return max(vmin, min(val, vmax)); }
//=== Compound types
// Generic math vector, of any dimension between 2 to 4 and any underlying type
template<usize Dim, arithmetic T>
struct vec {
static_assert(Dim >= 2 && Dim <= 4, "Vectors need to have 2, 3 or 4 components");
//=== Creation
// Uninitialized init
constexpr vec() = default;
// Fill the vector with copies of the value
explicit constexpr vec(T fillVal) { fill(fillVal); }
// Create the vector with provided component values
constexpr vec(std::initializer_list<T>);
//=== Conversions
// Type cast
template<arithmetic U>
requires (!std::same_as<T, U>)
explicit constexpr vec(vec<Dim, U> const&);
// Dimension downcast
template<usize N>
requires (N > Dim)
explicit constexpr vec(vec<N, T> const&);
// Dimension upcast
template<usize N>
requires (N < Dim)
constexpr vec(vec<N, T> const&, T fill);
//=== Member access
[[nodiscard]]
constexpr auto at(usize n) -> T& { return m_arr[n]; }
[[nodiscard]]
constexpr auto at(usize n) const -> T { return m_arr[n]; }
[[nodiscard]]
constexpr auto operator[](usize n) -> T& { return at(n); }
[[nodiscard]]
constexpr auto operator[](usize n) const -> T { return at(n); }
[[nodiscard]]
constexpr auto x() -> T& { static_assert(Dim >= 1); return m_arr[0]; }
[[nodiscard]]
constexpr auto x() const -> T { static_assert(Dim >= 1); return m_arr[0]; }
[[nodiscard]]
constexpr auto y() -> T& { static_assert(Dim >= 2); return m_arr[1]; }
[[nodiscard]]
constexpr auto y() const -> T { static_assert(Dim >= 2); return m_arr[1]; }
[[nodiscard]]
constexpr auto z() -> T& { static_assert(Dim >= 3); return m_arr[2]; }
[[nodiscard]]
constexpr auto z() const -> T { static_assert(Dim >= 3); return m_arr[2]; }
[[nodiscard]]
constexpr auto w() -> T& { static_assert(Dim >= 4); return m_arr[3]; }
[[nodiscard]]
constexpr auto w() const -> T { static_assert(Dim >= 4); return m_arr[3]; }
[[nodiscard]]
constexpr auto r() -> T& { return x(); }
[[nodiscard]]
constexpr auto r() const -> T { return x(); }
[[nodiscard]]
constexpr auto g() -> T& { return y(); }
[[nodiscard]]
constexpr auto g() const -> T { return y(); }
[[nodiscard]]
constexpr auto b() -> T& { return z(); }
[[nodiscard]]
constexpr auto b() const -> T { return z(); }
[[nodiscard]]
constexpr auto a() -> T& { return w(); }
[[nodiscard]]
constexpr auto a() const -> T { return w(); }
[[nodiscard]]
constexpr auto u() -> T& { return x(); }
[[nodiscard]]
constexpr auto u() const -> T { return x(); }
[[nodiscard]]
constexpr auto v() -> T& { return y(); }
[[nodiscard]]
constexpr auto v() const -> T { return y(); }
[[nodiscard]]
constexpr auto s() -> T& { return z(); }
[[nodiscard]]
constexpr auto s() const -> T { return z(); }
[[nodiscard]]
constexpr auto t() -> T& { return w(); }
[[nodiscard]]
constexpr auto t() const -> T { return w(); }
constexpr void fill(T val) { m_arr.fill(val); }
//=== Vector operations
// Component-wise arithmetic
constexpr auto operator+=(vec<Dim, T> const&) -> vec<Dim, T>&;
constexpr auto operator-=(vec<Dim, T> const&) -> vec<Dim, T>&;
constexpr auto operator*=(vec<Dim, T> const&) -> vec<Dim, T>&; // Component-wise
constexpr auto operator/=(vec<Dim, T> const&) -> vec<Dim, T>&;
constexpr auto operator%=(vec<Dim, T> const&) -> vec<Dim, T>&;
// Scalar arithmetic
constexpr auto operator*=(T) -> vec<Dim, T>&;
constexpr auto operator/=(T) -> vec<Dim, T>&;
constexpr auto operator%=(T) -> vec<Dim, T>&;
constexpr auto operator<<=(T) -> vec<Dim, T>&;
constexpr auto operator>>=(T) -> vec<Dim, T>&;
private:
array<T, Dim> m_arr;
};
// Binary vector operations
template<usize Dim, arithmetic T>
constexpr auto operator+(vec<Dim, T> const&, vec<Dim, T> const&) -> vec<Dim, T>;
template<usize Dim, arithmetic T>
constexpr auto operator-(vec<Dim, T> const&, vec<Dim, T> const&) -> vec<Dim, T>;
template<usize Dim, arithmetic T>
constexpr auto operator*(vec<Dim, T> const&, vec<Dim, T> const&) -> vec<Dim, T>; // Component-wise
template<usize Dim, arithmetic T>
constexpr auto operator/(vec<Dim, T> const&, vec<Dim, T> const&) -> vec<Dim, T>;
template<usize Dim, integral T>
constexpr auto operator%(vec<Dim, T> const&, vec<Dim, T> const&) -> vec<Dim, T>;
template<usize Dim, arithmetic T>
constexpr auto operator==(vec<Dim, T> const&, vec<Dim, T> const&) -> bool;
template<usize Dim, arithmetic T>
constexpr auto min(vec<Dim, T> const&, vec<Dim, T> const&) -> vec<Dim, T>;
template<usize Dim, arithmetic T>
constexpr auto max(vec<Dim, T> const&, vec<Dim, T> const&) -> vec<Dim, T>;
template<usize Dim, arithmetic T>
constexpr auto dot(vec<Dim, T> const&, vec<Dim, T> const&) -> T;
template<arithmetic T>
constexpr auto cross(vec<3, T> const&, vec<3, T> const&) -> vec<3, T>;
// Binary scalar operations
template<usize Dim, arithmetic T>
constexpr auto operator*(vec<Dim, T> const&, T) -> vec<Dim, T>;
template<usize Dim, arithmetic T>
constexpr auto operator*(T left, vec<Dim, T> const& right) -> vec<Dim, T> { return right * left; }
template<usize Dim, arithmetic T>
constexpr auto operator/(vec<Dim, T> const&, T) -> vec<Dim, T>;
template<usize Dim, integral T>
constexpr auto operator%(vec<Dim, T> const&, T) -> vec<Dim, T>;
template<usize Dim, integral T>
constexpr auto operator<<(vec<Dim, T> const&, T) -> vec<Dim, T>;
template<usize Dim, integral T>
constexpr auto operator>>(vec<Dim, T> const&, T) -> vec<Dim, T>;
// Unary vector operations
// Component-wise absolute value
template<usize Dim, std::floating_point T>
constexpr auto abs(vec<Dim, T> const&) -> vec<Dim, T>;
// Vector length as Euclidean distance
template<usize Dim, std::floating_point T>
constexpr auto length(vec<Dim, T> const& v) -> T {
static_assert(std::is_floating_point_v<T>);
return sqrt(length2(v));
}
// Square of vector length (faster to compute than length)
template<usize Dim, std::floating_point T>
constexpr auto length2(vec<Dim, T> const& v) -> T { return dot(v, v); }
// true if vector has the length of 1 (within reasonable epsilon)
template<usize Dim, std::floating_point T>
constexpr auto isUnit(vec<Dim, T> const& v) -> bool { return (abs(length2(v) - 1) < (1.0 / 16.0)); }
// Constructs a vector in the same direction but length 1
template<usize Dim, std::floating_point T>
constexpr auto normalize(vec<Dim, T> const&) -> vec<Dim, T>;
//=== GLSL-like vector aliases
using vec2 = vec<2, f32>;
using vec3 = vec<3, f32>;
using vec4 = vec<4, f32>;
using ivec2 = vec<2, i32>;
using ivec3 = vec<3, i32>;
using ivec4 = vec<4, i32>;
using uvec2 = vec<2, u32>;
using uvec3 = vec<3, u32>;
using uvec4 = vec<4, u32>;
using u8vec2 = vec<2, u8>;
using u8vec3 = vec<3, u8>;
using u8vec4 = vec<4, u8>;
using u16vec2 = vec<2, u16>;
using u16vec3 = vec<3, u16>;
using u16vec4 = vec<4, u16>;
static_assert(std::is_trivially_constructible_v<vec2>);
static_assert(std::is_trivially_constructible_v<vec3>);
static_assert(std::is_trivially_constructible_v<vec4>);
static_assert(std::is_trivially_constructible_v<ivec2>);
static_assert(std::is_trivially_constructible_v<ivec3>);
static_assert(std::is_trivially_constructible_v<ivec4>);
static_assert(std::is_trivially_constructible_v<uvec2>);
static_assert(std::is_trivially_constructible_v<uvec3>);
static_assert(std::is_trivially_constructible_v<uvec4>);
static_assert(std::is_trivially_constructible_v<u8vec2>);
static_assert(std::is_trivially_constructible_v<u8vec3>);
static_assert(std::is_trivially_constructible_v<u8vec4>);
static_assert(std::is_trivially_constructible_v<u16vec2>);
static_assert(std::is_trivially_constructible_v<u16vec3>);
// Quaternion, equivalent to a vec4 but with unique operations available.
// Main purpose is representing rotations. Data layout is {w, x, y, z}.
template<std::floating_point Prec = f32>
struct qua {
//=== Creation
// Uninitialized init
constexpr qua() = default;
// Create the quaternion with provided {w, x, y, z} values
constexpr qua(std::initializer_list<Prec>);
// Convert a position vector into a quaternion
template<usize N>
requires (N == 3 || N == 4)
constexpr qua(vec<N, Prec> const&);
// Create a unit quaternion that represents no rotation
static constexpr auto identity() -> qua<Prec> { return qua<Prec>{1, 0, 0, 0}; }
// Create a unit quaternion that represents a rotation around an arbitrary axis
static constexpr auto angleAxis(Prec angle, vec<3, Prec> axis) -> qua<Prec>;
//=== Conversion
// Type cast
template<arithmetic U>
requires (!std::same_as<Prec, U>)
explicit constexpr qua(qua<U> const&);
//=== Member access
constexpr auto at(usize n) -> Prec& { return m_arr[n]; }
constexpr auto at(usize n) const -> Prec { return m_arr[n]; }
constexpr auto operator[](usize n) -> Prec& { return at(n); }
constexpr auto operator[](usize n) const -> Prec { return at(n); }
constexpr auto w() -> Prec& { return m_arr[0]; }
constexpr auto w() const -> Prec { return m_arr[0]; }
constexpr auto x() -> Prec& { return m_arr[1]; }
constexpr auto x() const -> Prec { return m_arr[1]; }
constexpr auto y() -> Prec& { return m_arr[2]; }
constexpr auto y() const -> Prec { return m_arr[2]; }
constexpr auto z() -> Prec& { return m_arr[3]; }
constexpr auto z() const -> Prec { return m_arr[3]; }
private:
array<Prec, 4> m_arr;
};
// Binary quaternion operations
template<std::floating_point Prec>
constexpr auto operator*(qua<Prec> const&, qua<Prec> const&) -> qua<Prec>;
//=== Quaternion alias
using quat = qua<f32>;
static_assert(std::is_trivially_constructible_v<u16vec4>);
// Generic matrix type, of order 3 or 4, and any floating-point precision
template<usize Dim, std::floating_point Prec>
struct mat {
using col_t = vec<Dim, Prec>;
static_assert(Dim >= 3 && Dim <= 4, "Matrices need to have order 3x3 or 4x4");
//=== Creation
// Uninitialized init
constexpr mat() = default;
// Compose a matrix out of all component values, in column-major order
constexpr mat(std::initializer_list<Prec>);
// Compose a matrix out of column vectors
constexpr mat(std::initializer_list<col_t> list) { std::copy(list.begin(), list.end(), m_arr.begin()); }
// Create a matrix that is a no-op on multiplication
static constexpr auto identity() -> mat<Dim, Prec>;
// Classic translation, rotation and scale matrices for vector manipulation
static constexpr auto translate(vec<3, Prec> shift) -> mat<Dim, Prec>; // Cannot be mat3
static constexpr auto rotate(vec<3, Prec> axis, Prec angle) -> mat<Dim, Prec>;
static constexpr auto rotate(qua<Prec> quat) -> mat<Dim, Prec>;
static constexpr auto scale(vec<3, Prec> scale) -> mat<Dim, Prec>;
static constexpr auto scale(Prec scale) -> mat<Dim, Prec>;
//=== Conversion
// Type cast
template<arithmetic U>
requires (!std::same_as<Prec, U>)
explicit constexpr mat(mat<Dim, U> const&);
// Dimension cast
template<usize N>
requires (N != Dim)
explicit constexpr mat(mat<N, Prec> const&);
//=== Member access
constexpr auto at(usize x, usize y) -> Prec& { return m_arr[x][y]; }
constexpr auto at(usize x, usize y) const -> Prec { return m_arr[x][y]; }
constexpr auto operator[](usize x) -> col_t& { return m_arr[x]; }
constexpr auto operator[](usize x) const -> col_t const& { return m_arr[x]; }
constexpr auto fill(Prec val) { for (auto& col: m_arr) col.fill(val); }
//=== Operations
// Scalar arithmetic
constexpr auto operator*=(Prec) -> mat<Dim, Prec>&; // Component-wise
constexpr auto operator/=(Prec) -> mat<Dim, Prec>&; // Component-wise
private:
array<col_t, Dim> m_arr;
};
// Binary matrix operations
template<usize Dim, std::floating_point Prec>
constexpr auto operator*(mat<Dim, Prec> const&, mat<Dim, Prec> const&) -> mat<Dim, Prec>;
template<usize Dim, std::floating_point Prec>
constexpr auto operator*(mat<Dim, Prec> const&, vec<Dim, Prec> const&) -> vec<Dim, Prec>;
template<usize Dim, std::floating_point Prec>
constexpr auto operator*(mat<Dim, Prec> const&, Prec) -> mat<Dim, Prec>; // Component-wise
template<usize Dim, std::floating_point Prec>
constexpr auto operator*(Prec left, mat<Dim, Prec> const& right) -> mat<Dim, Prec> { return right * left; } // Component-wise
template<usize Dim, std::floating_point Prec>
constexpr auto operator/(mat<Dim, Prec> const&, Prec) -> mat<Dim, Prec>; // Component-wise
// Unary matrix operations
// Creates a matrix with rows transposed with columns
template<usize Dim, std::floating_point Prec>
constexpr auto transpose(mat<Dim, Prec> const&) -> mat<Dim, Prec>;
// Creates a matrix that results in identity when multiplied with the original (slow!)
template<usize Dim, std::floating_point Prec>
constexpr auto inverse(mat<Dim, Prec> const&) -> mat<Dim, Prec>;
// Specialized matrix generators
// Variant of lookAt matrix. Dir is a unit vector of the camera direction.
// Dir and Up are both required to be unit vectors
template<std::floating_point Prec = f32>
constexpr auto look(vec<3, Prec> pos, vec<3, Prec> dir, vec<3, Prec> up) -> mat<4, Prec>;
// Creates a perspective matrix. The matrix uses inverted infinite depth:
// 1.0 at zNear, 0.0 at infinity.
template<std::floating_point Prec = f32>
constexpr auto perspective(Prec vFov, Prec aspectRatio, Prec zNear) -> mat<4, Prec>;
//=== GLSL-like matrix aliases
using mat3 = mat<3, f32>;
using mat4 = mat<4, f32>;
static_assert(std::is_trivially_constructible_v<mat3>);
static_assert(std::is_trivially_constructible_v<mat4>);
//=== Conversion literals
namespace literals {
consteval auto operator""_cm(unsigned long long int val) -> f32 { return f64(val) * 0.000'001; }
consteval auto operator""_cm(long double val) -> f32 { return f64(val) * 0.000'001; }
consteval auto operator""_m(unsigned long long int val) -> f32 { return f64(val) * 0.001; }
consteval auto operator""_m(long double val) -> f32 { return f64(val) * 0.001; }
consteval auto operator""_km(unsigned long long int val) -> f32 { return val; }
consteval auto operator""_km(long double val) -> f32 { return val; }
consteval auto operator""_deg(unsigned long long int val) -> f32 { return radians(f64(val)); }
consteval auto operator""_deg(long double val) -> f32 { return radians(val); }
}
}
#include "base/math.tpp"

@ -0,0 +1,757 @@
// Some algorithms are adapted from GLM code:
// https://github.com/g-truc/glm
#include <algorithm>
#include <cassert>
#include "base/util.hpp"
namespace minote::base {
using namespace base::literals;
template<usize Dim, arithmetic T>
constexpr vec<Dim, T>::vec(std::initializer_list<T> _list) {
assert(_list.size() == m_arr.size());
std::copy(_list.begin(), _list.end(), m_arr.begin());
}
template<usize Dim, arithmetic T>
template<arithmetic U>
requires (!std::same_as<T, U>)
constexpr vec<Dim, T>::vec(vec<Dim, U> const& _other) {
for (auto i: iota(0_zu, Dim))
m_arr[i] = T(_other[i]);
}
template<usize Dim, arithmetic T>
template<usize N>
requires (N > Dim)
constexpr vec<Dim, T>::vec(vec<N, T> const& _other) {
for (auto i: iota(0_zu, Dim))
m_arr[i] = _other[i];
}
template<usize Dim, arithmetic T>
template<usize N>
requires (N < Dim)
constexpr vec<Dim, T>::vec(vec<N, T> const& _other, T _fill) {
m_arr.fill(_fill);
for (auto i: iota(0_zu, N))
m_arr[i] = _other[i];
}
template<usize Dim, arithmetic T>
constexpr auto vec<Dim, T>::operator+=(vec<Dim, T> const& _other) -> vec<Dim, T>& {
for (auto i: iota(0_zu, Dim))
m_arr[i] += _other[i];
return *this;
}
template<usize Dim, arithmetic T>
constexpr auto vec<Dim, T>::operator-=(vec<Dim, T> const& _other) -> vec<Dim, T>& {
for (auto i: iota(0_zu, Dim))
m_arr[i] -= _other[i];
return *this;
}
template<usize Dim, arithmetic T>
constexpr auto vec<Dim, T>::operator*=(vec<Dim, T> const& _other) -> vec<Dim, T>& {
for (auto i: iota(0_zu, Dim))
m_arr[i] *= _other[i];
return *this;
}
template<usize Dim, arithmetic T>
constexpr auto vec<Dim, T>::operator/=(vec<Dim, T> const& _other) -> vec<Dim, T>& {
for (auto i: iota(0_zu, Dim))
m_arr[i] /= _other[i];
return *this;
}
template<usize Dim, arithmetic T>
constexpr auto vec<Dim, T>::operator%=(vec<Dim, T> const& _other) -> vec<Dim, T>& {
static_assert(std::is_integral_v<T>);
for (auto i: iota(0_zu, Dim))
m_arr[i] /= _other[i];
return *this;
}
template<usize Dim, arithmetic T>
constexpr auto vec<Dim, T>::operator*=(T _other) -> vec<Dim, T>& {
for (auto i: iota(0_zu, Dim))
m_arr[i] *= _other;
return *this;
}
template<usize Dim, arithmetic T>
constexpr auto vec<Dim, T>::operator/=(T _other) -> vec<Dim, T>& {
for (auto i: iota(0_zu, Dim))
m_arr[i] /= _other;
return *this;
}
template<usize Dim, arithmetic T>
constexpr auto vec<Dim, T>::operator%=(T _other) -> vec<Dim, T>& {
static_assert(std::is_integral_v<T>);
for (auto i: iota(0_zu, Dim))
m_arr[i] %= _other;
return *this;
}
template<usize Dim, arithmetic T>
constexpr auto vec<Dim, T>::operator<<=(T _other) -> vec<Dim, T>& {
static_assert(std::is_integral_v<T>);
for (auto i: iota(0_zu, Dim))
m_arr[i] <<= _other;
return *this;
}
template<usize Dim, arithmetic T>
constexpr auto vec<Dim, T>::operator>>=(T _other) -> vec<Dim, T>& {
static_assert(std::is_integral_v<T>);
for (auto i: iota(0_zu, Dim))
m_arr[i] >>= _other;
return *this;
}
template<usize Dim, arithmetic T>
constexpr auto operator+(vec<Dim, T> const& _left, vec<Dim, T> const& _right) -> vec<Dim, T> {
auto result = _left;
result += _right;
return result;
}
template<usize Dim, arithmetic T>
constexpr auto operator-(vec<Dim, T> const& _left, vec<Dim, T> const& _right) -> vec<Dim, T> {
auto result = _left;
result -= _right;
return result;
}
template<usize Dim, arithmetic T>
constexpr auto operator*(vec<Dim, T> const& _left, vec<Dim, T> const& _right) -> vec<Dim, T> {
auto result = _left;
result *= _right;
return result;
}
template<usize Dim, arithmetic T>
constexpr auto operator/(vec<Dim, T> const& _left, vec<Dim, T> const& _right) -> vec<Dim, T> {
auto result = _left;
result /= _right;
return result;
}
template<usize Dim, integral T>
constexpr auto operator%(vec<Dim, T> const& _left, vec<Dim, T> const& _right) -> vec<Dim, T> {
auto result = _left;
result %= _right;
return result;
}
template<usize Dim, arithmetic T>
constexpr auto operator==(vec<Dim, T> const& _left, vec<Dim, T> const& _right) -> bool {
for (auto i: iota(0_zu, Dim))
if (_left[i] != _right[i])
return false;
return true;
}
template<usize Dim, arithmetic T>
constexpr auto min(vec<Dim, T> const& _left, vec<Dim, T> const& _right) -> vec<Dim, T> {
auto result = vec<Dim, T>();
for (auto i: iota(0_zu, Dim))
result[i] = min(_left[i], _right[i]);
return result;
}
template<usize Dim, arithmetic T>
constexpr auto max(vec<Dim, T> const& _left, vec<Dim, T> const& _right) -> vec<Dim, T> {
auto result = vec<Dim, T>();
for (auto i: iota(0_zu, Dim))
result[i] = max(_left[i], _right[i]);
return result;
}
template<usize Dim, arithmetic T>
constexpr auto dot(vec<Dim, T> const& _left, vec<Dim, T> const& _right) -> T {
auto result = T(0);
for (auto i: iota(0_zu, Dim))
result += _left[i] * _right[i];
return result;
}
template<arithmetic T>
constexpr auto cross(vec<3, T> const& _left, vec<3, T> const& _right) -> vec<3, T> {
return vec<3, T>{
_left[1]*_right[2] - _right[1]*_left[2],
_left[2]*_right[0] - _right[2]*_left[0],
_left[0]*_right[1] - _right[0]*_left[1]};
}
template<usize Dim, arithmetic T>
constexpr auto operator*(vec<Dim, T> const& _left, T _right) -> vec<Dim, T> {
auto result = _left;
result *= _right;
return result;
}
template<usize Dim, arithmetic T>
constexpr auto operator/(vec<Dim, T> const& _left, T _right) -> vec<Dim, T> {
auto result = _left;
result /= _right;
return result;
}
template<usize Dim, integral T>
constexpr auto operator%(vec<Dim, T> const& _left, T _right) -> vec<Dim, T> {
auto result = _left;
result %= _right;
return result;
}
template<usize Dim, integral T>
constexpr auto operator<<(vec<Dim, T> const& _left, T _right) -> vec<Dim, T> {
auto result = _left;
result <<= _right;
return result;
}
template<usize Dim, integral T>
constexpr auto operator>>(vec<Dim, T> const& _left, T _right) -> vec<Dim, T> {
auto result = _left;
result >>= _right;
return result;
}
template<usize Dim, std::floating_point T>
constexpr auto abs(vec<Dim, T> const& _vec) -> vec<Dim, T> {
auto result = vec<Dim, T>();
for (auto i: iota(0_zu, Dim))
result[i] = abs(_vec[i]);
return result;
}
template<usize Dim, std::floating_point T>
constexpr auto normalize(vec<Dim, T> const& _vec) -> vec<Dim, T> {
if constexpr (Dim == 4) {
auto norm = normalize(vec<3, T>(_vec));
return vec<Dim, T>(norm, _vec[3]);
}
return _vec / length(_vec);
}
template<std::floating_point Prec>
constexpr qua<Prec>::qua(std::initializer_list<Prec> _list) {
assert(_list.size() == m_arr.size());
std::copy(_list.begin(), _list.end(), m_arr.begin());
}
template<std::floating_point Prec>
template<usize N>
requires (N == 3 || N == 4)
constexpr qua<Prec>::qua(vec<N, Prec> const& _other) {
m_arr[0] = 0;
m_arr[1] = _other.x();
m_arr[2] = _other.y();
m_arr[3] = _other.z();
}
template<std::floating_point Prec>
constexpr auto qua<Prec>::angleAxis(Prec _angle, vec<3, Prec> _axis) -> qua<Prec> {
assert(isUnit(_axis));
auto halfAngle = _angle / Prec(2);
auto sinHalfAngle = sin(halfAngle);
return qua<Prec>{
cos(halfAngle),
sinHalfAngle * _axis[0],
sinHalfAngle * _axis[1],
sinHalfAngle * _axis[2]};
}
template<std::floating_point Prec>
template<arithmetic U>
requires (!std::same_as<Prec, U>)
constexpr qua<Prec>::qua(qua<U> const& _other) {
for (auto i: iota(0_zu, m_arr.size()))
m_arr[i] = _other[i];
}
template<std::floating_point Prec>
constexpr auto operator*(qua<Prec> const& _l, qua<Prec> const& _r) -> qua<Prec> {
return qua<Prec>{
-_l.x() * _r.x() - _l.y() * _r.y() - _l.z() * _r.z() + _l.w() * _r.w(),
_l.x() * _r.w() + _l.y() * _r.z() - _l.z() * _r.y() + _l.w() * _r.x(),
-_l.x() * _r.z() + _l.y() * _r.w() + _l.z() * _r.x() + _l.w() * _r.y(),
_l.x() * _r.y() - _l.y() * _r.x() + _l.z() * _r.w() + _l.w() * _r.z()};
}
template<usize Dim, std::floating_point Prec>
constexpr mat<Dim, Prec>::mat(std::initializer_list<Prec> _list) {
assert(_list.size() == Dim * Dim);
auto it = _list.begin();
for (auto x: iota(0_zu, Dim))
for (auto y: iota(0_zu, Dim)) {
m_arr[x][y] = *it;
it += 1;
}
}
template<usize Dim, std::floating_point Prec>
constexpr auto mat<Dim, Prec>::identity() -> mat<Dim, Prec> {
auto result = mat<Dim, Prec>();
result.fill(0);
for (auto i: iota(0_zu, Dim))
result[i][i] = 1;
return result;
}
template<usize Dim, std::floating_point Prec>
constexpr auto mat<Dim, Prec>::translate(vec<3, Prec> _shift) -> mat<Dim, Prec> {
static_assert(Dim == 4, "Translation matrix requires order of 4");
auto result = mat<Dim, Prec>::identity();
result[3][0] = _shift[0];
result[3][1] = _shift[1];
result[3][2] = _shift[2];
return result;
}
template<usize Dim, std::floating_point Prec>
constexpr auto mat<Dim, Prec>::rotate(vec<3, Prec> _axis, Prec _angle) -> mat<Dim, Prec> {
assert(isUnit(_axis));
auto sinT = sin(_angle);
auto cosT = cos(_angle);
auto temp = _axis * (Prec(1) - cosT);
auto result = mat<Dim, Prec>::identity();
result[0][0] = cosT + temp[0] * _axis[0];
result[0][1] = temp[0] * _axis[1] + sinT * _axis[2];
result[0][2] = temp[0] * _axis[2] - sinT * _axis[1];
result[1][0] = temp[1] * _axis[0] - sinT * _axis[2];
result[1][1] = cosT + temp[1] * _axis[1];
result[1][2] = temp[1] * _axis[2] + sinT * _axis[0];
result[2][0] = temp[2] * _axis[0] + sinT * _axis[1];
result[2][1] = temp[2] * _axis[1] - sinT * _axis[0];
result[2][2] = cosT + temp[2] * _axis[2];
return result;
}
template<usize Dim, std::floating_point Prec>
constexpr auto mat<Dim, Prec>::rotate(qua<Prec> _quat) -> mat<Dim, Prec> {
auto result = mat<Dim, Prec>::identity();
result[0][0] = 1.0f - 2.0f * (_quat.y() * _quat.y() + _quat.z() * _quat.z());
result[0][1] = 2.0f * (_quat.x() * _quat.y() + _quat.z() * _quat.w());
result[0][2] = 2.0f * (_quat.x() * _quat.z() - _quat.y() * _quat.w());
result[1][0] = 2.0f * (_quat.x() * _quat.y() - _quat.z() * _quat.w());
result[1][1] = 1.0f - 2.0f * (_quat.x() * _quat.x() + _quat.z() * _quat.z());
result[1][2] = 2.0f * (_quat.y() * _quat.z() + _quat.x() * _quat.w());
result[2][0] = 2.0f * (_quat.x() * _quat.z() + _quat.y() * _quat.w());
result[2][1] = 2.0f * (_quat.y() * _quat.z() - _quat.x() * _quat.w());
result[2][2] = 1.0f - 2.0f * (_quat.x() * _quat.x() + _quat.y() * _quat.y());
return result;
}
template<usize Dim, std::floating_point Prec>
constexpr auto mat<Dim, Prec>::scale(vec<3, Prec> _scale) -> mat<Dim, Prec> {
auto result = mat<Dim, Prec>::identity();
for (auto i: iota(0_zu, 3_zu))
result[i][i] = _scale[i];
return result;
}
template<usize Dim, std::floating_point Prec>
constexpr auto mat<Dim, Prec>::scale(Prec _scale) -> mat<Dim, Prec> {
auto result = mat<Dim, Prec>::identity();
for (auto i: iota(0_zu, 3_zu))
result[i][i] = _scale;
return result;
}
template<usize Dim, std::floating_point Prec>
template<arithmetic U>
requires (!std::same_as<Prec, U>)
constexpr mat<Dim, Prec>::mat(mat<Dim, U> const& _other) {
for (auto x: iota(0_zu, Dim))
for (auto y: iota(0_zu, Dim))
m_arr[x][y] = _other[x][y];
}
template<usize Dim, std::floating_point Prec>
template<usize N>
requires (N != Dim)
constexpr mat<Dim, Prec>::mat(mat<N, Prec> const& _other) {
if constexpr (Dim > N)
*this = identity();
constexpr auto Smaller = std::min(Dim, N);
for (auto x: iota(0_zu, Smaller))
for (auto y: iota(0_zu, Smaller))
m_arr[x][y] = _other[x][y];
}
template<usize Dim, std::floating_point Prec>
constexpr auto mat<Dim, Prec>::operator*=(Prec _other) -> mat<Dim, Prec>& {
for (auto x: iota(0_zu, Dim))
for (auto y: iota(0_zu, Dim))
m_arr[x][y] *= _other;
return *this;
}
template<usize Dim, std::floating_point Prec>
constexpr auto mat<Dim, Prec>::operator/=(Prec _other) -> mat<Dim, Prec>& {
for (auto x: iota(0_zu, Dim))
for (auto y: iota(0_zu, Dim))
m_arr[x][y] /= _other;
return *this;
}
template<usize Dim, std::floating_point Prec>
constexpr auto operator*(mat<Dim, Prec> const& _left, mat<Dim, Prec> const& _right) -> mat<Dim, Prec> {
static_assert(Dim == 3 || Dim == 4, "Unsupported matrix order for multiplication");
auto result = mat<Dim, Prec>();
if constexpr (Dim == 3) {
result[0] = _left[0]*_right[0][0] + _left[1]*_right[0][1] + _left[2]*_right[0][2];
result[1] = _left[0]*_right[1][0] + _left[1]*_right[1][1] + _left[2]*_right[1][2];
result[2] = _left[0]*_right[2][0] + _left[1]*_right[2][1] + _left[2]*_right[2][2];
} else if constexpr (Dim == 4) {
result[0] = _left[0]*_right[0][0] + _left[1]*_right[0][1] + _left[2]*_right[0][2] + _left[3]*_right[0][3];
result[1] = _left[0]*_right[1][0] + _left[1]*_right[1][1] + _left[2]*_right[1][2] + _left[3]*_right[1][3];
result[2] = _left[0]*_right[2][0] + _left[1]*_right[2][1] + _left[2]*_right[2][2] + _left[3]*_right[2][3];
result[3] = _left[0]*_right[3][0] + _left[1]*_right[3][1] + _left[2]*_right[3][2] + _left[3]*_right[3][3];
}
return result;
}
template<usize Dim, std::floating_point Prec>
constexpr auto operator*(mat<Dim, Prec> const& _left, vec<Dim, Prec> const& _right) -> vec<Dim, Prec> {
auto leftT = transpose(_left);
auto result = vec<Dim, Prec>();
for (auto i: iota(0_zu, Dim))
result[i] = dot(leftT[i], _right);
return result;
}
template<usize Dim, std::floating_point Prec>
constexpr auto operator*(mat<Dim, Prec> const& _left, Prec _right) -> mat<Dim, Prec> {
auto result = _left;
result *= _right;
return result;
}
template<usize Dim, std::floating_point Prec>
constexpr auto operator/(mat<Dim, Prec> const& _left, Prec _right) -> mat<Dim, Prec> {
auto result = _left;
result /= _right;
return result;
}
template<usize Dim, std::floating_point Prec>
constexpr auto transpose(mat<Dim, Prec> const& _mat) -> mat<Dim, Prec> {
auto result = mat<Dim, Prec>();
for (auto x: iota(0_zu, Dim))
for (auto y: iota(0_zu, Dim))
result[x][y] = _mat[y][x];
return result;
}
template<usize Dim, std::floating_point Prec>
constexpr auto inverse(mat<Dim, Prec> const& _mat) -> mat<Dim, Prec> {
static_assert(Dim == 3 || Dim == 4, "Unsupported matrix order for inversion");
if constexpr (Dim == 3) {
auto oneOverDeterminant = Prec(1) / (
+ _mat[0][0] * (_mat[1][1]*_mat[2][2] - _mat[2][1]*_mat[1][2])
- _mat[1][0] * (_mat[0][1]*_mat[2][2] - _mat[2][1]*_mat[0][2])
+ _mat[2][0] * (_mat[0][1]*_mat[1][2] - _mat[1][1]*_mat[0][2]));
auto result = mat<3, Prec>();
result[0][0] = + (_mat[1][1]*_mat[2][2] - _mat[2][1]*_mat[1][2]) * oneOverDeterminant;
result[1][0] = - (_mat[1][0]*_mat[2][2] - _mat[2][0]*_mat[1][2]) * oneOverDeterminant;
result[2][0] = + (_mat[1][0]*_mat[2][1] - _mat[2][0]*_mat[1][1]) * oneOverDeterminant;
result[0][1] = - (_mat[0][1]*_mat[2][2] - _mat[2][1]*_mat[0][2]) * oneOverDeterminant;
result[1][1] = + (_mat[0][0]*_mat[2][2] - _mat[2][0]*_mat[0][2]) * oneOverDeterminant;
result[2][1] = - (_mat[0][0]*_mat[2][1] - _mat[2][0]*_mat[0][1]) * oneOverDeterminant;
result[0][2] = + (_mat[0][1]*_mat[1][2] - _mat[1][1]*_mat[0][2]) * oneOverDeterminant;
result[1][2] = - (_mat[0][0]*_mat[1][2] - _mat[1][0]*_mat[0][2]) * oneOverDeterminant;
result[2][2] = + (_mat[0][0]*_mat[1][1] - _mat[1][0]*_mat[0][1]) * oneOverDeterminant;
return result;
} else if constexpr (Dim == 4) {
auto coef00 = _mat[2][2]*_mat[3][3] - _mat[3][2]*_mat[2][3];
auto coef02 = _mat[1][2]*_mat[3][3] - _mat[3][2]*_mat[1][3];
auto coef03 = _mat[1][2]*_mat[2][3] - _mat[2][2]*_mat[1][3];
auto coef04 = _mat[2][1]*_mat[3][3] - _mat[3][1]*_mat[2][3];
auto coef06 = _mat[1][1]*_mat[3][3] - _mat[3][1]*_mat[1][3];
auto coef07 = _mat[1][1]*_mat[2][3] - _mat[2][1]*_mat[1][3];
auto coef08 = _mat[2][1]*_mat[3][2] - _mat[3][1]*_mat[2][2];
auto coef10 = _mat[1][1]*_mat[3][2] - _mat[3][1]*_mat[1][2];
auto coef11 = _mat[1][1]*_mat[2][2] - _mat[2][1]*_mat[1][2];
auto coef12 = _mat[2][0]*_mat[3][3] - _mat[3][0]*_mat[2][3];
auto coef14 = _mat[1][0]*_mat[3][3] - _mat[3][0]*_mat[1][3];
auto coef15 = _mat[1][0]*_mat[2][3] - _mat[2][0]*_mat[1][3];
auto coef16 = _mat[2][0]*_mat[3][2] - _mat[3][0]*_mat[2][2];
auto coef18 = _mat[1][0]*_mat[3][2] - _mat[3][0]*_mat[1][2];
auto coef19 = _mat[1][0]*_mat[2][2] - _mat[2][0]*_mat[1][2];
auto coef20 = _mat[2][0]*_mat[3][1] - _mat[3][0]*_mat[2][1];
auto coef22 = _mat[1][0]*_mat[3][1] - _mat[3][0]*_mat[1][1];
auto coef23 = _mat[1][0]*_mat[2][1] - _mat[2][0]*_mat[1][1];
auto fac0 = vec<4, Prec>{coef00, coef00, coef02, coef03};
auto fac1 = vec<4, Prec>{coef04, coef04, coef06, coef07};
auto fac2 = vec<4, Prec>{coef08, coef08, coef10, coef11};
auto fac3 = vec<4, Prec>{coef12, coef12, coef14, coef15};
auto fac4 = vec<4, Prec>{coef16, coef16, coef18, coef19};
auto fac5 = vec<4, Prec>{coef20, coef20, coef22, coef23};
auto v0 = vec<4, Prec>{_mat[1][0], _mat[0][0], _mat[0][0], _mat[0][0]};
auto v1 = vec<4, Prec>{_mat[1][1], _mat[0][1], _mat[0][1], _mat[0][1]};
auto v2 = vec<4, Prec>{_mat[1][2], _mat[0][2], _mat[0][2], _mat[0][2]};
auto v3 = vec<4, Prec>{_mat[1][3], _mat[0][3], _mat[0][3], _mat[0][3]};
auto inv0 = vec<4, Prec>{v1*fac0 - v2*fac1 + v3*fac2};
auto inv1 = vec<4, Prec>{v0*fac0 - v2*fac3 + v3*fac4};
auto inv2 = vec<4, Prec>{v0*fac1 - v1*fac3 + v3*fac5};
auto inv3 = vec<4, Prec>{v0*fac2 - v1*fac4 + v2*fac5};
auto signA = vec<4, Prec>{+1, -1, +1, -1};
auto signB = vec<4, Prec>{-1, +1, -1, +1};
auto inv = mat<4, Prec>{inv0*signA, inv1*signB, inv2*signA, inv3*signB};
auto row0 = vec<4, Prec>{inv[0][0], inv[1][0], inv[2][0], inv[3][0]};
auto dot0 = _mat[0] * row0;
auto dot1 = (dot0.x() + dot0.y()) + (dot0.z() + dot0.w());
auto oneOverDeterminant = Prec(1) / dot1;
return inv * oneOverDeterminant;
}
}
template<std::floating_point Prec>
constexpr auto look(vec<3, Prec> _pos, vec<3, Prec> _dir, vec<3, Prec> _up) -> mat<4, Prec> {
assert(isUnit(_dir));
assert(isUnit(_up));
auto result = mat<4, Prec>::identity();
auto s = normalize(cross(_up, _dir));
auto u = cross(_dir, s);
result[0][0] = s[0];
result[1][0] = s[1];
result[2][0] = s[2];
result[0][1] = u[0];
result[1][1] = u[1];
result[2][1] = u[2];
result[0][2] = _dir[0];
result[1][2] = _dir[1];
result[2][2] = _dir[2];
result[3][0] = -dot(s, _pos);
result[3][1] = -dot(u, _pos);
result[3][2] = -dot(_dir, _pos);
return result;
}
template<std::floating_point Prec>
constexpr auto perspective(Prec _vFov, Prec _aspectRatio, Prec _zNear) -> mat<4, Prec> {
auto range = tan(_vFov / Prec(2)) * _zNear;
auto left = -range * _aspectRatio;
auto right = range * _aspectRatio;
auto bottom = -range;
auto top = range;
auto result = mat<4, Prec>();
result.fill(0);
result[0][0] = (Prec(2) * _zNear) / (right - left);
result[1][1] = (Prec(2) * _zNear) / (top - bottom);
result[2][3] = Prec(1);
result[3][2] = Prec(2) * _zNear;
return result;
}
}

@ -0,0 +1,43 @@
#pragma once
#include <cassert>
#include <cmath>
#include "pcg_basic.h"
#include "base/concepts.hpp"
#include "base/types.hpp"
namespace minote::base {
// PCG pseudorandom number generator
struct Rng {
// Seed the generator with any 64bit value. The generated sequence will
// always be the same for any given seed.
void seed(u64 seed) { pcg32_srandom_r(&m_state, seed, InitSeq); }
// Return a random positive integer, up to a bound (exclusive). RNG state
// is advanced by one step.
auto randInt(u32 bound) -> u32 {
assert(bound >= 1);
return pcg32_boundedrand_r(&m_state, bound);
}
// Return a random floating-point value between 0.0 (inclusive) and 1.0
// (exclusive). RNG state is advanced by one step.
template<floating_point T = f32>
auto randFloat() -> T {
return std::ldexp(T(pcg32_random_r(&m_state)), -32);
}
private:
static constexpr auto InitSeq = u64('M' * 'i' + 'n' * 'o' + 't' * 'e');
// Internal state of the RNG. The .inc (second) field must always be odd
pcg32_random_t m_state = {0, 1};
};
}

@ -0,0 +1,36 @@
#pragma once
#include <concepts>
#include "base/concepts.hpp"
#include "base/types.hpp"
namespace minote::base {
// Main timestamp/duration type. Has enough resolution to largely ignore
// rounding error, and wraps after >100 years.
using nsec = i64;
// Create nsec from a count of seconds
template<arithmetic T>
constexpr auto seconds(T val) -> nsec { return val * 1'000'000'000LL; }
// Create nsec from a count of milliseconds
template<arithmetic T>
constexpr auto milliseconds(T val) { return val * 1'000'000LL; }
// Get an accurate floating-point ratio between two nsecs
template<std::floating_point T = f32>
constexpr auto ratio(nsec left, nsec right) -> T { return f64(left) / f64(right); }
namespace literals {
// Create nsec from second/millisecond literals
constexpr auto operator""_s(unsigned long long val) { return seconds(val); }
constexpr auto operator""_s(long double val) { return seconds(val); }
constexpr auto operator""_ms(unsigned long long val) { return milliseconds(val); }
constexpr auto operator""_ms(long double val) { return milliseconds(val); }
}
}

@ -0,0 +1,43 @@
#pragma once
#include "base/concepts.hpp"
#include "base/types.hpp"
#include "base/ease.hpp"
#include "base/time.hpp"
namespace minote::base {
// Description of a tween instance. most of the fields need to be filled in manually before use;
// designated initializer syntax is convenient for this.
template<floating_point Prec = f32>
struct Tween {
Prec from = 0.0; // Initial value
Prec to = 1.0; // Final value
nsec start = 0; // Time of starting the tween
nsec duration = 1_s; // Time the tween will take to finish
EasingFunction<Prec> Prec = linearInterpolation; // Easing function to use during the tween
// Replay the tween from a given moment
void restart(nsec time) { start = time; }
// Calculate the value of the tween for a specified moment in time
constexpr auto apply(nsec time) const -> Prec;
};
template<floating_point Prec>
constexpr auto Tween<Prec>::apply(nsec time) const -> Prec {
if (start >= time) return from;
if (start + duration <= time) return to;
auto elapsed = time - start;
auto progress = ratio<Prec>(elapsed, duration);
auto span = to - from;
return from + span * progress;
}
}

@ -0,0 +1,34 @@
#pragma once
#include <cstddef>
#include <cstdint>
namespace minote::base {
using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;
using i8 = std::int8_t;
using i16 = std::int16_t;
using i32 = std::int32_t;
using i64 = std::int64_t;
using f32 = float;
using f64 = double;
using usize = std::size_t;
using isize = std::ptrdiff_t;
// Sanity check
static_assert(sizeof(u8) == 1);
static_assert(sizeof(u16) == 2);
static_assert(sizeof(u32) == 4);
static_assert(sizeof(u64) == 8);
static_assert(sizeof(i8) == 1);
static_assert(sizeof(i16) == 2);
static_assert(sizeof(i32) == 4);
static_assert(sizeof(i64) == 8);
static_assert(sizeof(f32) == 4);
static_assert(sizeof(f64) == 8);
}

@ -0,0 +1,93 @@
#pragma once
#include <type_traits>
#include <concepts>
#include <ranges>
#include <tuple>
#include "base/concepts.hpp"
#include "base/types.hpp"
namespace minote::base {
// Simple SemVer type
using Version = std::tuple<u32, u32, u32>;
// Selective imports of ranges library
using std::ranges::views::iota;
using std::ranges::views::reverse;
// Template replacement for the C offsetof() macro. Unfortunately, it cannot be constexpr within
// the current rules of the language.
// Example: offset_of(&Point::y)
// See: https://gist.github.com/graphitemaster/494f21190bb2c63c5516
template<typename T1, std::default_initializable T2>
inline auto offset_of(T1 T2::*member) -> usize {
static auto obj = T2();
return usize(&(obj.*member)) - usize(&obj);
}
// Align a size to a given power-of-two boundary.
constexpr auto alignPOT(usize size, usize boundary) -> usize {
if (boundary == 0) return size;
return (size + boundary - 1) & ~(boundary - 1);
}
constexpr auto nextPOT(u32 n) -> u32 {
n -= 1;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
n += 1;
return n;
}
// Execute n times.
template<typename F>
requires std::invocable<F>
constexpr void repeat(usize times, F func) {
for (auto i = usize(0); i < times; i += 1)
func();
}
// defer pseudo-keyword for executing code at scope exit
// https://stackoverflow.com/a/42060129
#ifndef defer
struct defer_dummy {};
template <class F> struct deferrer { F f; ~deferrer() { f(); } };
template <class F> deferrer<F> operator*(defer_dummy, F f) { return {f}; }
#define DEFER_(LINE) zz_defer##LINE
#define DEFER(LINE) DEFER_(LINE)
#define defer auto DEFER(__LINE__) = defer_dummy{} *[&]()
#endif // defer
namespace literals {
// Conversion of scoped enum to the underlying type, using the unary + operator
template<enum_type T>
constexpr auto operator+(T e) { return std::underlying_type_t<T>(e); }
// usize integer literal
consteval auto operator ""_zu(unsigned long long val) -> usize { return val; }
// Storage space literals
consteval auto operator ""_kb(unsigned long long val) { return val * 1024; }
consteval auto operator ""_mb(unsigned long long val) { return val * 1024 * 1024; }
consteval auto operator ""_gb(unsigned long long val) { return val * 1024 * 1024 * 1024; }
}
}

@ -0,0 +1,21 @@
#pragma once
#ifndef NDEBUG
#define LOG_LEVEL quill::LogLevel::TraceL3
#else
#define LOG_LEVEL quill::LogLevel::Debug
#endif
#ifndef NDEBUG
#define VK_VALIDATION 1
#else
#define VK_VALIDATION 0
#endif
#ifndef NDEBUG
inline constexpr auto Log_p = "minote-debug.log";
#else //NDEBUG
inline constexpr auto Log_p = "minote.log";
#endif //NDEBUG
inline constexpr auto Assets_p = "assets.db";

@ -0,0 +1,278 @@
#include "game.hpp"
#include "config.hpp"
#include <exception>
#include "backends/imgui_impl_sdl.h"
#include "imgui.h"
#include "base/containers/vector.hpp"
#include "base/math.hpp"
#include "base/util.hpp"
#include "base/log.hpp"
#include "gfx/models.hpp"
#include "assets.hpp"
#include "main.hpp"
namespace minote {
using namespace base;
using namespace base::literals;
// Rate of the logic update clock. Can be higher than display refresh rate.
static constexpr auto LogicRate = 120;
static constexpr auto LogicTick = 1_s / LogicRate;
void game(GameParams const& _params) try {
auto& window = _params.window;
auto& engine = _params.engine;
auto& mapper = _params.mapper;
// Load assets
auto modelList = gfx::ModelList();
auto assets = Assets(Assets_p);
assets.loadModels([&modelList](auto name, auto data) {
modelList.addModel(name, data);
});
// Initialize the engine
engine.init(std::move(modelList));
engine.camera() = gfx::Camera{
.position = {8.57_m, -16.07_m, 69.20_m},
.yaw = 2.41412449f,
.pitch = 0.113862038f,
.lookSpeed = 1.0f / 256.0f,
.moveSpeed = 1_m / 16.0f};
// Makeshift freeform camera controls
auto camUp = false;
auto camDown = false;
auto camLeft = false;
auto camRight = false;
auto camFloat = false;
auto camMoving = false;
auto camOffset = vec2(0.0f);
// Create test scene
// Keep track of the spinning cubes so that we can rotate them each frame
auto dynamicObjects = ivector<gfx::ObjectID>();
defer {
for (auto id: dynamicObjects)
engine.objects().destroy(id);
};
constexpr auto prescale = vec3{1_m, 1_m, 1_m};
constexpr auto Expand = 0u;
constexpr auto Spacing = 25_m;
constexpr auto TestScenes = 1;
for (auto x: iota(0, TestScenes))
for (auto y: iota(0, TestScenes)) {
constexpr auto TestSpacing = 80_m;
auto offset = vec3{x * 80_m, y * 80_m, 0.0f};
auto testscene_id = engine.objects().create();
auto testscene = engine.objects().get(testscene_id);
testscene.modelID = "testscene"_id;
testscene.transform.position = vec3{0_m, 0_m, 64_m} + offset;
testscene.transform.scale = prescale;
}
// Create another test scene
for (auto x = -Spacing * Expand; x <= Spacing * Expand; x += Spacing)
for (auto y = -Spacing * Expand; y <= Spacing * Expand; y += Spacing) {
auto offset = vec3{x, y, 32_m};
auto block1_id = engine.objects().create();
auto block1 = engine.objects().get(block1_id);
block1.modelID = "block"_id;
block1.color = {0.9f, 0.9f, 1.0f, 1.0f};
block1.transform.position = offset;
block1.transform.scale = vec3{12.0f, 12.0f, 1.0f} * prescale;
auto block2_id = engine.objects().create();
auto block2 = engine.objects().get(block2_id);
block2.modelID = "block"_id;
block2.color = {0.9f, 0.1f, 0.1f, 1.0f};
block2.transform.position = vec3{-4_m, -4_m, 2_m} + offset;
block2.transform.scale = prescale;
auto block3_id = engine.objects().create();
auto block3 = engine.objects().get(block3_id);
block3.modelID = "block"_id;
block3.color = {0.9f, 0.1f, 0.1f, 1.0f};
block3.transform.position = vec3{4_m, -4_m, 2_m} + offset;
block3.transform.scale = prescale;
auto block4_id = engine.objects().create();
auto block4 = engine.objects().get(block4_id);
block4.modelID = "block"_id;
block4.color = {0.9f, 0.1f, 0.1f, 1.0f};
block4.transform.position = vec3{-4_m, 4_m, 2_m} + offset;
block4.transform.scale = prescale;
auto block5_id = engine.objects().create();
auto block5 = engine.objects().get(block5_id);
block5.modelID = "block"_id;
block5.color = {0.9f, 0.1f, 0.1f, 1.0f};
block5.transform.position = vec3{4_m, 4_m, 2_m} + offset;
block5.transform.scale = prescale;
auto block6_id = engine.objects().create();
auto block6 = engine.objects().get(block6_id);
block6.modelID = "block"_id;
block6.color = {0.1f, 0.1f, 0.9f, 1.0f};
block6.transform.position = vec3{7_m, 0_m, 2_m} + offset;
block6.transform.scale = prescale;
auto block7_id = engine.objects().create();
auto block7 = engine.objects().get(block7_id);
dynamicObjects.emplace_back(block7_id);
block7.modelID = "block"_id;
block7.color = {0.2f, 0.9f, 0.5f, 1.0f};
block7.transform.position = vec3{0_m, 0_m, 2.5_m} + offset;
block7.transform.scale = vec3{1.5f, 1.5f, 1.5f} * prescale;
for (auto i: iota(0, 9)) {
auto offset2 = offset + vec3{f32(i - 4) / 4.0f * 8_m, 0_m, 0_m};
auto sphere1_id = engine.objects().create();
auto sphere1 = engine.objects().get(sphere1_id);
sphere1.modelID = "sphere"_id;
sphere1.transform.position = vec3{0_m, 8_m, 2_m} + offset2;
sphere1.transform.scale = prescale;
auto sphere2_id = engine.objects().create();
auto sphere2 = engine.objects().get(sphere2_id);
sphere2.modelID = "sphere"_id;
sphere2.transform.position = vec3{0_m, -8_m, 2_m} + offset2;
sphere2.transform.scale = prescale;
}
}
L_INFO("Game initialized");
// Main loop
auto nextUpdate = sys::System::getTime();
while (!sys::System::isQuitting()) {
auto framerateScale = min(144.0f / engine.fps(), 8.0f);
engine.camera().moveSpeed = 1_m / 16.0f * framerateScale;
// Input handling
ImGui_ImplSDL2_NewFrame(window.handle());
while (nextUpdate <= sys::System::getTime()) {
// Iterate over all events
sys::System::forEachEvent([&] (const sys::System::Event& e) -> bool {
// Don't handle events from the future
if (milliseconds(e.common.timestamp) > nextUpdate) return false;
// Leave quit events alone
if (e.type == SDL_QUIT) return false;
// Let ImGui handle all events it uses
ImGui_ImplSDL2_ProcessEvent(&e);
// If ImGui wants exclusive control, leave now
if (e.type == SDL_KEYDOWN)
if (ImGui::GetIO().WantCaptureKeyboard) return true;
if (e.type == SDL_MOUSEBUTTONDOWN || e.type == SDL_MOUSEMOTION)
if (ImGui::GetIO().WantCaptureMouse) return true;
// Camera motion
if (e.type == SDL_MOUSEBUTTONDOWN && e.button.button == SDL_BUTTON_LEFT)
camMoving = true;
if (e.type == SDL_MOUSEBUTTONUP && e.button.button == SDL_BUTTON_LEFT)
camMoving = false;
if (e.type == SDL_MOUSEMOTION)
camOffset += vec2{f32(e.motion.xrel), f32(e.motion.yrel)};
// Game logic events
if (auto action = mapper.convert(e)) {
// Quit event
if (action->type == Action::Type::Back)
sys::System::postQuitEvent();
// Placeholder camera input
auto state = (action->state == Mapper::Action::State::Pressed);
if (action->type == Action::Type::Drop)
camUp = state;
if (action->type == Action::Type::Lock)
camDown = state;
if (action->type == Action::Type::Left)
camLeft = state;
if (action->type == Action::Type::Right)
camRight = state;
if (action->type == Action::Type::Skip)
camFloat = state;
}
return true;
});
nextUpdate += LogicTick;
}
// Logic
// Camera update
camOffset.y() = -camOffset.y();
if (camMoving)
engine.camera().rotate(camOffset.x(), camOffset.y());
engine.camera().roam({
float(camRight) - float(camLeft),
0.0f,
float(camUp) - float(camDown)});
engine.camera().shift({0.0f, 0.0f, float(camFloat)});
camOffset = vec2(0.0f);
// Graphics
{
auto rotateAnim = quat::angleAxis(radians(ratio(sys::System::getTime(), 20_ms)), {0.0f, 0.0f, 1.0f});
for (auto& obj: dynamicObjects)
engine.objects().get(obj).transform.rotation = rotateAnim;
}
engine.render();
}
} catch (std::exception const& e) {
L_CRIT("Unhandled exception on game thread: {}", e.what());
L_CRIT("Cannot recover, shutting down. Please report this error to the developer");
sys::System::postQuitEvent();
}
}

@ -0,0 +1,19 @@
#pragma once
#include "sys/window.hpp"
#include "gfx/engine.hpp"
#include "mapper.hpp"
namespace minote {
struct GameParams {
sys::Window& window;
gfx::Engine& engine;
Mapper& mapper;
};
void game(GameParams const&);
}

@ -0,0 +1,52 @@
#include "gfx/camera.hpp"
namespace minote::gfx {
using namespace base::literals;
auto Camera::direction() const -> vec3 {
return vec3{
cos(pitch) * cos(yaw),
cos(pitch) * sin(yaw),
sin(pitch)};
}
auto Camera::transform() const -> mat4 {
return look(position, direction(), {0.0f, 0.0f, -1.0f});
}
void Camera::rotate(f32 horz, f32 vert) {
yaw -= horz * lookSpeed;
if (yaw < 0_deg) yaw += 360_deg;
if (yaw >= 360_deg) yaw -= 360_deg;
pitch += vert * lookSpeed;
pitch = clamp(pitch, -89_deg, 89_deg);
}
void Camera::shift(vec3 distance) {
position += distance * moveSpeed;
}
void Camera::roam(vec3 distance) {
auto fwd = direction();
auto right = vec3{fwd.y(), -fwd.x(), 0.0f};
auto up = vec3{-fwd.y(), fwd.z(), fwd.x()};
shift(
distance.x() * right +
distance.y() * up +
distance.z() * fwd);
}
}

@ -0,0 +1,39 @@
#pragma once
#include "base/math.hpp"
namespace minote::gfx {
using namespace base;
// A user-controllable camera. Easy to manipulate with intuitive functions,
// and can be converted into a world->view transform matrix.
struct Camera {
vec3 position;
f32 yaw;
f32 pitch;
f32 lookSpeed;
f32 moveSpeed;
// Return a unit vector of the direction the camera is pointing in.
[[nodiscard]]
auto direction() const -> vec3;
// Return a matrix that transforms from world space to view space.
[[nodiscard]]
auto transform() const -> mat4;
// Change camera direction by the provided offsets, taking into account lookSpeed.
void rotate(f32 horz, f32 vert);
// Change the camera position directly, taking into account moveSpeed.
void shift(vec3 distance);
// Change the camera position relatively to its direction, taking into account moveSpeed.
void roam(vec3 distance);
};
}

@ -0,0 +1,139 @@
#include "gfx/effects/bloom.hpp"
#include <cassert>
#include "vuk/CommandBuffer.hpp"
#include "base/containers/string.hpp"
#include "base/types.hpp"
#include "base/util.hpp"
#include "gfx/samplers.hpp"
#include "gfx/util.hpp"
namespace minote::gfx {
using namespace base;
void Bloom::compile(vuk::PerThreadContext& _ptc) {
auto bloomDownPci = vuk::ComputePipelineBaseCreateInfo();
bloomDownPci.add_spirv(std::vector<u32>{
#include "spv/bloom/down.comp.spv"
}, "bloom/down.comp");
_ptc.ctx.create_named_pipeline("bloom/down", bloomDownPci);
auto bloomDownKarisPci = vuk::ComputePipelineBaseCreateInfo();
bloomDownKarisPci.add_spirv(std::vector<u32>{
#include "spv/bloom/downKaris.comp.spv"
}, "bloom/downKaris.comp");
_ptc.ctx.create_named_pipeline("bloom/downKaris", bloomDownKarisPci);
auto bloomUpPci = vuk::ComputePipelineBaseCreateInfo();
bloomUpPci.add_spirv(std::vector<u32>{
#include "spv/bloom/up.comp.spv"
}, "bloom/up.comp");
_ptc.ctx.create_named_pipeline("bloom/up", bloomUpPci);
}
void Bloom::apply(Frame& _frame, Pool& _pool, Texture2D _target) {
assert(_target.size().x() >= (1u << BloomPasses));
assert(_target.size().y() >= (1u << BloomPasses));
// Create temporary resources
auto bloomTemp = Texture2D::make(_pool, nameAppend(_target.name, "bloomTemp"),
_target.size() / 2u, BloomFormat,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled,
BloomPasses);
bloomTemp.attach(_frame.rg, vuk::eNone, vuk::eNone);
// Downsample pass: repeatedly draw the source image into increasingly smaller mips
_frame.rg.add_pass({
.name = nameAppend(_target.name, "bloom/down"),
.resources = {
_target.resource(vuk::eComputeSampled),
bloomTemp.resource(vuk::eComputeRW) },
.execute = [_target, temp=bloomTemp](vuk::CommandBuffer& cmd) {
for (auto i: iota(0u, BloomPasses)) {
auto sourceSize = uvec2();
auto targetSize = _target.size() >> (i + 1);
if (i == 0) { // First pass, read from target with lowpass filter
cmd.bind_sampled_image(0, 0, _target, LinearClamp);
sourceSize = _target.size();
cmd.bind_compute_pipeline("bloom/downKaris");
} else { // Read from intermediate mip
cmd.image_barrier(temp.name, vuk::eComputeRW, vuk::eComputeSampled, i-1, 1);
cmd.bind_sampled_image(0, 0, *temp.mipView(i - 1), LinearClamp);
sourceSize = _target.size() >> i;
cmd.bind_compute_pipeline("bloom/down");
}
cmd.bind_storage_image(0, 1, *temp.mipView(i));
cmd.specialize_constants(0, u32Fromu16(sourceSize));
cmd.specialize_constants(1, u32Fromu16(targetSize));
cmd.dispatch_invocations(_target.size().x() >> (i + 1), _target.size().y() >> (i + 1), 1);
}
// Mipmap usage requires manual barrier management
cmd.image_barrier(temp.name, vuk::eComputeSampled, vuk::eComputeRW, 0, BloomPasses - 1);
}});
// Upsample pass: same as downsample, but in reverse order
_frame.rg.add_pass({
.name = nameAppend(_target.name, "bloom/up"),
.resources = {
bloomTemp.resource(vuk::eComputeRW),
_target.resource(vuk::eComputeRW) },
.execute = [_target, temp=bloomTemp](vuk::CommandBuffer& cmd) {
for (auto i: iota(0u, BloomPasses) | reverse) {
auto sourceSize = _target.size() >> (i + 1);
auto targetSize = uvec2();
auto power = 1.0f;
cmd.image_barrier(temp.name, vuk::eComputeRW, vuk::eComputeSampled, i, 1);
cmd.bind_sampled_image(0, 0, *temp.mipView(i), LinearClamp);
if (i == 0) { // Final pass, draw to target
cmd.bind_sampled_image(0, 1, _target, NearestClamp, vuk::ImageLayout::eGeneral)
.bind_storage_image(0, 2, _target);
targetSize = _target.size();
power = BloomStrength;
} else { // Draw to intermediate mip
cmd.bind_sampled_image(0, 1, *temp.mipView(i - 1), NearestClamp, vuk::ImageLayout::eGeneral)
.bind_storage_image(0, 2, *temp.mipView(i - 1));
targetSize = _target.size() >> i;
}
cmd.bind_compute_pipeline("bloom/up");
cmd.specialize_constants(0, u32Fromu16(sourceSize));
cmd.specialize_constants(1, u32Fromu16(targetSize));
cmd.push_constants(vuk::ShaderStageFlagBits::eCompute, 0, power);
cmd.dispatch_invocations(_target.size().x() >> i, _target.size().y() >> i, 1);
}
}});
}
}

@ -0,0 +1,32 @@
#pragma once
#include "vuk/Context.hpp"
#include "gfx/resources/texture2d.hpp"
#include "gfx/resources/pool.hpp"
#include "gfx/frame.hpp"
namespace minote::gfx {
using namespace base;
// Bloom effect. Blends an image with a blurred version of itself.
// This implementation has no thresholding to better mimic naked-eye glare,
// and uses a low-pass filter to avoid fireflies that are common in HDR source
// images. Relative blur width is resolution-independent.
struct Bloom {
static constexpr auto BloomFormat = vuk::Format::eB10G11R11UfloatPack32;
// More passes increases blur width at a small performance cost
static constexpr auto BloomPasses = 6u;
// Because the blending is additive, the strength multiplier needs to be very small
static constexpr auto BloomStrength = 1.0f / 64.0f;
// Build the shader.
static void compile(vuk::PerThreadContext&);
// Create a pass that applies bloom to the specified image.
static void apply(Frame&, Pool&, Texture2D target);
};
}

@ -0,0 +1,55 @@
#include "gfx/effects/bvh.hpp"
namespace minote::gfx {
using namespace base;
void BVH::compile(vuk::PerThreadContext& _ptc) {
auto debugAABBPci = vuk::PipelineBaseCreateInfo();
debugAABBPci.add_spirv(std::vector<u32>{
#include "spv/bvh/debugAABB.vert.spv"
}, "bvh/debugAABB.vert");
debugAABBPci.add_spirv(std::vector<u32>{
#include "spv/bvh/debugAABB.frag.spv"
}, "bvh/debugAABB.frag");
_ptc.ctx.create_named_pipeline("bvh/debugAABB", debugAABBPci);
}
void BVH::debugDrawAABBs(Frame& _frame, Texture2D _target, InstanceList _instances) {
auto aabbs = Buffer<AABB>::make(_frame.permPool, "AABBs",
vuk::BufferUsageFlagBits::eStorageBuffer,
_frame.models.cpu_meshletAABBs);
aabbs.attach(_frame.rg, vuk::eHostWrite, vuk::eNone);
_frame.rg.add_pass({
.name = nameAppend(_target.name, "bvh/debugAABB"),
.resources = {
aabbs.resource(vuk::eVertexRead),
_instances.instances.resource(vuk::eVertexRead),
_instances.transforms.resource(vuk::eVertexRead),
_target.resource(vuk::eColorWrite) },
.execute = [_target, _instances, aabbs, &_frame](vuk::CommandBuffer& cmd) {
cmd.set_viewport(0, vuk::Rect2D::framebuffer());
cmd.set_scissor(0, vuk::Rect2D::framebuffer());
cmd.set_color_blend(_target.name, vuk::BlendPreset::eOff);
cmd.set_primitive_topology(vuk::PrimitiveTopology::eLineList);
cmd.set_rasterization(vuk::PipelineRasterizationStateCreateInfo{
.lineWidth = 1.0f });
cmd.bind_uniform_buffer(0, 0, _frame.world)
.bind_storage_buffer(0, 1, aabbs)
.bind_storage_buffer(0, 2, _instances.instances)
.bind_storage_buffer(0, 3, _instances.transforms)
.bind_graphics_pipeline("bvh/debugAABB");
cmd.draw(12 * 2, _instances.size(), 0, 0);
}});
}
}

@ -0,0 +1,20 @@
#pragma once
#include "vuk/Context.hpp"
#include "gfx/resources/texture2d.hpp"
#include "gfx/effects/instanceList.hpp"
#include "gfx/frame.hpp"
namespace minote::gfx {
using namespace base;
struct BVH {
static void compile(vuk::PerThreadContext&);
static void debugDrawAABBs(Frame&, Texture2D target, InstanceList);
};
}

@ -0,0 +1,21 @@
#include "gfx/effects/clear.hpp"
#include "gfx/util.hpp"
namespace minote::gfx {
void Clear::apply(Frame& _frame, Texture2D _target, vuk::ClearColor _color) {
_frame.rg.add_pass({
.name = nameAppend(_target.name, "clear"),
.resources = {
_target.resource(vuk::eTransferClear) },
.execute = [_target, _color](vuk::CommandBuffer& cmd) {
cmd.clear_image(_target.name, _color);
}});
}
}

@ -0,0 +1,16 @@
#pragma once
#include "gfx/resources/texture2d.hpp"
#include "gfx/frame.hpp"
namespace minote::gfx {
using namespace base;
struct Clear {
static void apply(Frame&, Texture2D target, vuk::ClearColor);
};
}

@ -0,0 +1,111 @@
#include "gfx/effects/cubeFilter.hpp"
#include <cassert>
#include "vuk/CommandBuffer.hpp"
#include "base/types.hpp"
#include "base/math.hpp"
#include "base/util.hpp"
#include "gfx/effects/cubeFilterCoeffs.hpp"
#include "gfx/samplers.hpp"
#include "gfx/util.hpp"
namespace minote::gfx {
using namespace base;
void CubeFilter::compile(vuk::PerThreadContext& _ptc) {
auto cubeFilterPrePci = vuk::ComputePipelineBaseCreateInfo();
cubeFilterPrePci.add_spirv(std::vector<u32>{
#include "spv/cubeFilter/pre.comp.spv"
}, "cubeFilter/pre.comp");
_ptc.ctx.create_named_pipeline("cubeFilter/pre", cubeFilterPrePci);
auto cubeFilterPostPci = vuk::ComputePipelineBaseCreateInfo();
cubeFilterPostPci.add_spirv(std::vector<u32>{
#include "spv/cubeFilter/post.comp.spv"
}, "cubeFilter/post.comp");
_ptc.ctx.create_named_pipeline("cubeFilter/post", cubeFilterPostPci);
}
void CubeFilter::apply(Frame& _frame, Cubemap _src, Cubemap _dst) {
assert(_src.size() == uvec2(BaseSize));
assert(_dst.size() == uvec2(BaseSize));
_frame.rg.add_pass({
.name = nameAppend(_src.name, "cubeFilter/pre"),
.resources = {
_src.resource(vuk::eComputeWrite) },
.execute = [_src](vuk::CommandBuffer& cmd) {
for (auto i: iota(1u, MipCount)) {
cmd.image_barrier(_src.name, vuk::eComputeWrite, vuk::eComputeSampled, i-1, 1);
cmd.bind_sampled_image(0, 0, *_src.mipView(i-1), LinearClamp)
.bind_storage_image(0, 1, *_src.mipArrayView(i))
.bind_compute_pipeline("cubeFilter/pre");
cmd.specialize_constants(0, _src.size().x() >> i);
cmd.dispatch_invocations(BaseSize >> i, BaseSize >> i, 6);
}
cmd.image_barrier(_src.name, vuk::eComputeSampled, vuk::eComputeWrite, 0, MipCount-1);
}});
_frame.rg.add_pass({
.name = nameAppend(_src.name, "cubeFilter/post"),
.resources = {
_src.resource(vuk::eComputeRead),
_dst.resource(vuk::eComputeWrite) },
.execute = [_src, _dst](vuk::CommandBuffer& cmd) {
cmd.bind_sampled_image(0, 0, _src.name, TrilinearClamp)
.bind_storage_image(0, 1, *_dst.mipArrayView(1))
.bind_storage_image(0, 2, *_dst.mipArrayView(2))
.bind_storage_image(0, 3, *_dst.mipArrayView(3))
.bind_storage_image(0, 4, *_dst.mipArrayView(4))
.bind_storage_image(0, 5, *_dst.mipArrayView(5))
.bind_storage_image(0, 6, *_dst.mipArrayView(6))
.bind_storage_image(0, 7, *_dst.mipArrayView(7))
.bind_compute_pipeline("cubeFilter/post");
auto* coeffs = cmd.map_scratch_uniform_binding<vec4[7][5][3][24]>(0, 8);
std::memcpy(coeffs, IBLCoefficients, sizeof(IBLCoefficients));
cmd.dispatch_invocations(21840, 6);
}});
_frame.rg.add_pass({
.name = nameAppend(_src.name, "mip0 copy"),
.resources = {
_src.resource(vuk::eTransferSrc),
_dst.resource(vuk::eTransferDst) },
.execute = [_src, _dst](vuk::CommandBuffer& cmd) {
cmd.image_barrier(_src.name, vuk::eComputeRead, vuk::eTransferSrc);
cmd.image_barrier(_dst.name, vuk::eComputeWrite, vuk::eTransferDst);
cmd.blit_image(_src.name, _dst.name,
vuk::ImageBlit{
.srcSubresource = vuk::ImageSubresourceLayers{
.aspectMask = vuk::ImageAspectFlagBits::eColor,
.layerCount = 6 },
.srcOffsets = {vuk::Offset3D{0, 0, 0}, vuk::Offset3D{BaseSize, BaseSize, 1}},
.dstSubresource = vuk::ImageSubresourceLayers{
.aspectMask = vuk::ImageAspectFlagBits::eColor,
.layerCount = 6 },
.dstOffsets = {vuk::Offset3D{0, 0, 0}, vuk::Offset3D{BaseSize, BaseSize, 1}} },
vuk::Filter::eNearest);
}});
}
}

@ -0,0 +1,28 @@
#pragma once
#include "vuk/Context.hpp"
#include "gfx/resources/cubemap.hpp"
#include "gfx/frame.hpp"
namespace minote::gfx {
using namespace base;
// Performs filtering of a cubemap, generating increasingly blurred versions
// of each mipmap. Useful for IBL with a range of roughness values.
struct CubeFilter {
// 1st mip is perfect specular, next mips are increasingly rough
static constexpr auto MipCount = 1u + 7u;
// The technique only supports cubemaps of this size
static constexpr auto BaseSize = 256u;
// Build the shader.
static void compile(vuk::PerThreadContext&);
// Using mip0 of src as input, generate MipCount mips in dst.
static void apply(Frame&, Cubemap src, Cubemap dst);
};
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,102 @@
#include "gfx/effects/hiz.hpp"
#include "vuk/CommandBuffer.hpp"
#include "base/types.hpp"
#include "gfx/samplers.hpp"
#include "gfx/util.hpp"
namespace minote::gfx {
using namespace base;
void HiZ::compile(vuk::PerThreadContext& _ptc) {
auto hizFirstPci = vuk::ComputePipelineBaseCreateInfo();
hizFirstPci.add_spirv(std::vector<u32>{
#include "spv/hiz/first.comp.spv"
}, "hiz/first.comp");
_ptc.ctx.create_named_pipeline("hiz/first", hizFirstPci);
auto hizMipPci = vuk::ComputePipelineBaseCreateInfo();
hizMipPci.add_spirv(std::vector<u32>{
#include "spv/hiz/mip.comp.spv"
}, "hiz/mip.comp");
_ptc.ctx.create_named_pipeline("hiz/mip", hizMipPci);
}
auto HiZ::make(Pool& _pool, vuk::Name _name, Texture2DMS _depth) -> Texture2D {
auto dim = max(nextPOT(_depth.size().x()), nextPOT(_depth.size().y()));
auto size = uvec2(dim);
return Texture2D::make(_pool, _name,
size, vuk::Format::eR32Sfloat,
vuk::ImageUsageFlagBits::eSampled |
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eTransferDst,
mipmapCount(dim));
}
void HiZ::fill(Frame& _frame, Texture2D _hiz, Texture2DMS _depth) {
auto mipCount = mipmapCount(_hiz.size().x());
_frame.rg.add_pass({
.name = nameAppend(_hiz.name, "hiz/first"),
.resources = {
_depth.resource(vuk::eComputeSampled),
_hiz.resource(vuk::eComputeWrite) },
.execute = [mipCount, _hiz, _depth](vuk::CommandBuffer& cmd) {
auto mipsGenerated = 0;
// Initial pass
cmd.bind_sampled_image(0, 0, _depth, NearestClamp)
.bind_storage_image(0, 1, *_hiz.mipView(min(0u, mipCount - 1)))
.bind_storage_image(0, 2, *_hiz.mipView(min(1u, mipCount - 1)))
.bind_storage_image(0, 3, *_hiz.mipView(min(2u, mipCount - 1)))
.bind_storage_image(0, 4, *_hiz.mipView(min(3u, mipCount - 1)))
.bind_storage_image(0, 5, *_hiz.mipView(min(4u, mipCount - 1)))
.bind_storage_image(0, 6, *_hiz.mipView(min(5u, mipCount - 1)));
cmd.specialize_constants(0, u32Fromu16(_depth.size()));
cmd.specialize_constants(1, u32Fromu16(_hiz.size()));
cmd.specialize_constants(2, min(mipCount, 6u));
cmd.bind_compute_pipeline("hiz/first");
cmd.dispatch_invocations(_depth.size().x(), _depth.size().y());
mipsGenerated += 6;
while (mipsGenerated < mipCount) {
cmd.image_barrier(_hiz.name, vuk::eComputeRW, vuk::eComputeSampled, mipsGenerated - 1, 1);
cmd.bind_sampled_image(0, 0, *_hiz.mipView(min(mipsGenerated - 1, mipCount - 1)), MinClamp)
.bind_storage_image(0, 1, *_hiz.mipView(min(mipsGenerated + 0, mipCount - 1)))
.bind_storage_image(0, 2, *_hiz.mipView(min(mipsGenerated + 1, mipCount - 1)))
.bind_storage_image(0, 3, *_hiz.mipView(min(mipsGenerated + 2, mipCount - 1)))
.bind_storage_image(0, 4, *_hiz.mipView(min(mipsGenerated + 3, mipCount - 1)))
.bind_storage_image(0, 5, *_hiz.mipView(min(mipsGenerated + 4, mipCount - 1)))
.bind_storage_image(0, 6, *_hiz.mipView(min(mipsGenerated + 5, mipCount - 1)))
.bind_storage_image(0, 7, *_hiz.mipView(min(mipsGenerated + 6, mipCount - 1)));
cmd.specialize_constants(0, u32Fromu16(_hiz.size() >> (mipsGenerated - 1u)));
cmd.specialize_constants(1, min(mipCount - mipsGenerated, 7u));
cmd.bind_compute_pipeline("hiz/mip");
cmd.dispatch_invocations(_depth.size().x() / 4, _depth.size().y() / 4);
cmd.image_barrier(_hiz.name, vuk::eComputeSampled, vuk::eComputeRW, mipsGenerated - 1, 1);
mipsGenerated += 7;
}
}});
}
}

@ -0,0 +1,24 @@
#pragma once
#include "vuk/Context.hpp"
#include "vuk/Name.hpp"
#include "gfx/resources/texture2dms.hpp"
#include "gfx/resources/texture2d.hpp"
#include "gfx/resources/pool.hpp"
#include "gfx/frame.hpp"
namespace minote::gfx {
using namespace base;
struct HiZ {
static void compile(vuk::PerThreadContext&);
static auto make(Pool&, vuk::Name, Texture2DMS depth) -> Texture2D;
static void fill(Frame&, Texture2D hiz, Texture2DMS depth);
};
}

@ -0,0 +1,272 @@
#include "gfx/effects/instanceList.hpp"
#include <cassert>
#include <span>
#include "base/containers/vector.hpp"
#include "base/util.hpp"
#include "gfx/samplers.hpp"
#include "gfx/util.hpp"
#include "tools/modelSchema.hpp"
namespace minote::gfx {
using namespace base;
constexpr auto encodeTransform(ObjectPool::Transform _in) -> InstanceList::Transform {
auto rw = _in.rotation.w();
auto rx = _in.rotation.x();
auto ry = _in.rotation.y();
auto rz = _in.rotation.z();
auto rotationMat = mat3{
1.0f - 2.0f * (ry * ry + rz * rz), 2.0f * (rx * ry - rw * rz), 2.0f * (rx * rz + rw * ry),
2.0f * (rx * ry + rw * rz), 1.0f - 2.0f * (rx * rx + rz * rz), 2.0f * (ry * rz - rw * rx),
2.0f * (rx * rz - rw * ry), 2.0f * (ry * rz + rw * rx), 1.0f - 2.0f * (rx * rx + ry * ry)};
rotationMat[0] *= _in.scale.x();
rotationMat[1] *= _in.scale.y();
rotationMat[2] *= _in.scale.z();
return to_array({
vec4(rotationMat[0], _in.position.x()),
vec4(rotationMat[1], _in.position.y()),
vec4(rotationMat[2], _in.position.z())});
}
auto InstanceList::upload(Pool& _pool, Frame& _frame, vuk::Name _name,
ObjectPool const& _objects) -> InstanceList {
auto result = InstanceList();
// Precalculate instance count
auto modelCount = 0u;
auto instanceCount = 0u;
for (auto idx: iota(ObjectID(0), _objects.size())) {
auto& metadata = _objects.metadata[idx];
if (!metadata.exists || !metadata.visible)
continue;
auto id = _objects.modelIDs[idx];
auto modelIdx = _frame.models.cpu_modelIndices.at(id);
modelCount += 1;
instanceCount += _frame.models.cpu_models[modelIdx].meshletCount;
}
// Queue up all valid objects
auto instances = pvector<Instance>();
instances.reserve(instanceCount);
auto transforms = pvector<Transform>();
transforms.reserve(modelCount);
auto prevTransforms = pvector<Transform>();
prevTransforms.reserve(modelCount);
auto colors = pvector<vec4>();
colors.reserve(modelCount);
result.triangleCount = 0;
for (auto idx: iota(ObjectID(0), _objects.size())) {
auto& metadata = _objects.metadata[idx];
if (!metadata.exists || !metadata.visible)
continue;
auto id = _objects.modelIDs[idx];
auto modelIdx = _frame.models.cpu_modelIndices.at(id);
auto& model = _frame.models.cpu_models[modelIdx];
// Add meshlet instances
for (auto i: iota(0u, model.meshletCount)) {
instances.push_back(Instance{
.objectIdx = u32(transforms.size()),
.meshletIdx = model.meshletOffset + i });
result.triangleCount += divRoundUp(_frame.models.cpu_meshlets[model.meshletOffset + i].indexCount, 3u);
}
// Add model details
transforms.emplace_back(encodeTransform(_objects.transforms[idx]));
prevTransforms.emplace_back(encodeTransform(_objects.prevTransforms[idx]));
colors.emplace_back(_objects.colors[idx]);
}
// Upload data to GPU
result.colors = Buffer<vec4>::make(_pool, nameAppend(_name, "colors"),
vuk::BufferUsageFlagBits::eStorageBuffer,
colors);
result.transforms = Buffer<Transform>::make(_pool, nameAppend(_name, "transforms"),
vuk::BufferUsageFlagBits::eStorageBuffer,
transforms);
result.prevTransforms = Buffer<Transform>::make(_pool, nameAppend(_name, "prevTransforms"),
vuk::BufferUsageFlagBits::eStorageBuffer,
prevTransforms);
result.instances = Buffer<Instance>::make(_pool, nameAppend(_name, "instances"),
vuk::BufferUsageFlagBits::eStorageBuffer,
instances);
result.colors.attach(_frame.rg, vuk::eHostWrite, vuk::eNone);
result.transforms.attach(_frame.rg, vuk::eHostWrite, vuk::eNone);
result.prevTransforms.attach(_frame.rg, vuk::eHostWrite, vuk::eNone);
result.instances.attach(_frame.rg, vuk::eHostWrite, vuk::eNone);
return result;
}
void TriangleList::compile(vuk::PerThreadContext& _ptc) {
auto genIndicesPci = vuk::ComputePipelineBaseCreateInfo();
genIndicesPci.add_spirv(std::vector<u32>{
#include "spv/instanceList/genIndices.comp.spv"
}, "instanceList/genIndices.comp");
_ptc.ctx.create_named_pipeline("instanceList/genIndices", genIndicesPci);
auto cullMeshletsPci = vuk::ComputePipelineBaseCreateInfo();
cullMeshletsPci.add_spirv(std::vector<u32>{
#include "spv/instanceList/cullMeshlets.comp.spv"
}, "instanceList/cullMeshlets.comp");
_ptc.ctx.create_named_pipeline("instanceList/cullMeshlets", cullMeshletsPci);
}
auto TriangleList::fromInstances(InstanceList _instances, Pool& _pool, Frame& _frame, vuk::Name _name,
Texture2D _hiZ, uvec2 _hiZInnerSize, mat4 _view, mat4 _projection) -> TriangleList {
auto result = TriangleList();
result.colors = _instances.colors;
result.transforms = _instances.transforms;
result.prevTransforms = _instances.prevTransforms;
result.instances = Buffer<Instance>::make(_pool, nameAppend(_name, "instances"),
vuk::BufferUsageFlagBits::eStorageBuffer,
_instances.size());
result.instances.attach(_frame.rg, vuk::eNone, vuk::eNone);
auto instanceCountData = uvec4{0, 1, 1, 0};
result.instanceCount = Buffer<uvec4>::make(_frame.framePool, nameAppend(_name, "instanceCount"),
vuk::BufferUsageFlagBits::eIndirectBuffer |
vuk::BufferUsageFlagBits::eStorageBuffer,
std::span(&instanceCountData, 1));
result.instanceCount.attach(_frame.rg, vuk::eHostWrite, vuk::eNone);
auto groupCounterData = 0u;
auto groupCounter = Buffer<u32>::make(_frame.framePool, nameAppend(_name, "groupCounter"),
vuk::BufferUsageFlagBits::eStorageBuffer,
std::span(&groupCounterData, 1));
auto invView = inverse(_view);
auto cameraPos = vec3{invView[3][0], invView[3][1], invView[3][2]};
_frame.rg.add_pass({
.name = nameAppend(_name, "instanceList/cullMeshlets"),
.resources = {
_instances.instances.resource(vuk::eComputeRead),
_instances.transforms.resource(vuk::eComputeRead),
_hiZ.resource(vuk::eComputeSampled),
result.instanceCount.resource(vuk::eComputeRW),
result.instances.resource(vuk::eComputeWrite) },
.execute = [&_frame, _instances, result, groupCounter, _view,
_hiZ, _hiZInnerSize, _projection](vuk::CommandBuffer& cmd) {
cmd.bind_storage_buffer(0, 1, _frame.models.meshlets)
.bind_storage_buffer(0, 2, _instances.instances)
.bind_storage_buffer(0, 3, _instances.transforms)
.bind_sampled_image(0, 4, _hiZ, MinClamp)
.bind_storage_buffer(0, 5, result.instanceCount)
.bind_storage_buffer(0, 6, result.instances)
.bind_storage_buffer(0, 7, groupCounter)
.bind_compute_pipeline("instanceList/cullMeshlets");
struct CullingData {
mat4 view;
vec4 frustum;
f32 P00;
f32 P11;
};
*cmd.map_scratch_uniform_binding<CullingData>(0, 0) = CullingData{
.view = _view,
.frustum = [_projection] {
auto projectionT = transpose(_projection);
vec4 frustumX = projectionT[3] + projectionT[0];
vec4 frustumY = projectionT[3] + projectionT[1];
frustumX /= length(vec3(frustumX));
frustumY /= length(vec3(frustumY));
return vec4{frustumX.x(), frustumX.z(), frustumY.y(), frustumY.z()};
}(),
.P00 = _projection[0][0],
.P11 = _projection[1][1] };
cmd.specialize_constants(0, tools::MeshletMaxTris);
cmd.specialize_constants(1, _projection[3][2]);
cmd.specialize_constants(2, u32Fromu16(_hiZ.size()));
cmd.specialize_constants(3, u32Fromu16(_hiZInnerSize));
cmd.push_constants(vuk::ShaderStageFlagBits::eCompute, 0, u32(_instances.size()));
cmd.dispatch_invocations(_instances.size());
}});
auto commandData = Command({
.indexCount = 0, // Calculated at runtime
.instanceCount = 1,
.firstIndex = 0,
.vertexOffset = 0,
.firstInstance = 0 });
result.command = Buffer<Command>::make(_frame.framePool, nameAppend(_name, "command"),
vuk::BufferUsageFlagBits::eIndirectBuffer |
vuk::BufferUsageFlagBits::eStorageBuffer,
std::span(&commandData, 1));
result.command.attach(_frame.rg, vuk::eHostWrite, vuk::eNone);
result.indices = Buffer<u32>::make(_pool, _name,
vuk::BufferUsageFlagBits::eIndexBuffer |
vuk::BufferUsageFlagBits::eStorageBuffer,
_instances.triangleCount * 3);
result.indices.attach(_frame.rg, vuk::eNone, vuk::eNone);
_frame.rg.add_pass({
.name = nameAppend(_name, "instanceList/genIndices"),
.resources = {
result.instanceCount.resource(vuk::eIndirectRead),
result.instances.resource(vuk::eComputeRead),
result.transforms.resource(vuk::eComputeRead),
result.command.resource(vuk::eComputeRW),
result.indices.resource(vuk::eComputeWrite) },
.execute = [result, &_frame, _view, cameraPos](vuk::CommandBuffer& cmd) {
cmd.bind_storage_buffer(0, 0, _frame.models.meshlets)
.bind_storage_buffer(0, 1, result.instances)
.bind_storage_buffer(0, 2, result.instanceCount)
.bind_storage_buffer(0, 3, result.transforms)
.bind_storage_buffer(0, 4, _frame.models.triIndices)
.bind_storage_buffer(0, 5, _frame.models.vertIndices)
.bind_storage_buffer(0, 6, _frame.models.vertices)
.bind_storage_buffer(0, 7, result.command)
.bind_storage_buffer(0, 8, result.indices)
.bind_compute_pipeline("instanceList/genIndices");
cmd.specialize_constants(0, tools::MeshletMaxTris);
cmd.push_constants(vuk::ShaderStageFlagBits::eCompute, 0, cameraPos);
cmd.dispatch_indirect(result.instanceCount);
}});
return result;
}
}

@ -0,0 +1,61 @@
#pragma once
#include "base/containers/array.hpp"
#include "base/types.hpp"
#include "base/math.hpp"
#include "gfx/resources/buffer.hpp"
#include "gfx/objects.hpp"
#include "gfx/frame.hpp"
namespace minote::gfx {
using namespace base;
using namespace base::literals;
struct InstanceList {
using Transform = array<vec4, 3>;
struct Instance {
u32 objectIdx;
u32 meshletIdx;
};
Buffer<vec4> colors;
Buffer<Transform> transforms;
Buffer<Transform> prevTransforms;
Buffer<Instance> instances;
u32 triangleCount;
static auto upload(Pool&, Frame&, vuk::Name, ObjectPool const&) -> InstanceList;
auto size() const -> usize { return instances.length(); }
};
struct TriangleList {
using Command = VkDrawIndexedIndirectCommand;
using Transform = InstanceList::Transform;
using Instance = InstanceList::Instance;
Buffer<vec4> colors;
Buffer<Transform> transforms;
Buffer<Transform> prevTransforms;
Buffer<Instance> instances;
Buffer<uvec4> instanceCount;
Buffer<Command> command;
Buffer<u32> indices;
static void compile(vuk::PerThreadContext&);
static auto fromInstances(InstanceList, Pool&, Frame&, vuk::Name,
Texture2D hiZ, uvec2 hiZInnerSize, mat4 view, mat4 projection) -> TriangleList;
};
}

@ -0,0 +1,73 @@
#include "gfx/effects/pbr.hpp"
#include <cassert>
#include "gfx/samplers.hpp"
#include "gfx/util.hpp"
namespace minote::gfx {
using namespace base::literals;
void PBR::compile(vuk::PerThreadContext& _ptc) {
auto pbrPci = vuk::ComputePipelineBaseCreateInfo();
pbrPci.add_spirv(std::vector<u32>{
#include "spv/pbr.comp.spv"
}, "pbr.comp");
_ptc.ctx.create_named_pipeline("pbr", pbrPci);
}
void PBR::apply(Frame& _frame, QuadBuffer& _quadbuf, Worklist _worklist,
TriangleList _triangles, Cubemap _ibl,
Buffer<vec3> _sunLuminance, Texture3D _aerialPerspective) {
_frame.rg.add_pass({
.name = nameAppend(_quadbuf.name, "pbr"),
.resources = {
_worklist.counts.resource(vuk::eIndirectRead),
_worklist.lists.resource(vuk::eComputeRead),
_triangles.indices.resource(vuk::eComputeRead),
_triangles.instances.resource(vuk::eComputeRead),
_triangles.colors.resource(vuk::eComputeRead),
_sunLuminance.resource(vuk::eComputeRead),
_aerialPerspective.resource(vuk::eComputeSampled),
_ibl.resource(vuk::eComputeSampled),
_quadbuf.visbuf.resource(vuk::eComputeSampled),
_quadbuf.offset.resource(vuk::eComputeSampled),
_quadbuf.depth.resource(vuk::eComputeSampled),
_quadbuf.normal.resource(vuk::eComputeSampled),
_quadbuf.clusterOut.resource(vuk::eComputeWrite) },
.execute = [_quadbuf, _worklist, &_frame, _triangles, _ibl,
_sunLuminance, _aerialPerspective,
tileCount=_worklist.counts.offsetView(+MaterialType::PBR)](vuk::CommandBuffer& cmd) {
cmd.bind_uniform_buffer(0, 0, _frame.world)
.bind_storage_buffer(0, 1, _frame.models.meshlets)
.bind_storage_buffer(0, 2, _triangles.indices)
.bind_storage_buffer(0, 3, _triangles.instances)
.bind_storage_buffer(0, 4, _triangles.colors)
.bind_storage_buffer(0, 5, _frame.models.materials)
.bind_uniform_buffer(0, 6, _sunLuminance)
.bind_sampled_image(0, 7, _ibl, TrilinearClamp)
.bind_sampled_image(0, 8, _aerialPerspective, TrilinearClamp)
.bind_sampled_image(0, 9, _quadbuf.visbuf, NearestClamp)
.bind_sampled_image(0, 10, _quadbuf.offset, NearestClamp)
.bind_sampled_image(0, 11, _quadbuf.depth, NearestClamp)
.bind_sampled_image(0, 12, _quadbuf.normal, NearestClamp)
.bind_storage_image(0, 13, _quadbuf.clusterOut)
.bind_storage_buffer(0, 14, _worklist.lists)
.bind_compute_pipeline("pbr");
cmd.specialize_constants(0, u32Fromu16({_aerialPerspective.size().x(), _aerialPerspective.size().y()}));
cmd.specialize_constants(1, _aerialPerspective.size().z());
cmd.specialize_constants(2, u32Fromu16(_quadbuf.clusterOut.size()));
cmd.specialize_constants(3, _worklist.tileDimensions.x() * _worklist.tileDimensions.y() * +MaterialType::PBR);
cmd.dispatch_indirect(tileCount);
}});
}
}

@ -0,0 +1,27 @@
#pragma once
#include "vuk/Context.hpp"
#include "gfx/resources/texture3d.hpp"
#include "gfx/resources/texture2d.hpp"
#include "gfx/resources/cubemap.hpp"
#include "gfx/effects/instanceList.hpp"
#include "gfx/effects/quadBuffer.hpp"
#include "gfx/effects/visibility.hpp"
#include "gfx/frame.hpp"
#include "base/math.hpp"
namespace minote::gfx {
using namespace base;
struct PBR {
// Build the shader.
static void compile(vuk::PerThreadContext&);
static void apply(Frame&, QuadBuffer&, Worklist, TriangleList,
Cubemap ibl, Buffer<vec3> sunLuminance, Texture3D aerialPerspective);
};
}

@ -0,0 +1,274 @@
#include "gfx/effects/quadBuffer.hpp"
#include "base/containers/array.hpp"
#include "gfx/effects/clear.hpp"
#include "gfx/samplers.hpp"
#include "gfx/util.hpp"
namespace minote::gfx {
void QuadBuffer::compile(vuk::PerThreadContext& _ptc) {
auto quadClusterizePci = vuk::ComputePipelineBaseCreateInfo();
quadClusterizePci.add_spirv(std::vector<u32>{
#include "spv/quad/clusterize.comp.spv"
}, "quadClusterize.comp");
_ptc.ctx.create_named_pipeline("quad/clusterize", quadClusterizePci);
auto quadGenBuffersPci = vuk::ComputePipelineBaseCreateInfo();
quadGenBuffersPci.add_spirv(std::vector<u32>{
#include "spv/quad/genBuffers.comp.spv"
}, "quad/genBuffers.comp");
_ptc.ctx.create_named_pipeline("quad/genBuffers", quadGenBuffersPci);
auto quadResolvePci = vuk::ComputePipelineBaseCreateInfo();
quadResolvePci.add_spirv(std::vector<u32>{
#include "spv/quad/resolve.comp.spv"
}, "quad/resolve.comp");
_ptc.ctx.create_named_pipeline("quad/resolve", quadResolvePci);
}
auto QuadBuffer::create(Pool& _pool, Frame& _frame,
vuk::Name _name, uvec2 _size, bool _flushTemporal) -> QuadBuffer {
auto result = QuadBuffer();
result.name = _name;
auto oddFrame = _pool.ptc().ctx.frame_counter.load() % 2;
auto quadDepths = to_array({
Texture2D::make(_pool, nameAppend(_name, "quadDepth0"),
divRoundUp(_size, 2u), vuk::Format::eR16Sfloat,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled |
vuk::ImageUsageFlagBits::eTransferDst),
Texture2D::make(_pool, nameAppend(_name, "quadDepth1"),
divRoundUp(_size, 2u), vuk::Format::eR16Sfloat,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled |
vuk::ImageUsageFlagBits::eTransferDst) });
result.quadDepth = quadDepths[oddFrame];
result.quadDepthPrev = quadDepths[!oddFrame];
auto outputs = to_array({
Texture2D::make(_pool, nameAppend(_name, "output0"),
_size, vuk::Format::eR16G16B16A16Sfloat,
vuk::ImageUsageFlagBits::eSampled |
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eTransferSrc |
vuk::ImageUsageFlagBits::eTransferDst),
Texture2D::make(_pool, nameAppend(_name, "output1"),
_size, vuk::Format::eR16G16B16A16Sfloat,
vuk::ImageUsageFlagBits::eSampled |
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eTransferSrc |
vuk::ImageUsageFlagBits::eTransferDst) });
result.output = outputs[oddFrame];
result.outputPrev = outputs[!oddFrame];
result.visbuf = Texture2D::make(_pool, nameAppend(_name, "visbuf"),
_size, vuk::Format::eR32Uint,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled);
result.subsamples = Texture2D::make(_pool, nameAppend(_name, "subsamples"),
_size, vuk::Format::eR32Uint,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled);
result.offset = Texture2D::make(_pool, nameAppend(_name, "offset"),
_size, vuk::Format::eR8G8Unorm,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled);
result.depth = Texture2D::make(_pool, nameAppend(_name, "depth"),
_size, vuk::Format::eR32Sfloat,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled);
result.jitterMap = Texture2D::make(_pool, nameAppend(_name, "jitterMap"),
divRoundUp(_size, 8u), vuk::Format::eR16Uint,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled |
vuk::ImageUsageFlagBits::eTransferDst);
result.quadDepthRepro = Texture2D::make(_pool, nameAppend(_name, "quadDepthRepro"),
divRoundUp(_size, 2u), vuk::Format::eR16Sfloat,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled);
result.clusterOut = Texture2D::make(_pool, nameAppend(_name, "clusterOut"),
_size, vuk::Format::eR16G16B16A16Sfloat,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled);
result.normal = Texture2D::make(_pool, nameAppend(_name, "normals"),
_size, vuk::Format::eR32Uint,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled);
result.velocity = Texture2D::make(_pool, nameAppend(_name, "velocity"),
_size, vuk::Format::eR16G16Sfloat,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled);
result.quadDepth.attach(_frame.rg, vuk::eNone, vuk::eComputeRead);
result.output.attach(_frame.rg, vuk::eNone, vuk::eTransferSrc);
if (_flushTemporal) {
result.quadDepthPrev.attach(_frame.rg, vuk::eNone, vuk::eNone);
result.outputPrev.attach(_frame.rg, vuk::eNone, vuk::eNone);
} else {
result.quadDepthPrev.attach(_frame.rg, vuk::eComputeRead, vuk::eNone);
result.outputPrev.attach(_frame.rg, vuk::eTransferSrc, vuk::eNone);
}
result.visbuf.attach(_frame.rg, vuk::eNone, vuk::eNone);
result.subsamples.attach(_frame.rg, vuk::eNone, vuk::eNone);
result.offset.attach(_frame.rg, vuk::eNone, vuk::eNone);
result.depth.attach(_frame.rg, vuk::eNone, vuk::eNone);
result.jitterMap.attach(_frame.rg, vuk::eNone, vuk::eNone);
result.quadDepthRepro.attach(_frame.rg, vuk::eNone, vuk::eNone);
result.clusterOut.attach(_frame.rg, vuk::eNone, vuk::eNone);
result.normal.attach(_frame.rg, vuk::eNone, vuk::eNone);
result.velocity.attach(_frame.rg, vuk::eNone, vuk::eNone);
Clear::apply(_frame, result.jitterMap, vuk::ClearColor(0u, 0u, 0u, 0u));
if (_flushTemporal) {
Clear::apply(_frame, result.quadDepthPrev, vuk::ClearColor(1.0f, 1.0f, 1.0f, 1.0f));
Clear::apply(_frame, result.outputPrev, vuk::ClearColor(0.0f, 0.0f, 0.0f, 0.0f));
}
return result;
}
void QuadBuffer::clusterize(Frame& _frame, QuadBuffer& _quadbuf, Texture2DMS _visbuf) {
_frame.rg.add_pass({
.name = nameAppend(_quadbuf.name, "quad/clusterize"),
.resources = {
_visbuf.resource(vuk::eComputeSampled),
_quadbuf.visbuf.resource(vuk::eComputeWrite),
_quadbuf.subsamples.resource(vuk::eComputeWrite),
_quadbuf.jitterMap.resource(vuk::eComputeWrite) },
.execute = [_quadbuf, _visbuf, &_frame](vuk::CommandBuffer& cmd) {
cmd.bind_uniform_buffer(0, 0, _frame.world)
.bind_sampled_image(0, 1, _visbuf, NearestClamp)
.bind_storage_image(0, 2, _quadbuf.visbuf)
.bind_storage_image(0, 3, _quadbuf.subsamples)
.bind_storage_image(0, 4, _quadbuf.jitterMap)
.bind_compute_pipeline("quad/clusterize");
auto invocationCount = _visbuf.size() / 2u + _visbuf.size() % 2u;
cmd.dispatch_invocations(invocationCount.x(), invocationCount.y());
}});
}
void QuadBuffer::genBuffers(Frame& _frame, QuadBuffer& _quadbuf, TriangleList _triangles) {
_frame.rg.add_pass({
.name = nameAppend(_quadbuf.name, "quad/genBuffers"),
.resources = {
_triangles.indices.resource(vuk::eComputeRead),
_triangles.instances.resource(vuk::eComputeRead),
_triangles.transforms.resource(vuk::eComputeRead),
_triangles.prevTransforms.resource(vuk::eComputeRead),
_quadbuf.visbuf.resource(vuk::eComputeSampled),
_quadbuf.subsamples.resource(vuk::eComputeSampled),
_quadbuf.offset.resource(vuk::eComputeWrite),
_quadbuf.depth.resource(vuk::eComputeWrite),
_quadbuf.quadDepth.resource(vuk::eComputeWrite),
_quadbuf.quadDepthRepro.resource(vuk::eComputeWrite),
_quadbuf.normal.resource(vuk::eComputeWrite),
_quadbuf.velocity.resource(vuk::eComputeWrite) },
.execute = [_quadbuf, &_frame, _triangles](vuk::CommandBuffer& cmd) {
cmd.bind_uniform_buffer(0, 0, _frame.world)
.bind_storage_buffer(0, 1, _frame.models.meshlets)
.bind_storage_buffer(0, 2, _triangles.instances)
.bind_storage_buffer(0, 3, _triangles.transforms)
.bind_storage_buffer(0, 4, _triangles.prevTransforms)
.bind_storage_buffer(0, 5, _triangles.indices)
.bind_storage_buffer(0, 6, _frame.models.vertIndices)
.bind_storage_buffer(0, 7, _frame.models.vertices)
.bind_storage_buffer(0, 8, _frame.models.normals)
.bind_sampled_image(0, 9, _quadbuf.visbuf, NearestClamp)
.bind_sampled_image(0, 10, _quadbuf.subsamples, NearestClamp)
.bind_storage_image(0, 11, _quadbuf.offset)
.bind_storage_image(0, 12, _quadbuf.depth)
.bind_storage_image(0, 13, _quadbuf.quadDepth)
.bind_storage_image(0, 14, _quadbuf.quadDepthRepro)
.bind_storage_image(0, 15, _quadbuf.normal)
.bind_storage_image(1, 0, _quadbuf.velocity)
.bind_compute_pipeline("quad/genBuffers");
cmd.specialize_constants(0, u32Fromu16(_quadbuf.visbuf.size()));
cmd.dispatch_invocations(divRoundUp(_quadbuf.visbuf.size().x(), 8u), divRoundUp(_quadbuf.visbuf.size().y(), 8u));
}});
}
void QuadBuffer::resolve(Frame& _frame, QuadBuffer& _quadbuf, Texture2D _output) {
_frame.rg.add_pass({
.name = nameAppend(_quadbuf.name, "quad/resolve"),
.resources = {
_quadbuf.subsamples.resource(vuk::eComputeSampled),
_quadbuf.jitterMap.resource(vuk::eComputeSampled),
_quadbuf.clusterOut.resource(vuk::eComputeSampled),
_quadbuf.outputPrev.resource(vuk::eComputeSampled),
_quadbuf.velocity.resource(vuk::eComputeSampled),
_quadbuf.quadDepthRepro.resource(vuk::eComputeSampled),
_quadbuf.quadDepthPrev.resource(vuk::eComputeSampled),
_quadbuf.output.resource(vuk::eComputeWrite) },
.execute = [_quadbuf, &_frame](vuk::CommandBuffer& cmd) {
cmd.bind_uniform_buffer(0, 0, _frame.world)
.bind_sampled_image(0, 1, _quadbuf.subsamples, NearestClamp)
.bind_sampled_image(0, 2, _quadbuf.jitterMap, NearestClamp)
.bind_sampled_image(0, 3, _quadbuf.clusterOut, NearestClamp)
.bind_sampled_image(0, 4, _quadbuf.outputPrev, LinearClamp)
.bind_sampled_image(0, 5, _quadbuf.velocity, LinearClamp)
.bind_sampled_image(0, 6, _quadbuf.quadDepthRepro, LinearClamp)
.bind_sampled_image(0, 7, _quadbuf.quadDepthPrev, LinearClamp)
.bind_storage_image(0, 8, _quadbuf.output)
.specialize_constants(0, u32Fromu16(_quadbuf.output.size()))
.bind_compute_pipeline("quad/resolve");
auto invocationCount = _quadbuf.output.size() / 2u + _quadbuf.output.size() % 2u;
cmd.dispatch_invocations(invocationCount.x(), invocationCount.y());
}});
_frame.rg.add_pass({
.name = nameAppend(_quadbuf.name, "copy"),
.resources = {
_quadbuf.output.resource(vuk::eTransferSrc),
_output.resource(vuk::eTransferDst) },
.execute = [_quadbuf, _output](vuk::CommandBuffer& cmd) {
cmd.blit_image(_quadbuf.output.name, _output.name, vuk::ImageBlit{
.srcSubresource = vuk::ImageSubresourceLayers{ .aspectMask = vuk::ImageAspectFlagBits::eColor },
.srcOffsets = {vuk::Offset3D{0, 0, 0}, vuk::Offset3D{i32(_output.size().x()), i32(_output.size().y()), 1}},
.dstSubresource = vuk::ImageSubresourceLayers{ .aspectMask = vuk::ImageAspectFlagBits::eColor },
.dstOffsets = {vuk::Offset3D{0, 0, 0}, vuk::Offset3D{i32(_output.size().x()), i32(_output.size().y()), 1}} },
vuk::Filter::eNearest);
}});
}
}

@ -0,0 +1,47 @@
#pragma once
#include "vuk/Context.hpp"
#include "gfx/resources/texture2dms.hpp"
#include "gfx/resources/texture2d.hpp"
#include "gfx/resources/buffer.hpp"
#include "gfx/resources/pool.hpp"
#include "gfx/effects/instanceList.hpp"
#include "gfx/frame.hpp"
namespace minote::gfx {
using namespace base;
struct QuadBuffer {
Texture2D quadDepth;
Texture2D output;
Texture2D quadDepthPrev;
Texture2D outputPrev;
Texture2D visbuf;
Texture2D subsamples;
Texture2D offset;
Texture2D depth;
Texture2D jitterMap;
Texture2D quadDepthRepro;
Texture2D clusterOut;
Texture2D normal;
Texture2D velocity;
vuk::Name name;
static void compile(vuk::PerThreadContext&);
static auto create(Pool&, Frame&, vuk::Name, uvec2 size, bool flushTemporal = false) -> QuadBuffer;
static void clusterize(Frame&, QuadBuffer&, Texture2DMS visbuf);
static void genBuffers(Frame&, QuadBuffer&, TriangleList);
static void resolve(Frame&, QuadBuffer&, Texture2D output);
};
}

@ -0,0 +1,365 @@
#include "gfx/effects/sky.hpp"
#include "vuk/CommandBuffer.hpp"
#include "base/containers/array.hpp"
#include "base/types.hpp"
#include "base/util.hpp"
#include "gfx/samplers.hpp"
#include "gfx/models.hpp"
#include "gfx/util.hpp"
namespace minote::gfx {
using namespace base;
using namespace base::literals;
auto Atmosphere::Params::earth() -> Params {
constexpr auto EarthRayleighScaleHeight = 8.0f;
constexpr auto EarthMieScaleHeight = 1.2f;
constexpr auto MieScattering = vec3{0.003996f, 0.003996f, 0.003996f};
constexpr auto MieExtinction = vec3{0.004440f, 0.004440f, 0.004440f};
return Params{
.BottomRadius = 6360.0f,
.TopRadius = 6460.0f,
.RayleighDensityExpScale = -1.0f / EarthRayleighScaleHeight,
.RayleighScattering = {0.005802f, 0.013558f, 0.033100f},
.MieDensityExpScale = -1.0f / EarthMieScaleHeight,
.MieScattering = MieScattering,
.MieExtinction = MieExtinction,
.MieAbsorption = max(MieExtinction - MieScattering, vec3(0.0f)),
.MiePhaseG = 0.8f,
.AbsorptionDensity0LayerWidth = 25.0f,
.AbsorptionDensity0ConstantTerm = -2.0f / 3.0f,
.AbsorptionDensity0LinearTerm = 1.0f / 15.0f,
.AbsorptionDensity1ConstantTerm = 8.0f / 3.0f,
.AbsorptionDensity1LinearTerm = -1.0f / 15.0f,
.AbsorptionExtinction = {0.000650f, 0.001881f, 0.000085f},
.GroundAlbedo = {0.0f, 0.0f, 0.0f} };
}
void Atmosphere::compile(vuk::PerThreadContext& _ptc) {
auto skyGenTransmittancePci = vuk::ComputePipelineBaseCreateInfo();
skyGenTransmittancePci.add_spirv(std::vector<u32>{
#include "spv/sky/genTransmittance.comp.spv"
}, "sky/genTransmittance.comp");
_ptc.ctx.create_named_pipeline("sky/genTransmittance", skyGenTransmittancePci);
auto skyGenMultiScatteringPci = vuk::ComputePipelineBaseCreateInfo();
skyGenMultiScatteringPci.add_spirv(std::vector<u32>{
#include "spv/sky/genMultiScattering.comp.spv"
}, "sky/genMultiScattering.comp");
_ptc.ctx.create_named_pipeline("sky/genMultiScattering", skyGenMultiScatteringPci);
}
auto Atmosphere::create(Pool& _pool, Frame& _frame, vuk::Name _name,
Params const& _params) -> Atmosphere {
auto result = Atmosphere();
bool calculate = !_pool.contains(nameAppend(_name, "transmittance"));
result.transmittance = Texture2D::make(_pool, nameAppend(_name, "transmittance"),
TransmittanceSize, TransmittanceFormat,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled);
result.multiScattering = Texture2D::make(_pool, nameAppend(_name, "multiScattering"),
MultiScatteringSize, MultiScatteringFormat,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled);
result.params = Buffer<Params>::make(_pool, nameAppend(_name, "params"),
vuk::BufferUsageFlagBits::eUniformBuffer,
std::span(&_params, 1));
if (calculate) {
result.transmittance.attach(_frame.rg, vuk::eNone, vuk::eComputeSampled);
result.multiScattering.attach(_frame.rg, vuk::eNone, vuk::eComputeSampled);
_frame.rg.add_pass({
.name = nameAppend(result.transmittance.name, "sky/genTransmittance"),
.resources = {
result.transmittance.resource(vuk::eComputeWrite) },
.execute = [result](vuk::CommandBuffer& cmd) {
cmd.bind_uniform_buffer(0, 0, result.params)
.bind_storage_image(0, 1, result.transmittance)
.bind_compute_pipeline("sky/genTransmittance");
cmd.specialize_constants(0, u32Fromu16(result.transmittance.size()));
cmd.dispatch_invocations(result.transmittance.size().x(), result.transmittance.size().y());
}});
_frame.rg.add_pass({
.name = nameAppend(result.multiScattering.name, "sky/genMultiScattering"),
.resources = {
result.transmittance.resource(vuk::eComputeSampled),
result.multiScattering.resource(vuk::eComputeWrite) },
.execute = [result](vuk::CommandBuffer& cmd) {
cmd.bind_uniform_buffer(0, 0, result.params)
.bind_sampled_image(0, 1, result.transmittance, LinearClamp)
.bind_storage_image(0, 2, result.multiScattering)
.bind_compute_pipeline("sky/genMultiScattering");
cmd.specialize_constants(0, u32Fromu16(result.multiScattering.size()));
cmd.dispatch_invocations(result.multiScattering.size().x(), result.multiScattering.size().y(), 1);
}});
}
return result;
}
void Sky::compile(vuk::PerThreadContext& _ptc) {
auto skyGenSkyViewPci = vuk::ComputePipelineBaseCreateInfo();
skyGenSkyViewPci.add_spirv(std::vector<u32>{
#include "spv/sky/genSkyView.comp.spv"
}, "sky/genSkyView.comp");
_ptc.ctx.create_named_pipeline("sky/genSkyView", skyGenSkyViewPci);
auto skyGenSunLuminancePci = vuk::ComputePipelineBaseCreateInfo();
skyGenSunLuminancePci.add_spirv(std::vector<u32>{
#include "spv/sky/genSunLuminance.comp.spv"
}, "sky/genSunLuminance.comp");
_ptc.ctx.create_named_pipeline("sky/genSunLuminance", skyGenSunLuminancePci);
auto skyDrawPci = vuk::ComputePipelineBaseCreateInfo();
skyDrawPci.add_spirv(std::vector<u32>{
#include "spv/sky/draw.comp.spv"
}, "sky/draw.comp");
_ptc.ctx.create_named_pipeline("sky/draw", skyDrawPci);
auto skyDrawCubemapPci = vuk::ComputePipelineBaseCreateInfo();
skyDrawCubemapPci.add_spirv(std::vector<u32>{
#include "spv/sky/drawCubemap.comp.spv"
}, "sky/drawCubemap.comp");
_ptc.ctx.create_named_pipeline("sky/drawCubemap", skyDrawCubemapPci);
auto skyAerialPerspectivePci = vuk::ComputePipelineBaseCreateInfo();
skyAerialPerspectivePci.add_spirv(std::vector<u32>{
#include "spv/sky/genAerialPerspective.comp.spv"
}, "sky/genAerialPerspective.comp");
_ptc.ctx.create_named_pipeline("sky/genAerialPerspective", skyAerialPerspectivePci);
}
auto Sky::createView(Pool& _pool, Frame& _frame, vuk::Name _name,
vec3 _probePos, Atmosphere _atmo) -> Texture2D {
auto view = Texture2D::make(_pool, _name,
ViewSize, ViewFormat,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled);
view.attach(_frame.rg, vuk::eNone, vuk::eNone);
_frame.rg.add_pass({
.name = nameAppend(_name, "sky/genSkyView"),
.resources = {
view.resource(vuk::eComputeWrite) },
.execute = [view, &_frame, _probePos, _atmo](vuk::CommandBuffer& cmd) {
struct PushConstants {
vec3 probePosition;
f32 pad0;
uvec2 skyViewSize;
};
cmd.bind_uniform_buffer(0, 0, _frame.world)
.bind_uniform_buffer(0, 1, _atmo.params)
.bind_sampled_image(0, 2, _atmo.transmittance, LinearClamp)
.bind_sampled_image(0, 3, _atmo.multiScattering, LinearClamp)
.bind_storage_image(0, 4, view)
.bind_compute_pipeline("sky/genSkyView");
cmd.push_constants(vuk::ShaderStageFlagBits::eCompute, 0, _probePos);
cmd.specialize_constants(0, u32Fromu16(view.size()));
cmd.dispatch_invocations(view.size().x(), view.size().y(), 1);
}});
return view;
}
auto Sky::createAerialPerspective(Pool& _pool, Frame& _frame, vuk::Name _name,
vec3 _probePos, mat4 _invViewProj, Atmosphere _atmo) -> Texture3D {
auto aerialPerspective = Texture3D::make(_pool, _name,
AerialPerspectiveSize, AerialPerspectiveFormat,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled);
aerialPerspective.attach(_frame.rg, vuk::eNone, vuk::eNone);
_frame.rg.add_pass({
.name = nameAppend(_name, "sky/genAerialPerspective"),
.resources = {
aerialPerspective.resource(vuk::eComputeWrite) },
.execute = [aerialPerspective, &_frame, _atmo, _invViewProj, _probePos](vuk::CommandBuffer& cmd) {
struct PushConstants {
mat4 invViewProj;
vec3 probePosition;
};
cmd.bind_uniform_buffer(0, 0, _frame.world)
.bind_uniform_buffer(0, 1, _atmo.params)
.bind_sampled_image(0, 2, _atmo.transmittance, LinearClamp)
.bind_sampled_image(0, 3, _atmo.multiScattering, LinearClamp)
.bind_storage_image(0, 4, aerialPerspective)
.bind_compute_pipeline("sky/genAerialPerspective");
cmd.push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants{
.invViewProj = _invViewProj,
.probePosition = _probePos });
cmd.specialize_constants(0, u32Fromu16({aerialPerspective.size().x(), aerialPerspective.size().y()}));
cmd.specialize_constants(1, aerialPerspective.size().z());
cmd.dispatch_invocations(aerialPerspective.size().x(), aerialPerspective.size().y(), aerialPerspective.size().z());
}});
return aerialPerspective;
}
auto Sky::createSunLuminance(Pool& _pool, Frame& _frame, vuk::Name _name,
vec3 _probePos, Atmosphere _atmo) -> Buffer<vec3> {
auto sunLuminance = Buffer<vec3>::make(_pool, _name,
vuk::BufferUsageFlagBits::eStorageBuffer | vuk::BufferUsageFlagBits::eUniformBuffer);
sunLuminance.attach(_frame.rg, vuk::eNone, vuk::eNone);
_frame.rg.add_pass({
.name = nameAppend(_name, "sky/genSunLuminance"),
.resources = {
sunLuminance.resource(vuk::eComputeWrite) },
.execute = [sunLuminance,&_frame, _atmo, _probePos](vuk::CommandBuffer& cmd) {
cmd.bind_uniform_buffer(0, 0, _frame.world)
.bind_uniform_buffer(0, 1, _atmo.params)
.bind_sampled_image(0, 2, _atmo.transmittance, LinearClamp)
.bind_storage_buffer(0, 3, sunLuminance)
.bind_compute_pipeline("sky/genSunLuminance");
cmd.push_constants(vuk::ShaderStageFlagBits::eCompute, 0, _probePos);
cmd.dispatch_invocations(1);
}});
return sunLuminance;
}
void Sky::draw(Frame& _frame, QuadBuffer& _quadbuf, Worklist _worklist,
Texture2D _skyView, Atmosphere _atmo) {
_frame.rg.add_pass({
.name = nameAppend(_quadbuf.name, "sky/draw"),
.resources = {
_skyView.resource(vuk::eComputeSampled),
_worklist.counts.resource(vuk::eIndirectRead),
_worklist.lists.resource(vuk::eComputeRead),
_quadbuf.visbuf.resource(vuk::eComputeSampled),
_quadbuf.offset.resource(vuk::eComputeSampled),
_quadbuf.clusterOut.resource(vuk::eComputeWrite) },
.execute = [_worklist, _skyView, _atmo, _quadbuf, &_frame,
tileCount=_worklist.counts.offsetView(+MaterialType::None)](vuk::CommandBuffer& cmd) {
cmd.bind_uniform_buffer(0, 0, _frame.world)
.bind_uniform_buffer(0, 1, _atmo.params)
.bind_sampled_image(0, 2, _atmo.transmittance, LinearClamp)
.bind_sampled_image(0, 3, _skyView, LinearClamp)
.bind_storage_buffer(0, 4, _worklist.lists)
.bind_sampled_image(0, 5, _quadbuf.visbuf, NearestClamp)
.bind_sampled_image(0, 6, _quadbuf.offset, NearestClamp)
.bind_storage_image(0, 7, _quadbuf.clusterOut)
.bind_compute_pipeline("sky/draw");
cmd.specialize_constants(0, u32Fromu16(_skyView.size()));
cmd.specialize_constants(1, u32Fromu16(_quadbuf.clusterOut.size()));
cmd.specialize_constants(2, _worklist.tileDimensions.x() * _worklist.tileDimensions.y() * +MaterialType::None);
cmd.dispatch_indirect(tileCount);
}});
}
void Sky::draw(Frame& _frame, Cubemap _target, vec3 _probePos, Texture2D _skyView, Atmosphere _atmo) {
_frame.rg.add_pass({
.name = nameAppend(_target.name, "sky/drawCubemap"),
.resources = {
_skyView.resource(vuk::eComputeSampled),
_target.resource(vuk::eComputeWrite) },
.execute = [_target, _skyView, _atmo, &_frame, _probePos](vuk::CommandBuffer& cmd) {
struct PushConstants {
vec3 probePosition;
u32 cubemapSize;
uvec2 skyViewSize;
};
cmd.bind_uniform_buffer(0, 0, _frame.world)
.bind_uniform_buffer(0, 1, _atmo.params)
.bind_sampled_image(0, 2, _atmo.transmittance, LinearClamp)
.bind_sampled_image(0, 3, _skyView, LinearClamp)
.bind_storage_image(0, 4, _target)
.bind_compute_pipeline("sky/drawCubemap");
auto* sides = cmd.map_scratch_uniform_binding<array<mat4, 6>>(0, 5);
*sides = to_array<mat4>({
mat4(mat3{
0.0f, 0.0f, -1.0f,
0.0f, -1.0f, 0.0f,
1.0f, 0.0f, 0.0f}),
mat4(mat3{
0.0f, 0.0f, 1.0f,
0.0f, -1.0f, 0.0f,
-1.0f, 0.0f, 0.0f}),
mat4(mat3{
1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f}),
mat4(mat3{
1.0f, 0.0f, 0.0f,
0.0f, 0.0f, -1.0f,
0.0f, -1.0f, 0.0f}),
mat4(mat3{
1.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f,
0.0f, 0.0f, 1.0f}),
mat4(mat3{
-1.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f,
0.0f, 0.0f, -1.0f})});
cmd.push_constants(vuk::ShaderStageFlagBits::eCompute, 0, _probePos);
cmd.specialize_constants(0, u32Fromu16(_skyView.size()));
cmd.specialize_constants(1, _target.size().x());
cmd.dispatch_invocations(_target.size().x(), _target.size().y(), 6);
}});
}
}

@ -0,0 +1,105 @@
#pragma once
#include "vuk/Context.hpp"
#include "base/math.hpp"
#include "gfx/resources/texture3d.hpp"
#include "gfx/resources/texture2d.hpp"
#include "gfx/resources/cubemap.hpp"
#include "gfx/resources/buffer.hpp"
#include "gfx/resources/pool.hpp"
#include "gfx/effects/quadBuffer.hpp"
#include "gfx/effects/visibility.hpp"
#include "gfx/camera.hpp"
#include "gfx/frame.hpp"
namespace minote::gfx {
using namespace base;
using namespace base::literals;
// Precalculated representation of a planet's atmosphere. Once created, it can
// be used repeatedly to sample the sky at any elevation and sun position.
struct Atmosphere {
constexpr static auto TransmittanceFormat = vuk::Format::eR16G16B16A16Sfloat;
constexpr static auto TransmittanceSize = uvec2{256u, 64u};
constexpr static auto MultiScatteringFormat = vuk::Format::eR16G16B16A16Sfloat;
constexpr static auto MultiScatteringSize = uvec2{32u, 32u};
struct Params {
float BottomRadius; // Radius of the planet (center to ground)
float TopRadius; // Maximum considered atmosphere height (center to atmosphere top)
float RayleighDensityExpScale; // Rayleigh scattering exponential distribution scale in the atmosphere
float pad0;
vec3 RayleighScattering; // Rayleigh scattering coefficients
float MieDensityExpScale; // Mie scattering exponential distribution scale in the atmosphere
vec3 MieScattering; // Mie scattering coefficients
float pad1;
vec3 MieExtinction; // Mie extinction coefficients
float pad2;
vec3 MieAbsorption; // Mie absorption coefficients
float MiePhaseG; // Mie phase function excentricity
// Another medium type in the atmosphere
float AbsorptionDensity0LayerWidth;
float AbsorptionDensity0ConstantTerm;
float AbsorptionDensity0LinearTerm;
float AbsorptionDensity1ConstantTerm;
float AbsorptionDensity1LinearTerm;
float pad3;
float pad4;
float pad5;
vec3 AbsorptionExtinction; // This other medium only absorb light, e.g. useful to represent ozone in the earth atmosphere
float pad6;
vec3 GroundAlbedo;
// Return params that model Earth's atmosphere
static auto earth() -> Params;
};
Texture2D transmittance;
Texture2D multiScattering;
Buffer<Params> params;
// Build required shaders.
static void compile(vuk::PerThreadContext&);
static auto create(Pool&, Frame&, vuk::Name, Params const&) -> Atmosphere;
};
// Effect for rendering sky backgrounds, IBL cubemaps and other position-dependent
// lookup tables.
struct Sky {
constexpr static auto ViewFormat = vuk::Format::eB10G11R11UfloatPack32;
constexpr static auto ViewSize = uvec2{192u, 108u};
constexpr static auto AerialPerspectiveFormat = vuk::Format::eR16G16B16A16Sfloat;
constexpr static auto AerialPerspectiveSize = uvec3{32u, 32u, 32u};
// Build the shader.
static void compile(vuk::PerThreadContext&);
static auto createView(Pool&, Frame&, vuk::Name, vec3 probePos, Atmosphere) -> Texture2D;
static auto createAerialPerspective(Pool&, Frame&, vuk::Name,
vec3 probePos, mat4 invViewProj, Atmosphere) -> Texture3D;
static auto createSunLuminance(Pool&, Frame&, vuk::Name,
vec3 probePos, Atmosphere) -> Buffer<vec3>;
static void draw(Frame&, QuadBuffer&, Worklist, Texture2D skyView, Atmosphere);
// Draw the sky into an existing cubemap. Target is the mip 0 of provided image.
static void draw(Frame&, Cubemap target, vec3 probePos, Texture2D skyView, Atmosphere);
};
}

@ -0,0 +1,43 @@
#include "gfx/effects/tonemap.hpp"
#include "vuk/CommandBuffer.hpp"
#include "base/types.hpp"
#include "gfx/samplers.hpp"
#include "gfx/util.hpp"
namespace minote::gfx {
using namespace base;
void Tonemap::compile(vuk::PerThreadContext& _ptc) {
auto tonemapPci = vuk::ComputePipelineBaseCreateInfo();
tonemapPci.add_spirv(std::vector<u32>{
#include "spv/tonemap.comp.spv"
}, "tonemap.comp");
_ptc.ctx.create_named_pipeline("tonemap", tonemapPci);
}
void Tonemap::apply(Frame& _frame, Texture2D _source, Texture2D _target) {
_frame.rg.add_pass({
.name = nameAppend(_source.name, "tonemapping"),
.resources = {
_source.resource(vuk::eComputeSampled),
_target.resource(vuk::eComputeWrite) },
.execute = [_source, _target](vuk::CommandBuffer& cmd) {
cmd.bind_sampled_image(0, 0, _source, NearestClamp)
.bind_storage_image(0, 1, _target)
.bind_compute_pipeline("tonemap");
cmd.specialize_constants(0, u32Fromu16(_target.size()));
cmd.dispatch_invocations(_target.size().x(), _target.size().y());
}});
}
}

@ -0,0 +1,21 @@
#pragma once
#include "vuk/Context.hpp"
#include "gfx/resources/texture2d.hpp"
#include "gfx/frame.hpp"
namespace minote::gfx {
using namespace base;
struct Tonemap {
// Build the shader.
static void compile(vuk::PerThreadContext&);
// Perform tonemaping from source to target. The target image is not created.
static void apply(Frame&, Texture2D source, Texture2D target);
};
}

@ -0,0 +1,135 @@
#include "gfx/effects/visibility.hpp"
#include "vuk/CommandBuffer.hpp"
#include "base/containers/string.hpp"
#include "base/util.hpp"
#include "gfx/samplers.hpp"
#include "gfx/util.hpp"
namespace minote::gfx {
using namespace base;
using namespace base::literals;
void Visibility::compile(vuk::PerThreadContext& _ptc) {
auto visibilityPci = vuk::PipelineBaseCreateInfo();
visibilityPci.add_spirv(std::vector<u32>{
#include "spv/visibility/visbuf.vert.spv"
}, "visibility/visbuf.vert");
visibilityPci.add_spirv(std::vector<u32>{
#include "spv/visibility/visbuf.frag.spv"
}, "visibility/visbuf.frag");
_ptc.ctx.create_named_pipeline("visibility/visbuf", visibilityPci);
}
void Visibility::apply(Frame& _frame, Texture2DMS _visbuf, Texture2DMS _depth,
TriangleList _triangles) {
_frame.rg.add_pass({
.name = nameAppend(_visbuf.name, "visibility/visbuf"),
.resources = {
_triangles.command.resource(vuk::eIndirectRead),
_triangles.indices.resource(vuk::eIndexRead),
_triangles.instances.resource(vuk::eVertexRead),
_triangles.transforms.resource(vuk::eVertexRead),
_visbuf.resource(vuk::eColorWrite),
_depth.resource(vuk::eDepthStencilRW) },
.execute = [_visbuf, _triangles, &_frame](vuk::CommandBuffer& cmd) {
cmd.set_viewport(0, vuk::Rect2D::framebuffer());
cmd.set_scissor(0, vuk::Rect2D::framebuffer());
cmd.set_color_blend(_visbuf.name, vuk::BlendPreset::eOff);
cmd.set_rasterization(vuk::PipelineRasterizationStateCreateInfo{
// .cullMode = vuk::CullModeFlagBits::eNone });
.cullMode = vuk::CullModeFlagBits::eBack });
cmd.set_depth_stencil(vuk::PipelineDepthStencilStateCreateInfo{
.depthTestEnable = true,
.depthWriteEnable = true,
.depthCompareOp = vuk::CompareOp::eGreater });
cmd.bind_index_buffer(_triangles.indices, vuk::IndexType::eUint32)
.bind_uniform_buffer(0, 0, _frame.world)
.bind_storage_buffer(0, 1, _frame.models.vertIndices)
.bind_storage_buffer(0, 2, _frame.models.vertices)
.bind_storage_buffer(0, 3, _frame.models.meshlets)
.bind_storage_buffer(0, 4, _triangles.instances)
.bind_storage_buffer(0, 5, _triangles.transforms)
.bind_graphics_pipeline("visibility/visbuf");
cmd.draw_indexed_indirect(1, _triangles.command);
}});
}
void Worklist::compile(vuk::PerThreadContext& _ptc) {
auto worklistPci = vuk::ComputePipelineBaseCreateInfo();
worklistPci.add_spirv(std::vector<u32>{
#include "spv/visibility/worklist.comp.spv"
}, "visibility/worklist.comp");
_ptc.ctx.create_named_pipeline("visibility/worklist", worklistPci);
}
auto Worklist::create(Pool& _pool, Frame& _frame, vuk::Name _name,
Texture2D _visbuf, TriangleList _triangles) -> Worklist {
auto result = Worklist();
result.tileDimensions = uvec2{
u32(ceil(f32(_visbuf.size().x()) / f32(TileSize.x()))),
u32(ceil(f32(_visbuf.size().y()) / f32(TileSize.y()))) };
// Create buffers
constexpr auto InitialCount = uvec4{0, 1, 1, 0};
auto initialCounts = array<uvec4, ListCount>();
initialCounts.fill(InitialCount);
result.counts = Buffer<uvec4>::make(_frame.framePool, nameAppend(_name, "counts"),
vuk::BufferUsageFlagBits::eStorageBuffer |
vuk::BufferUsageFlagBits::eIndirectBuffer,
initialCounts);
result.lists = Buffer<u32>::make(_pool, nameAppend(_name, "tiles"),
vuk::BufferUsageFlagBits::eStorageBuffer,
usize(result.tileDimensions.x() * result.tileDimensions.y()) * ListCount);
result.counts.attach(_frame.rg, vuk::eHostWrite, vuk::eNone);
result.lists.attach(_frame.rg, vuk::eNone, vuk::eNone);
// Generate worklists
_frame.rg.add_pass({
.name = nameAppend(_name, "visibility/worklist"),
.resources = {
_visbuf.resource(vuk::eComputeSampled),
_triangles.instances.resource(vuk::eComputeRead),
result.counts.resource(vuk::eComputeRW),
result.lists.resource(vuk::eComputeWrite) },
.execute = [result, _visbuf, _triangles, &_frame](vuk::CommandBuffer& cmd) {
cmd.bind_sampled_image(0, 0, _visbuf, NearestClamp)
.bind_storage_buffer(0, 1, _triangles.indices)
.bind_storage_buffer(0, 2, _triangles.instances)
.bind_storage_buffer(0, 3, _frame.models.meshlets)
.bind_storage_buffer(0, 4, _frame.models.materials)
.bind_storage_buffer(0, 5, result.counts)
.bind_storage_buffer(0, 6, result.lists)
.bind_compute_pipeline("visibility/worklist");
cmd.specialize_constants(0, u32Fromu16(_visbuf.size()));
cmd.specialize_constants(1, ListCount);
cmd.dispatch_invocations(_visbuf.size().x(), _visbuf.size().y());
}});
return result;
}
}

@ -0,0 +1,45 @@
#pragma once
#include "vuk/Context.hpp"
#include "base/types.hpp"
#include "base/math.hpp"
#include "gfx/resources/texture2dms.hpp"
#include "gfx/resources/texture2d.hpp"
#include "gfx/resources/buffer.hpp"
#include "gfx/resources/pool.hpp"
#include "gfx/effects/instanceList.hpp"
#include "gfx/models.hpp"
#include "gfx/frame.hpp"
#include "gfx/util.hpp"
namespace minote::gfx {
using namespace base;
using namespace base::literals;
struct Visibility {
// Build the shader.
static void compile(vuk::PerThreadContext&);
static void apply(Frame&, Texture2DMS visbuf, Texture2DMS depth, TriangleList);
};
struct Worklist {
static constexpr auto TileSize = vec2{8, 8};
static constexpr auto ListCount = +MaterialType::Count;
Buffer<uvec4> counts; // x holds tile count, yz are 1 (for dispatch indirect)
Buffer<u32> lists;
uvec2 tileDimensions; // How many tiles fit in each dimension
// Build the shader.
static void compile(vuk::PerThreadContext&);
static auto create(Pool&, Frame&, vuk::Name, Texture2D visbuf, TriangleList) -> Worklist;
};
}

@ -0,0 +1,281 @@
#include "gfx/engine.hpp"
#include "config.hpp"
#include <cassert>
#include "volk.h"
#include "vuk/CommandBuffer.hpp"
#include "vuk/RenderGraph.hpp"
#include "base/error.hpp"
#include "base/math.hpp"
#include "base/log.hpp"
#include "sys/system.hpp"
#include "gfx/resources/texture2d.hpp"
#include "gfx/effects/instanceList.hpp"
#include "gfx/effects/quadbuffer.hpp"
#include "gfx/effects/cubeFilter.hpp"
#include "gfx/effects/visibility.hpp"
#include "gfx/effects/tonemap.hpp"
#include "gfx/effects/bloom.hpp"
#include "gfx/effects/bvh.hpp"
#include "gfx/effects/pbr.hpp"
#include "gfx/effects/sky.hpp"
#include "gfx/effects/hiz.hpp"
#include "gfx/frame.hpp"
#include "gfx/util.hpp"
#include "main.hpp"
namespace minote::gfx {
using namespace base;
using namespace std::string_literals;
static constexpr auto compileOpts = vuk::RenderGraph::CompileOptions{
.reorder_passes = false,
#if VK_VALIDATION
// .check_pass_ordering = true, // Temporarily disabled
#endif //VK_VALIDATION
};
Engine::~Engine() {
m_vk.context->wait_idle();
m_imguiData.fontTex.view.reset();
m_imguiData.fontTex.image.reset();
L_INFO("Graphics engine cleaned up");
}
void Engine::init(ModelList&& _modelList) {
auto ifc = m_vk.context->begin();
auto ptc = ifc.begin();
m_permPool.setPtc(ptc);
TriangleList::compile(ptc);
QuadBuffer::compile(ptc);
CubeFilter::compile(ptc);
Atmosphere::compile(ptc);
Visibility::compile(ptc);
Worklist::compile(ptc);
Tonemap::compile(ptc);
Bloom::compile(ptc);
BVH::compile(ptc);
PBR::compile(ptc);
HiZ::compile(ptc);
Sky::compile(ptc);
m_swapchainDirty = false;
m_flushTemporalResources = true;
m_imguiData = ImGui_ImplVuk_Init(ptc);
ImGui::GetIO().DisplaySize = ImVec2(
f32(m_vk.swapchain->extent.width),
f32(m_vk.swapchain->extent.height));
// Begin imgui frame so that first-frame calls succeed
ImGui::NewFrame();
m_models = std::move(_modelList).upload(m_permPool, "models");
// Finalize
ptc.wait_all_transfers();
L_INFO("Graphics engine initialized");
}
void Engine::render() {
// Quit if repaint needed
if (m_swapchainDirty) return;
// Lock the renderer
m_renderLock.lock();
defer { m_renderLock.unlock(); };
// Framerate calculation
m_framesSinceLastCheck += 1;
auto currentTime = sys::System::getTime();
auto timeElapsed = currentTime - m_lastFramerateCheck;
if (timeElapsed >= 1_s) {
auto secondsElapsed = ratio(timeElapsed, 1_s);
m_framerate = f32(m_framesSinceLastCheck) / secondsElapsed;
m_lastFramerateCheck = currentTime;
m_framesSinceLastCheck = 0;
}
ImGui::Text("FPS: %.1f", m_framerate);
// Prepare per-frame data
// Basic scene properties
if (!m_flushTemporalResources)
m_world.prevViewProjection = m_world.viewProjection;
auto viewport = uvec2{m_vk.swapchain->extent.width, m_vk.swapchain->extent.height};
m_world.projection = perspective(VerticalFov, f32(viewport.x()) / f32(viewport.y()), NearPlane);
m_world.view = m_camera.transform();
m_world.viewProjection = m_world.projection * m_world.view;
m_world.viewProjectionInverse = inverse(m_world.viewProjection);
m_world.viewportSize = viewport;
m_world.nearPlane = NearPlane;
m_world.cameraPos = m_camera.position;
m_world.frameCounter = m_vk.context->frame_counter.load();
if (m_flushTemporalResources)
m_world.prevViewProjection = m_world.viewProjection;
// Sun properties
static auto sunPitch = 16_deg;
static auto sunYaw = 8_deg;
ImGui::SliderAngle("Sun pitch", &sunPitch, -8.0f, 60.0f, "%.1f deg", ImGuiSliderFlags_NoRoundToFormat);
ImGui::SliderAngle("Sun yaw", &sunYaw, -180.0f, 180.0f, nullptr, ImGuiSliderFlags_NoRoundToFormat);
m_world.sunDirection =
mat3::rotate({0.0f, 0.0f, 1.0f}, sunYaw) *
mat3::rotate({0.0f, -1.0f, 0.0f}, sunPitch) *
vec3{1.0f, 0.0f, 0.0f};
static auto sunIlluminance = 4.0f;
ImGui::SliderFloat("Sun illuminance", &sunIlluminance, 0.01f, 100.0f, nullptr, ImGuiSliderFlags_Logarithmic | ImGuiSliderFlags_NoRoundToFormat);
m_world.sunIlluminance = vec3(sunIlluminance);
// Prepare frame
auto ifc = m_vk.context->begin();
auto ptc = ifc.begin();
m_permPool.setPtc(ptc);
m_framePool.setPtc(ptc);
m_swapchainPool.setPtc(ptc);
auto rg = vuk::RenderGraph();
// Create main rendering destination
auto screen = Texture2D::make(m_swapchainPool, "screen",
viewport, vuk::Format::eR8G8B8A8Unorm,
vuk::ImageUsageFlagBits::eTransferSrc |
vuk::ImageUsageFlagBits::eColorAttachment |
vuk::ImageUsageFlagBits::eStorage);
screen.attach(rg, vuk::eNone, vuk::eNone);
// Draw frame
auto frame = Frame(*this, rg);
frame.draw(screen, m_objects, m_flushTemporalResources);
ImGui::Render();
ImGui_ImplVuk_Render(m_framePool, ptc, rg, screen.name, m_imguiData, ImGui::GetDrawData());
// Blit frame to swapchain
rg.attach_swapchain("swapchain", m_vk.swapchain, vuk::ClearColor(0.0f, 0.0f, 0.0f, 0.0f));
rg.add_pass({
.name = "swapchain copy",
.resources = {
screen.resource(vuk::eTransferSrc),
"swapchain"_image(vuk::eTransferDst) },
.execute = [screen](vuk::CommandBuffer& cmd) {
cmd.blit_image(screen.name, "swapchain", vuk::ImageBlit{
.srcSubresource = vuk::ImageSubresourceLayers{ .aspectMask = vuk::ImageAspectFlagBits::eColor },
.srcOffsets = {vuk::Offset3D{0, 0, 0}, vuk::Offset3D{i32(screen.size().x()), i32(screen.size().y()), 1}},
.dstSubresource = vuk::ImageSubresourceLayers{ .aspectMask = vuk::ImageAspectFlagBits::eColor },
.dstOffsets = {vuk::Offset3D{0, 0, 0}, vuk::Offset3D{i32(screen.size().x()), i32(screen.size().y()), 1}} },
vuk::Filter::eNearest);
}});
// Acquire swapchain image
auto presentSem = ptc.acquire_semaphore();
auto swapchainImageIndex = u32(0);
{
auto error = vkAcquireNextImageKHR(m_vk.device.device, m_vk.swapchain->swapchain,
UINT64_MAX, presentSem, VK_NULL_HANDLE, &swapchainImageIndex);
if (error == VK_ERROR_OUT_OF_DATE_KHR) {
m_swapchainDirty = true;
return; // Requires repaint
}
if (error != VK_SUCCESS && error != VK_SUBOPTIMAL_KHR) // Unknown result
throw runtime_error_fmt("Unable to acquire swapchain image: error {}", error);
}
// Build and submit the rendergraph
auto erg = std::move(rg).link(ptc, compileOpts);
auto commandBuffer = erg.execute(ptc, {{m_vk.swapchain, swapchainImageIndex}});
auto renderSem = ptc.acquire_semaphore();
auto waitStage = VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
auto submitInfo = VkSubmitInfo{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &presentSem,
.pWaitDstStageMask = &waitStage,
.commandBufferCount = 1,
.pCommandBuffers = &commandBuffer,
.signalSemaphoreCount = 1,
.pSignalSemaphores = &renderSem};
m_vk.context->submit_graphics(submitInfo, ptc.acquire_fence());
// Present to screen
{
auto presentInfo = VkPresentInfoKHR{
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &renderSem,
.swapchainCount = 1,
.pSwapchains = &m_vk.swapchain->swapchain,
.pImageIndices = &swapchainImageIndex};
auto result = vkQueuePresentKHR(m_vk.context->graphics_queue, &presentInfo);
if (result == VK_ERROR_OUT_OF_DATE_KHR)
m_swapchainDirty = true; // No need to return, only cleanup is left
else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR)
throw runtime_error_fmt("Unable to present to the screen: error {}", result);
}
// Clean up
m_flushTemporalResources = false;
ImGui::NewFrame();
m_framePool.reset();
m_objects.copyTransforms();
}
void Engine::refreshSwapchain(uvec2 _newSize) {
auto lock = std::lock_guard(m_renderLock);
for (auto iv: m_vk.swapchain->image_views)
m_vk.context->enqueue_destroy(iv);
auto newSwapchain = m_vk.context->add_swapchain(m_vk.createSwapchain(_newSize, m_vk.swapchain->swapchain));
m_vk.context->enqueue_destroy(m_vk.swapchain->swapchain);
m_vk.context->remove_swapchain(m_vk.swapchain);
m_vk.swapchain = newSwapchain;
m_swapchainDirty = false;
m_swapchainPool.reset();
m_flushTemporalResources = true;
ImGui::GetIO().DisplaySize = ImVec2(
f32(m_vk.swapchain->extent.width),
f32(m_vk.swapchain->extent.height));
}
}

@ -0,0 +1,88 @@
#pragma once
#include <optional>
#include <mutex>
#include "base/math.hpp"
#include "base/time.hpp"
#include "sys/vulkan.hpp"
#include "gfx/resources/cubemap.hpp"
#include "gfx/resources/pool.hpp"
#include "gfx/objects.hpp"
#include "gfx/models.hpp"
#include "gfx/camera.hpp"
#include "gfx/world.hpp"
#include "gfx/imgui.hpp"
namespace minote::gfx {
using namespace base;
using namespace base::literals;
// Graphics engine. Feed with models and objects, enjoy pretty pictures.
struct Engine {
static constexpr auto VerticalFov = 50_deg;
static constexpr auto NearPlane = 0.1_m;
// Create the engine in uninitialized state.
Engine(sys::Vulkan& vk):
m_vk(vk),
m_framerate(60.0f),
m_lastFramerateCheck(0),
m_framesSinceLastCheck(0) {}
~Engine();
void init(ModelList&&);
// Render all objects to the screen. If repaint is false, the function will
// only render it no other thread is currently rendering. Otherwise it will
// block until a frame can be successfully renderered. To avoid deadlock,
// only ever use repaint=true on one thread.
void render();
// Use this function when the surface is resized to recreate the swapchain.
void refreshSwapchain(uvec2 newSize);
// Subcomponent access
// Use freely to add/remove/modify objects for drawing
auto objects() -> ObjectPool& { return m_objects; }
// Use freely to modify the rendering camera
auto camera() -> Camera& { return m_camera; }
auto fps() const -> f32 { return m_framerate; }
// Not copyable, not movable
Engine(Engine const&) = delete;
auto operator=(Engine const&) -> Engine& = delete;
Engine(Engine&&) = delete;
auto operator=(Engine&&) -> Engine& = delete;
private:
sys::Vulkan& m_vk;
std::mutex m_renderLock;
bool m_swapchainDirty;
bool m_flushTemporalResources;
f32 m_framerate;
nsec m_lastFramerateCheck;
u32 m_framesSinceLastCheck;
ImguiData m_imguiData;
ModelBuffer m_models;
ObjectPool m_objects;
World m_world;
Camera m_camera;
Pool m_permPool;
Pool m_framePool;
Pool m_swapchainPool;
friend struct Frame;
};
}

@ -0,0 +1,128 @@
#include "gfx/frame.hpp"
#include "gfx/effects/instanceList.hpp"
#include "gfx/effects/quadbuffer.hpp"
#include "gfx/effects/cubeFilter.hpp"
#include "gfx/effects/visibility.hpp"
#include "gfx/effects/tonemap.hpp"
#include "gfx/effects/bloom.hpp"
#include "gfx/effects/clear.hpp"
#include "gfx/effects/bvh.hpp"
#include "gfx/effects/pbr.hpp"
#include "gfx/effects/sky.hpp"
#include "gfx/effects/hiz.hpp"
#include "base/math.hpp"
namespace minote::gfx {
using namespace base;
Frame::Frame(Engine& _engine, vuk::RenderGraph& _rg):
ptc(_engine.m_framePool.ptc()),
rg(_rg),
framePool(_engine.m_framePool),
swapchainPool(_engine.m_swapchainPool),
permPool(_engine.m_permPool),
models(_engine.m_models),
cpu_world(_engine.m_world) {}
void Frame::draw(Texture2D _target, ObjectPool& _objects, bool _flush) {
// Upload resources
world = cpu_world.upload(framePool, "world");
auto instances = InstanceList::upload(framePool, *this, "instances", _objects);
auto atmosphere = Atmosphere::create(permPool, *this, "earth", Atmosphere::Params::earth());
// Even size simplifies quad-based effects
auto viewport = uvec2{u32(alignPOT(_target.size().x(), 2u)), u32(alignPOT(_target.size().y(), 2u))};
// Create textures
auto iblUnfiltered = Cubemap::make(permPool, "iblUnfiltered",
256, vuk::Format::eR16G16B16A16Sfloat,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled |
vuk::ImageUsageFlagBits::eTransferSrc);
auto iblFiltered = Cubemap::make(permPool, "iblFiltered",
256, vuk::Format::eR16G16B16A16Sfloat,
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eSampled |
vuk::ImageUsageFlagBits::eTransferDst);
iblUnfiltered.attach(rg, vuk::eNone, vuk::eNone);
iblFiltered.attach(rg, vuk::eNone, vuk::eNone);
constexpr auto IblProbePosition = vec3{0_m, 0_m, 64_m};
auto color = Texture2D::make(swapchainPool, "color",
viewport, vuk::Format::eR16G16B16A16Sfloat,
vuk::ImageUsageFlagBits::eSampled |
vuk::ImageUsageFlagBits::eStorage |
vuk::ImageUsageFlagBits::eColorAttachment |
vuk::ImageUsageFlagBits::eTransferDst);
color.attach(rg, vuk::eNone, vuk::eNone);
auto visbuf = Texture2DMS::make(swapchainPool, "visbuf",
viewport, vuk::Format::eR32Uint,
vuk::ImageUsageFlagBits::eColorAttachment |
vuk::ImageUsageFlagBits::eSampled,
vuk::Samples::e8);
visbuf.attach(rg, vuk::eClear, vuk::eNone, vuk::ClearColor(-1u, -1u, -1u, -1u));
auto depth = Texture2DMS::make(swapchainPool, "depth",
viewport, vuk::Format::eD32Sfloat,
vuk::ImageUsageFlagBits::eDepthStencilAttachment |
vuk::ImageUsageFlagBits::eSampled,
vuk::Samples::e8);
depth.attach(rg, vuk::eClear, vuk::eNone, vuk::ClearDepthStencil(0.0f, 0));
auto hiz = HiZ::make(swapchainPool, "hiz", depth);
if (_flush) {
hiz.attach(rg, vuk::eNone, vuk::eComputeWrite);
Clear::apply(*this, hiz, vuk::ClearColor(1.0f, 1.0f, 1.0f, 1.0f));
} else {
hiz.attach(rg, vuk::eComputeWrite, vuk::eComputeWrite);
}
auto quadbuf = QuadBuffer::create(swapchainPool, *this, "quadbuf", viewport, _flush);
// Create rendering passes
// Instance list processing
auto screenTriangles = TriangleList::fromInstances(instances, framePool, *this, "screenTriangles",
hiz, depth.size(), cpu_world.view, cpu_world.projection);
// Sky generation
auto cameraSky = Sky::createView(permPool, *this, "cameraSky", cpu_world.cameraPos, atmosphere);
auto cubeSky = Sky::createView(permPool, *this, "cubeSky", IblProbePosition, atmosphere);
auto aerialPerspective = Sky::createAerialPerspective(permPool, *this, "aerialPerspective",
cpu_world.cameraPos, cpu_world.viewProjectionInverse, atmosphere);
auto sunLuminance = Sky::createSunLuminance(permPool, *this, "sunLuminance", cpu_world.cameraPos, atmosphere);
// IBL generation
Sky::draw(*this, iblUnfiltered, IblProbePosition, cubeSky, atmosphere);
CubeFilter::apply(*this, iblUnfiltered, iblFiltered);
// Drawing
Visibility::apply(*this, visbuf, depth, screenTriangles);
QuadBuffer::clusterize(*this, quadbuf, visbuf);
QuadBuffer::genBuffers(*this, quadbuf, screenTriangles);
auto worklist = Worklist::create(swapchainPool, *this, "worklist", quadbuf.visbuf, screenTriangles);
PBR::apply(*this, quadbuf, worklist, screenTriangles,
iblFiltered, sunLuminance, aerialPerspective);
Sky::draw(*this, quadbuf, worklist, cameraSky, atmosphere);
QuadBuffer::resolve(*this, quadbuf, color);
// Postprocessing
Bloom::apply(*this, swapchainPool, color);
Tonemap::apply(*this, color, _target);
// BVH::debugDrawAABBs(*this, _target, instances);
// Next-frame tasks
HiZ::fill(*this, hiz, depth);
}
}

@ -0,0 +1,35 @@
#pragma once
#include "vuk/RenderGraph.hpp"
#include "vuk/Context.hpp"
#include "gfx/resources/texture2d.hpp"
#include "gfx/resources/buffer.hpp"
#include "gfx/resources/pool.hpp"
#include "gfx/objects.hpp"
#include "gfx/engine.hpp"
#include "gfx/models.hpp"
#include "gfx/world.hpp"
#include "base/types.hpp"
#include "base/math.hpp"
namespace minote::gfx {
using namespace base;
struct Frame {
explicit Frame(Engine&, vuk::RenderGraph&);
void draw(Texture2D target, ObjectPool&, bool flush);
vuk::PerThreadContext& ptc;
vuk::RenderGraph& rg;
Pool& framePool;
Pool& swapchainPool;
Pool& permPool;
ModelBuffer& models;
World& cpu_world;
Buffer<World> world;
};
}

@ -0,0 +1,282 @@
#include "gfx/imgui.hpp"
#include "vuk/CommandBuffer.hpp"
#include "vuk/Context.hpp"
#include "base/containers/array.hpp"
#include "base/types.hpp"
#include "base/util.hpp"
#include "base/math.hpp"
#include "base/log.hpp"
#include "gfx/resources/buffer.hpp"
#include "gfx/samplers.hpp"
namespace minote::gfx {
using namespace base;
using namespace base::literals;
auto ImGui_ImplVuk_Init(vuk::PerThreadContext& _ptc) -> ImguiData {
// Set theme
ImGui::GetStyle().FrameRounding = 4.0f;
ImGui::GetStyle().GrabRounding = 4.0f;
auto* colors = ImGui::GetStyle().Colors;
colors[ImGuiCol_Text] = ImVec4(0.95f, 0.96f, 0.98f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.36f, 0.42f, 0.47f, 1.00f);
colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);
colors[ImGuiCol_ChildBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);
colors[ImGuiCol_Border] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.12f, 0.20f, 0.28f, 1.00f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.09f, 0.12f, 0.14f, 1.00f);
colors[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.12f, 0.14f, 0.65f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.39f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.18f, 0.22f, 0.25f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.09f, 0.21f, 0.31f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.37f, 0.61f, 1.00f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.20f, 0.25f, 0.29f, 0.55f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.10f, 0.40f, 0.75f, 0.78f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.10f, 0.40f, 0.75f, 1.00f);
colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f);
colors[ImGuiCol_Tab] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);
colors[ImGuiCol_TabHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f);
colors[ImGuiCol_TabActive] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);
colors[ImGuiCol_TabUnfocused] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
// Basic properties
auto& io = ImGui::GetIO();
io.BackendRendererName = "imgui_impl_vuk";
// We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
// Create the font texture
// Retrieve default font bitmap
auto* pixels = static_cast<unsigned char*>(nullptr);
auto width = int(0);
auto height = int(0);
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
// Upload the font to GPU
auto data = ImguiData();
data.fontTex = _ptc.create_texture(
vuk::Format::eR8G8B8A8Srgb,
vuk::Extent3D{u32(width), u32(height), 1u},
pixels).first;
_ptc.ctx.debug.set_name(data.fontTex, "ImGui/font");
data.fontSI = std::make_unique<vuk::SampledImage>(vuk::SampledImage::Global{ *data.fontTex.view, TrilinearRepeat, vuk::ImageLayout::eShaderReadOnlyOptimal });
io.Fonts->TexID = ImTextureID(data.fontSI.get());
// Create the ImGui pipeline
auto imguiPci = vuk::PipelineBaseCreateInfo();
imguiPci.add_spirv(std::vector<u32>{
#include "spv/imgui.vert.spv"
}, "imgui.vert");
imguiPci.add_spirv(std::vector<u32>{
#include "spv/imgui.frag.spv"
}, "imgui.frag");
_ptc.ctx.create_named_pipeline("imgui", imguiPci);
L_DEBUG("ImGui initialized");
return data;
}
void ImGui_ImplVuk_Render(Pool& _pool, vuk::PerThreadContext& _ptc, vuk::RenderGraph& _rg,
vuk::Name _target, ImguiData& _imdata, ImDrawData* _drawdata) {
// This is a real mess, but it works. Mostly copypasted from vuk example code
// and reformatted.
auto reset_render_state = [](const ImguiData& imdata, vuk::CommandBuffer& cmd,
ImDrawData* drawdata, vuk::Buffer vertex, vuk::Buffer index) {
cmd.bind_sampled_image(0, 0, *imdata.fontTex.view, TrilinearRepeat);
if (index.size > 0)
cmd.bind_index_buffer(index, sizeof(ImDrawIdx) == 2? vuk::IndexType::eUint16 : vuk::IndexType::eUint32);
cmd.bind_vertex_buffer(0, vertex, 0, vuk::Packed{
vuk::Format::eR32G32Sfloat,
vuk::Format::eR32G32Sfloat,
vuk::Format::eR8G8B8A8Unorm});
cmd.bind_graphics_pipeline("imgui");
cmd.set_viewport(0, vuk::Rect2D::framebuffer());
struct PC {
array<f32, 2> scale;
array<f32, 2> translate;
} pc;
pc.scale[0] = 2.0f / drawdata->DisplaySize.x;
pc.scale[1] = 2.0f / drawdata->DisplaySize.y;
pc.translate[0] = -1.0f - drawdata->DisplayPos.x * pc.scale[0];
pc.translate[1] = -1.0f - drawdata->DisplayPos.y * pc.scale[1];
cmd.push_constants(vuk::ShaderStageFlagBits::eVertex, 0, pc);
};
auto imvert = Buffer<ImDrawVert>::make(_pool, "imgui verts",
vuk::BufferUsageFlagBits::eVertexBuffer |
vuk::BufferUsageFlagBits::eTransferDst,
_drawdata->TotalVtxCount, vuk::MemoryUsage::eCPUtoGPU);
auto imind = Buffer<ImDrawIdx>::make(_pool, "imgui indices",
vuk::BufferUsageFlagBits::eIndexBuffer |
vuk::BufferUsageFlagBits::eTransferDst,
_drawdata->TotalIdxCount, vuk::MemoryUsage::eCPUtoGPU);
auto vtx_dst = 0_zu;
auto idx_dst = 0_zu;
for (auto n: iota(0, _drawdata->CmdListsCount)) {
auto* cmd_list = _drawdata->CmdLists[n];
std::memcpy(imvert.mappedPtr() + vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(decltype(imvert)::value_type));
std::memcpy(imind.mappedPtr() + idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(decltype(imind)::value_type));
vtx_dst += cmd_list->VtxBuffer.Size;
idx_dst += cmd_list->IdxBuffer.Size;
}
auto pass = vuk::Pass{
.name = "Imgui",
.resources = { vuk::Resource(_target, vuk::Resource::Type::eImage, vuk::eColorRW) },
.execute = [&_imdata, imvert, imind, _drawdata, reset_render_state, _target](vuk::CommandBuffer& cmd) {
cmd.set_dynamic_state(vuk::DynamicStateFlagBits::eScissor);
cmd.set_rasterization(vuk::PipelineRasterizationStateCreateInfo{});
cmd.set_color_blend(_target, vuk::BlendPreset::eAlphaBlend);
reset_render_state(_imdata, cmd, _drawdata, imvert, imind);
// Will project scissor/clipping rectangles into framebuffer space
auto clip_off = _drawdata->DisplayPos; // (0,0) unless using multi-viewports
auto clip_scale = _drawdata->FramebufferScale; // (1,1) unless using retina display which are often (2,2)
// Render command lists
// (Because we merged all buffers into a single one, we maintain our own offset into them)
int global_vtx_offset = 0;
int global_idx_offset = 0;
for (auto n: iota(0, _drawdata->CmdListsCount)) {
auto* cmd_list = _drawdata->CmdLists[n];
for (auto cmd_i: iota(0, cmd_list->CmdBuffer.Size)) {
auto* pcmd = &cmd_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback != nullptr) {
// User callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value
// used by the user to request the renderer to reset render state.)
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
reset_render_state(_imdata, cmd, _drawdata, imvert, imind);
else
pcmd->UserCallback(cmd_list, pcmd);
} else {
// Project scissor/clipping rectangles into framebuffer space
auto clip_rect = ImVec4{
(pcmd->ClipRect.x - clip_off.x) * clip_scale.x,
(pcmd->ClipRect.y - clip_off.y) * clip_scale.y,
(pcmd->ClipRect.z - clip_off.x) * clip_scale.x,
(pcmd->ClipRect.w - clip_off.y) * clip_scale.y};
auto fb_width = cmd.get_ongoing_renderpass().extent.width;
auto fb_height = cmd.get_ongoing_renderpass().extent.height;
if (clip_rect.x < fb_width && clip_rect.y < fb_height &&
clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) {
// Negative offsets are illegal for vkCmdSetScissor
if (clip_rect.x < 0.0f)
clip_rect.x = 0.0f;
if (clip_rect.y < 0.0f)
clip_rect.y = 0.0f;
// Apply scissor/clipping rectangle
auto scissor = vuk::Rect2D{
.offset = {i32(clip_rect.x), i32(clip_rect.y)},
.extent = {u32(clip_rect.z - clip_rect.x), u32(clip_rect.w - clip_rect.y)} };
cmd.set_scissor(0, scissor);
// Bind texture
if (pcmd->TextureId) {
auto& si = *reinterpret_cast<vuk::SampledImage*>(pcmd->TextureId);
if (si.is_global) {
cmd.bind_sampled_image(0, 0, si.global.iv, si.global.sci);
} else {
if (si.rg_attachment.ivci)
cmd.bind_sampled_image(0, 0, si.rg_attachment.attachment_name, *si.rg_attachment.ivci, si.rg_attachment.sci);
else
cmd.bind_sampled_image(0, 0, si.rg_attachment.attachment_name, si.rg_attachment.sci);
}
}
// Draw
cmd.draw_indexed(pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0);
}
}
}
global_idx_offset += cmd_list->IdxBuffer.Size;
global_vtx_offset += cmd_list->VtxBuffer.Size;
}
}
};
// add rendergraph dependencies to be transitioned
// make all rendergraph sampled images available
for (auto& si: _ptc.get_sampled_images()) {
if (!si.is_global)
pass.resources.push_back(vuk::Resource(si.rg_attachment.attachment_name, vuk::Resource::Type::eImage, vuk::Access::eFragmentSampled));
}
_rg.add_pass(std::move(pass));
}
}

@ -0,0 +1,26 @@
#pragma once
#include "imgui.h"
#include "vuk/SampledImage.hpp"
#include "vuk/RenderGraph.hpp"
#include "gfx/resources/pool.hpp"
namespace minote::gfx {
// GPU resources used by Imgui
struct ImguiData {
vuk::Texture fontTex;
std::unique_ptr<vuk::SampledImage> fontSI;
};
// Initialize Imgui. Run once after Vulkan is initialized, and keep the result
// struct around as long as Imgui is being used.
auto ImGui_ImplVuk_Init(vuk::PerThreadContext&) -> ImguiData;
// Draw all GUI elements that were queued up for this frame.
void ImGui_ImplVuk_Render(Pool&, vuk::PerThreadContext&, vuk::RenderGraph&,
vuk::Name target, ImguiData&, ImDrawData*);
}

@ -0,0 +1,228 @@
#include "gfx/models.hpp"
#include <utility>
#include <cassert>
#include <cstring>
#include "mpack/mpack.h"
#include "gfx/util.hpp"
#include "base/error.hpp"
#include "base/util.hpp"
#include "base/log.hpp"
namespace minote::gfx {
using namespace base;
using namespace base::literals;
using namespace tools;
void ModelList::addModel(string_view _name, std::span<char const> _model) {
// Load in data
auto in = mpack_reader_t();
mpack_reader_init_data(&in, _model.data(), _model.size_bytes());
if (auto magic = mpack_expect_u32(&in); magic != ModelMagic)
throw runtime_error_fmt("Wrong magic number of model {}: got {}, expected {}", _name, magic, ModelMagic);
mpack_expect_map_match(&in, 6);
// Load the materials
auto materialOffset = m_materials.size();
mpack_expect_cstr_match(&in, "materials");
auto materialCount = mpack_expect_array(&in);
m_materials.reserve(m_materials.size() + materialCount);
for (auto i: iota(0u, materialCount)) {
auto& material = m_materials.emplace_back();
material.id = +MaterialType::PBR;
mpack_expect_map_match(&in, 4);
mpack_expect_cstr_match(&in, "color");
mpack_expect_array_match(&in, 4);
material.color[0] = mpack_expect_float(&in);
material.color[1] = mpack_expect_float(&in);
material.color[2] = mpack_expect_float(&in);
material.color[3] = mpack_expect_float(&in);
mpack_done_array(&in);
mpack_expect_cstr_match(&in, "emissive");
mpack_expect_array_match(&in, 3);
material.emissive[0] = mpack_expect_float(&in);
material.emissive[1] = mpack_expect_float(&in);
material.emissive[2] = mpack_expect_float(&in);
mpack_done_array(&in);
mpack_expect_cstr_match(&in, "metalness");
material.metalness = mpack_expect_float(&in);
mpack_expect_cstr_match(&in, "roughness");
material.roughness = mpack_expect_float(&in);
mpack_done_map(&in);
}
mpack_done_array(&in);
// Safety fallback
if (materialCount == 0) {
auto& material = m_materials.emplace_back();
material.id = +MaterialType::PBR;
material.color = {1.0f, 1.0f, 1.0f, 1.0f};
material.emissive = {0.0f, 0.0f, 0.0f};
material.metalness = 0.0f;
material.roughness = 0.0f;
L_WARN("Model {} has no materials, using defaults", _name);
}
// Load the meshlets
m_modelIndices.emplace(_name, m_models.size());
auto& model = m_models.emplace_back(Model{
.meshletOffset = u32(m_meshlets.size()),
.meshletCount = 0 });
mpack_expect_cstr_match(&in, "meshlets");
auto meshletCount = mpack_expect_array(&in);
model.meshletCount += meshletCount;
for (auto i: iota(0u, meshletCount)) {
mpack_expect_map_match(&in, 8);
auto& meshlet = m_meshlets.emplace_back();
auto& aabb = m_meshletAABBs.emplace_back();
mpack_expect_cstr_match(&in, "materialIdx");
auto materialIdx = mpack_expect_u32(&in);
meshlet.materialIdx = materialOffset + materialIdx;
mpack_expect_cstr_match(&in, "indexOffset");
auto indexOffset = mpack_expect_u32(&in);
meshlet.indexOffset = indexOffset + m_triIndices.size();
mpack_expect_cstr_match(&in, "indexCount");
auto indexCount = mpack_expect_u32(&in);
meshlet.indexCount = indexCount;
mpack_expect_cstr_match(&in, "vertexOffset");
auto vertexOffset = mpack_expect_u32(&in);
meshlet.vertexOffset = vertexOffset + m_vertIndices.size();
mpack_expect_cstr_match(&in, "boundingSphereCenter");
mpack_expect_array_match(&in, 3);
for (auto i: iota(0, 3))
meshlet.boundingSphereCenter[i] = mpack_expect_float(&in);
mpack_done_array(&in);
mpack_expect_cstr_match(&in, "boundingSphereRadius");
meshlet.boundingSphereRadius = mpack_expect_float(&in);
mpack_expect_cstr_match(&in, "aabbMin");
mpack_expect_array_match(&in, 3);
for (auto i: iota(0, 3))
aabb.min[i] = mpack_expect_float(&in);
mpack_done_array(&in);
mpack_expect_cstr_match(&in, "aabbMax");
mpack_expect_array_match(&in, 3);
for (auto i: iota(0, 3))
aabb.max[i] = mpack_expect_float(&in);
mpack_done_array(&in);
mpack_done_map(&in);
}
mpack_done_array(&in);
mpack_expect_cstr_match(&in, "triIndices");
auto triIndexCount = mpack_expect_bin(&in) / sizeof(TriIndexType);
auto triIndices = pvector<TriIndexType>(triIndexCount);
mpack_read_bytes(&in, reinterpret_cast<char*>(triIndices.data()), triIndexCount * sizeof(TriIndexType));
m_triIndices.insert(m_triIndices.end(), triIndices.begin(), triIndices.end());
mpack_done_bin(&in);
mpack_expect_cstr_match(&in, "vertIndices");
auto vertIndexCount = mpack_expect_bin(&in) / sizeof(VertIndexType);
auto vertIndices = pvector<VertIndexType>(vertIndexCount);
mpack_read_bytes(&in, reinterpret_cast<char*>(vertIndices.data()), vertIndexCount * sizeof(VertIndexType));
for (auto& v: vertIndices) // Offset values for the unified buffer
v += m_vertices.size();
m_vertIndices.insert(m_vertIndices.end(), vertIndices.begin(), vertIndices.end());
mpack_done_bin(&in);
mpack_expect_cstr_match(&in, "vertices");
auto vertexCount = mpack_expect_bin(&in) / sizeof(VertexType);
auto vertexOffset = m_vertices.size();
m_vertices.resize(vertexOffset + vertexCount);
auto* verticesIt = &m_vertices[vertexOffset];
mpack_read_bytes(&in, reinterpret_cast<char*>(verticesIt), vertexCount * sizeof(VertexType));
mpack_done_bin(&in);
mpack_expect_cstr_match(&in, "normals");
auto normalCount = mpack_expect_bin(&in) / sizeof(NormalType);
auto normalOffset = m_normals.size();
m_normals.resize(normalOffset + normalCount);
auto* normalsIt = &m_normals[normalOffset];
mpack_read_bytes(&in, reinterpret_cast<char*>(normalsIt), normalCount * sizeof(NormalType));
mpack_done_bin(&in);
mpack_done_map(&in);
mpack_reader_destroy(&in);
L_DEBUG("Loaded model {}: {} materials, {} meshlets", _name, materialCount, model.meshletCount);
}
auto ModelList::upload(Pool& _pool, vuk::Name _name) && -> ModelBuffer {
auto result = ModelBuffer{
.materials = Buffer<Material>::make(_pool, nameAppend(_name, "materials"),
vuk::BufferUsageFlagBits::eStorageBuffer,
m_materials),
.triIndices = Buffer<u32>::make(_pool, nameAppend(_name, "triIndices"),
vuk::BufferUsageFlagBits::eStorageBuffer,
m_triIndices),
.vertIndices = Buffer<VertIndexType>::make(_pool, nameAppend(_name, "vertIndices"),
vuk::BufferUsageFlagBits::eStorageBuffer,
m_vertIndices),
.vertices = Buffer<VertexType>::make(_pool, nameAppend(_name, "vertices"),
vuk::BufferUsageFlagBits::eStorageBuffer,
m_vertices),
.normals = Buffer<NormalType>::make(_pool, nameAppend(_name, "normals"),
vuk::BufferUsageFlagBits::eStorageBuffer,
m_normals),
.meshlets = Buffer<Meshlet>::make(_pool, nameAppend(_name, "meshlets"),
vuk::BufferUsageFlagBits::eStorageBuffer,
m_meshlets),
.models = Buffer<Model>::make(_pool, nameAppend(_name, "models"),
vuk::BufferUsageFlagBits::eStorageBuffer,
m_models),
.cpu_modelIndices = std::move(m_modelIndices) };
result.cpu_meshlets = std::move(m_meshlets); // Must still exist for .meshlets creation
result.cpu_meshletAABBs = std::move(m_meshletAABBs);
result.cpu_models = std::move(m_models);
// Clean up in case this isn't a temporary
m_materials.clear();
m_materials.shrink_to_fit();
m_triIndices.clear();
m_triIndices.shrink_to_fit();
m_vertIndices.clear();
m_vertIndices.shrink_to_fit();
m_vertices.clear();
m_vertices.shrink_to_fit();
m_normals.clear();
m_normals.shrink_to_fit();
return result;
L_DEBUG("Uploaded all models to GPU");
}
}

@ -0,0 +1,103 @@
#pragma once
#include <type_traits>
#include <span>
#include "vuk/Context.hpp"
#include "gfx/resources/buffer.hpp"
#include "gfx/resources/pool.hpp"
#include "gfx/util.hpp"
#include "base/containers/hashmap.hpp"
#include "base/containers/string.hpp"
#include "base/containers/vector.hpp"
#include "base/types.hpp"
#include "base/math.hpp"
#include "base/id.hpp"
#include "tools/modelSchema.hpp"
namespace minote::gfx {
using namespace base;
struct Material {
enum struct Type: u32 {
None = 0, // Invalid
PBR = 1,
Count
};
vec4 color;
vec3 emissive;
u32 id;
f32 metalness;
f32 roughness;
vec2 pad0;
};
using MaterialType = Material::Type;
struct Meshlet {
u32 materialIdx;
u32 indexOffset;
u32 indexCount;
u32 vertexOffset;
vec3 boundingSphereCenter;
f32 boundingSphereRadius;
};
struct Model {
u32 meshletOffset;
u32 meshletCount;
};
// A set of buffers storing vertex data for all models, and how to access each
// model within the buffer.
struct ModelBuffer {
Buffer<Material> materials;
Buffer<u32> triIndices;
Buffer<tools::VertIndexType> vertIndices;
Buffer<tools::VertexType> vertices;
Buffer<tools::NormalType> normals;
Buffer<Meshlet> meshlets;
Buffer<Model> models;
ivector<Meshlet> cpu_meshlets;
ivector<AABB> cpu_meshletAABBs;
ivector<Model> cpu_models;
hashmap<ID, u32> cpu_modelIndices;
};
// Structure storing model data as they're being loaded. After all models are
// loaded in, it can be uploaded to GPU by converting it into a ModelBuffer.
struct ModelList {
// Parse a model file, and append it to the list.
void addModel(string_view name, std::span<char const> model);
// Convert into a ModelBuffer. The instance must be moved in,
// so all CPU-side resources are freed.
auto upload(Pool&, vuk::Name) && -> ModelBuffer;
private:
pvector<Material> m_materials;
pvector<u32> m_triIndices;
pvector<tools::VertIndexType> m_vertIndices;
pvector<tools::VertexType> m_vertices;
pvector<tools::NormalType> m_normals;
ivector<Meshlet> m_meshlets; // Meshlet descriptors, for access to index buffers
ivector<AABB> m_meshletAABBs;
ivector<Model> m_models; // Model descriptors, for access to m_modelMeshes
hashmap<ID, u32> m_modelIndices; // Mapping of model IDs to their index in m_models
};
}

@ -0,0 +1,58 @@
#include "gfx/objects.hpp"
#include "base/util.hpp"
namespace minote::gfx {
using namespace base::literals;
auto ObjectPool::create() -> ObjectID {
if (m_deletedIDs.empty()) {
metadata.emplace_back(Metadata::make_default());
modelIDs.emplace_back();
colors.emplace_back(1.0f); // Fully opaque
transforms.emplace_back(Transform::make_default());
prevTransforms.emplace_back(Transform::make_default());
return size() - 1;
} else {
auto id = m_deletedIDs.back();
m_deletedIDs.pop_back();
metadata[id] = Metadata::make_default();
colors[id] = vec4(1.0f); // Fully opaque
transforms[id] = Transform::make_default();
prevTransforms[id] = Transform::make_default();
return id;
}
}
void ObjectPool::destroy(ObjectID _id) {
metadata[_id].exists = false;
m_deletedIDs.push_back(_id);
}
auto ObjectPool::get(ObjectID _id) -> Proxy {
return Proxy{
.metadata = metadata[_id],
.modelID = modelIDs[_id],
.color = colors[_id],
.transform = transforms[_id] };
}
void ObjectPool::copyTransforms() {
prevTransforms = transforms;
}
}

@ -0,0 +1,102 @@
#pragma once
#include <type_traits>
#include "base/containers/vector.hpp"
#include "base/types.hpp"
#include "base/math.hpp"
#include "base/id.hpp"
namespace minote::gfx {
using namespace base;
// Pool of renderable objects.
struct ObjectPool {
// Stable handle to an object in the pool
using ObjectID = usize;
// Object state
struct Metadata {
bool visible; // Invisible objects are temporarily excluded from drawing
bool exists; // Do not modify - internal garbage collection
// Construct with default values
static constexpr auto make_default() -> Metadata { return Metadata{true, true}; }
};
// Spatial properties
struct Transform {
vec3 position;
f32 pad0;
vec3 scale;
f32 pad1;
quat rotation;
// Construct with default values
static constexpr auto make_default() -> Transform {
return Transform{
.position = vec3(0.0f),
.scale = vec3(1.0f),
.rotation = quat::identity()};
}
};
// Convenient access to all properties of a single object
struct Proxy {
Metadata& metadata;
ID& modelID;
vec4& color;
Transform& transform;
};
// Ensure fast operation in large containers
static_assert(std::is_trivially_constructible_v<Metadata>);
static_assert(std::is_trivially_constructible_v<Transform>);
// Return a handle to a new, uninitialized object. See above which fields
// do not have a default initializer. Remember to destroy() the object.
[[nodiscard]]
auto create() -> ObjectID;
// Destroy the object, freeing up the ID for use with objects created
// in the future.
void destroy(ObjectID);
// Return a proxy for convenient access to an object's properties. The proxy
// can only be considered valid until any other ObjectPool method is called.
[[nodiscard]]
auto get(ObjectID) -> Proxy;
void copyTransforms();
// Return the current size of the pool. This might include nonexistent
// objects, so it's only useful for transfers.
[[nodiscard]]
auto size() const -> usize { return metadata.size(); }
// Direct access to these is discouraged, unless you're doing a whole
// container transfer.
ivector<Metadata> metadata;
ivector<ID> modelIDs;
ivector<vec4> colors;
ivector<Transform> transforms;
ivector<Transform> prevTransforms;
private:
ivector<ObjectID> m_deletedIDs;
};
using ObjectID = ObjectPool::ObjectID;
}

@ -0,0 +1,61 @@
#pragma once
#include <span>
#include "vuk/RenderGraph.hpp"
#include "vuk/Buffer.hpp"
#include "vuk/Name.hpp"
#include "base/types.hpp"
#include "gfx/resources/pool.hpp"
namespace minote::gfx {
using namespace base;
using namespace base::literals;
template<typename T>
struct Buffer {
using value_type = T;
vuk::Name name;
vuk::Buffer* handle = nullptr;
// Construct an empty buffer inside a pool. If the pool already contained a buffer under
// the same name, the existing one is retrieved instead.
static auto make(Pool&, vuk::Name, vuk::BufferUsageFlags, usize elements = 1,
vuk::MemoryUsage = vuk::MemoryUsage::eGPUonly) -> Buffer<T>;
// Construct a buffer inside a pool and transfer data into it. If the pool already contained
// a buffer under the same name, the existing one is retrieved instead, but the transfer
// still proceeds. Setting elementCapacity allows for a buffer larger than provided data.
static auto make(Pool&, vuk::Name, vuk::BufferUsageFlags, std::span<T const> data, usize elementCapacity = 0_zu) -> Buffer<T>;
// Create a buffer reference that starts at the specified element count.
auto offsetView(usize elements) const -> vuk::Buffer;
// Size of the buffer in bytes.
[[nodiscard]]
auto size() const -> usize { return handle->size; }
// Number of elements in the buffer.
auto length() const -> usize { return size() / sizeof(value_type); }
[[nodiscard]]
auto mappedPtr() -> T* { return reinterpret_cast<T*>(handle->mapped_ptr); }
// Declare as a vuk::Resource.
[[nodiscard]]
auto resource(vuk::Access) const -> vuk::Resource;
// Attach the buffer to the rendergraph.
void attach(vuk::RenderGraph&, vuk::Access initial, vuk::Access final);
// Convertible to vuk::Buffer
operator vuk::Buffer() const { return *handle; }
};
}
#include "gfx/resources/buffer.tpp"

@ -0,0 +1,95 @@
#include "gfx/resources/buffer.hpp"
#include <cassert>
#include <cstring>
namespace minote::gfx {
template<typename T>
auto Buffer<T>::make(Pool& _pool, vuk::Name _name, vuk::BufferUsageFlags _usage,
usize _elements, vuk::MemoryUsage _memUsage) -> Buffer<T> {
assert(_memUsage == vuk::MemoryUsage::eCPUtoGPU ||
_memUsage == vuk::MemoryUsage::eGPUonly);
auto& buffer = [&_pool, _name, _elements, _usage, _memUsage]() -> vuk::Buffer& {
if (_pool.contains(_name)) {
return *_pool.get<vuk::Unique<vuk::Buffer>>(_name);
} else {
auto size = sizeof(T) * _elements;
return *_pool.insert<vuk::Unique<vuk::Buffer>>(_name,
_pool.ptc().allocate_buffer(_memUsage, _usage, size, alignof(T)));
}
}();
return Buffer<T>{
.name = _name,
.handle = &buffer };
}
template<typename T>
auto Buffer<T>::make(Pool& _pool, vuk::Name _name, vuk::BufferUsageFlags _usage,
std::span<T const> _data, usize _elementCapacity) -> Buffer<T> {
assert(_elementCapacity == 0 || _elementCapacity >= _data.size());
auto& buffer = [&_pool, _name, &_data, _usage, _elementCapacity]() -> vuk::Buffer& {
if (_pool.contains(_name)) {
return *_pool.get<vuk::Unique<vuk::Buffer>>(_name);
} else {
auto size = _data.size_bytes();
if (_elementCapacity != 0)
size = _elementCapacity * sizeof(T);
return *_pool.insert<vuk::Unique<vuk::Buffer>>(_name,
_pool.ptc().allocate_buffer(vuk::MemoryUsage::eCPUtoGPU, _usage, size, alignof(T)));
}
}();
assert(buffer.size >= _data.size_bytes());
std::memcpy(buffer.mapped_ptr, _data.data(), _data.size_bytes());
return Buffer<T>{
.name = _name,
.handle = &buffer };
}
template<typename T>
auto Buffer<T>::offsetView(usize _elements) const -> vuk::Buffer {
auto result = *handle;
result.offset += _elements * sizeof(T);
return result;
}
template<typename T>
auto Buffer<T>::resource(vuk::Access _access) const -> vuk::Resource {
return vuk::Resource(name, vuk::Resource::Type::eBuffer, _access);
}
template<typename T>
void Buffer<T>::attach(vuk::RenderGraph& _rg, vuk::Access _initial, vuk::Access _final) {
_rg.attach_buffer(name, *handle, _initial, _final);
}
}

@ -0,0 +1,70 @@
#include "gfx/resources/cubemap.hpp"
#include <utility>
#include "gfx/util.hpp"
namespace minote::gfx {
auto Cubemap::make(Pool& _pool, vuk::Name _name, u32 _size, vuk::Format _format,
vuk::ImageUsageFlags _usage) -> Cubemap {
auto& texture = [&_pool, _name, _format, _size, _usage]() -> vuk::Texture& {
if (_pool.contains(_name)) {
return _pool.get<vuk::Texture>(_name);
} else {
auto result = _pool.ptc().allocate_texture(vuk::ImageCreateInfo{
.flags = vuk::ImageCreateFlagBits::eCubeCompatible,
.format = _format,
.extent = {_size, _size, 1},
.mipLevels = mipmapCount(_size),
.arrayLayers = 6,
.usage = _usage });
result.view = _pool.ptc().create_image_view(vuk::ImageViewCreateInfo{
.image = *result.image,
.viewType = vuk::ImageViewType::eCube,
.format = result.format,
.subresourceRange = vuk::ImageSubresourceRange{
.aspectMask = vuk::ImageAspectFlagBits::eColor,
.levelCount = VK_REMAINING_MIP_LEVELS,
.layerCount = 6 }});
return _pool.insert(_name, std::move(result));
}
}();
return Cubemap{
.name = _name,
.handle = &texture };
}
auto Cubemap::mipView(u32 _mip) const -> vuk::Unique<vuk::ImageView> {
return handle->view.mip_subrange(_mip, 1).apply();
}
auto Cubemap::mipArrayView(u32 _mip) const -> vuk::Unique<vuk::ImageView> {
return handle->view.mip_subrange(_mip, 1).view_as(vuk::ImageViewType::e2DArray).apply();
}
auto Cubemap::resource(vuk::Access _access) const -> vuk::Resource {
return vuk::Resource(name, vuk::Resource::Type::eImage, _access);
}
void Cubemap::attach(vuk::RenderGraph& _rg, vuk::Access _initial, vuk::Access _final) const {
_rg.attach_image(name, vuk::ImageAttachment::from_texture(*handle), _initial, _final);
}
}

@ -0,0 +1,53 @@
#pragma once
#include "vuk/RenderGraph.hpp"
#include "vuk/Image.hpp"
#include "vuk/Name.hpp"
#include "base/types.hpp"
#include "gfx/resources/pool.hpp"
namespace minote::gfx {
using namespace base;
// Array of 6 textures, functioning as a sampler that has a value for every
// angle vector. Mipmaps are created, and each mipmap level can be accessed
// with a corresponding view.
struct Cubemap {
vuk::Name name;
vuk::Texture* handle = nullptr;
// Construct a cubemap inside a pool. If the pool already contained a cubemap under the same
// name, the existing one is retrieved instead.
static auto make(Pool&, vuk::Name, u32 size, vuk::Format, vuk::ImageUsageFlags) -> Cubemap;
// Create a cubemap image view into a specified mipmap level.
[[nodiscard]]
auto mipView(u32 mip) const -> vuk::Unique<vuk::ImageView>;
// Create a 6-layer image view into a specified mipmap level.
[[nodiscard]]
auto mipArrayView(u32 mip) const -> vuk::Unique<vuk::ImageView>;
// Return the size of the texture.
[[nodiscard]]
auto size() const -> uvec2 { return uvec2{handle->extent.width, handle->extent.height}; }
// Return the surface format.
[[nodiscard]]
auto format() const -> vuk::Format { return handle->format; }
// Declare as a vuk::Resource.
[[nodiscard]]
auto resource(vuk::Access) const -> vuk::Resource;
// Attach texture to rendergraph
void attach(vuk::RenderGraph&, vuk::Access initial, vuk::Access final) const;
// Convertible to vuk::ImageView
operator vuk::ImageView() const { return *handle->view; }
};
}

@ -0,0 +1,65 @@
#pragma once
#include <utility>
#include <variant>
#include <span>
#include "vuk/Context.hpp"
#include "vuk/Buffer.hpp"
#include "vuk/Image.hpp"
#include "vuk/Name.hpp"
#include "base/containers/hashmap.hpp"
namespace minote::gfx {
using namespace base;
// A pool for holding resources.
struct Pool {
// Create a pool with no PTC bound. Use setPtc() to bind one before making
// any elements.
Pool() = default;
// Create an empty pool.
explicit Pool(vuk::PerThreadContext& ptc): m_ptc(&ptc) {}
// Bind a new PerFrameContext. For pools used on multiple frames.
void setPtc(vuk::PerThreadContext& ptc) { m_ptc = &ptc; }
// Return the current PerFrameContext.
auto ptc() -> vuk::PerThreadContext& { return *m_ptc; }
// Enqueue destruction of all resources in the pool.
void reset() { m_resources.clear(); }
// Interface for resources to insert themselves into the pool. Accepted resources are:
// vuk::Texture
// vuk::Unique<vuk::Buffer>
// Check if pool contains a resource by a given name.
[[nodiscard]]
auto contains(vuk::Name name) const -> bool { return m_resources.contains(name); }
// Return a resource at the given name. No checking for existence or type.
template<typename T>
auto get(vuk::Name name) -> T& { return std::get<T>(m_resources.at(name)); }
// Insert a resource at the given name. If the name is taken, nothing happens. Returns
// a reference to the resource under that name.
template<typename T>
auto insert(vuk::Name name, T&& res) -> T& {
return std::get<T>(m_resources.try_emplace(name, std::in_place_type<T>, std::forward<T>(res)).first->second);
}
private:
vuk::PerThreadContext* m_ptc;
using Resource = std::variant<vuk::Unique<vuk::Buffer>, vuk::Texture>;
hashmap<vuk::Name, Resource> m_resources;
};
}

@ -0,0 +1,58 @@
#include "gfx/resources/texture2d.hpp"
#include "gfx/util.hpp"
namespace minote::gfx {
auto Texture2D::make(Pool& _pool, vuk::Name _name, uvec2 _size, vuk::Format _format,
vuk::ImageUsageFlags _usage, u32 _mips) -> Texture2D {
auto& texture = [&_pool, _name, _format, _size, _mips, _usage]() -> vuk::Texture& {
if (_pool.contains(_name)) {
return _pool.get<vuk::Texture>(_name);
} else {
return _pool.insert<vuk::Texture>(_name,
_pool.ptc().allocate_texture(vuk::ImageCreateInfo{
.format = _format,
.extent = {_size.x(), _size.y(), 1},
.mipLevels = _mips,
.usage = _usage }));
}
}();
_pool.ptc().ctx.debug.set_name(*texture.image, _name);
_pool.ptc().ctx.debug.set_name(texture.view->payload, nameAppend(_name, "main"));
return Texture2D{
.name = _name,
.handle = &texture };
}
auto Texture2D::mipView(u32 _mip) const -> vuk::Unique<vuk::ImageView> {
//TODO add debug name
return handle->view.mip_subrange(_mip, 1).apply();
}
auto Texture2D::resource(vuk::Access _access) const -> vuk::Resource {
return vuk::Resource(name, vuk::Resource::Type::eImage, _access);
}
void Texture2D::attach(vuk::RenderGraph& _rg, vuk::Access _initial, vuk::Access _final,
vuk::Clear _clear) const {
_rg.attach_image(name, vuk::ImageAttachment::from_texture(*handle, _clear), _initial, _final);
}
}

@ -0,0 +1,49 @@
#pragma once
#include "vuk/RenderGraph.hpp"
#include "vuk/Image.hpp"
#include "vuk/Name.hpp"
#include "base/types.hpp"
#include "base/math.hpp"
#include "gfx/resources/pool.hpp"
namespace minote::gfx {
using namespace base;
struct Texture2D {
vuk::Name name;
vuk::Texture* handle = nullptr;
// Construct a texture inside a pool. If the pool already contained a texture under the same
// name, the existing one is retrieved instead.
static auto make(Pool&, vuk::Name, uvec2 size, vuk::Format, vuk::ImageUsageFlags, u32 mips = 1) -> Texture2D;
// Create an image view into a specified mipmap level.
[[nodiscard]]
auto mipView(u32 mip) const -> vuk::Unique<vuk::ImageView>;
// Return the size of the texture.
[[nodiscard]]
auto size() const -> uvec2 { return uvec2{handle->extent.width, handle->extent.height}; }
// Return the surface format.
[[nodiscard]]
auto format() const -> vuk::Format { return handle->format; }
// Declare as a vuk::Resource.
[[nodiscard]]
auto resource(vuk::Access) const -> vuk::Resource;
// Attach texture to rendergraph
void attach(vuk::RenderGraph&, vuk::Access initial, vuk::Access final, vuk::Clear = {}) const;
operator bool() const { return handle != nullptr; }
// Convertible to vuk::ImageView
operator vuk::ImageView() const { return *handle->view; }
};
}

@ -0,0 +1,51 @@
#include "gfx/resources/texture2dms.hpp"
#include "gfx/util.hpp"
namespace minote::gfx {
auto Texture2DMS::make(Pool& _pool, vuk::Name _name, uvec2 _size, vuk::Format _format,
vuk::ImageUsageFlags _usage, vuk::SampleCountFlagBits _samples) -> Texture2DMS {
auto& texture = [&_pool, _name, _format, _size, _usage, _samples]() -> vuk::Texture& {
if (_pool.contains(_name)) {
return _pool.get<vuk::Texture>(_name);
} else {
return _pool.insert<vuk::Texture>(_name,
_pool.ptc().allocate_texture(vuk::ImageCreateInfo{
.format = _format,
.extent = {_size.x(), _size.y(), 1},
.samples = _samples,
.usage = _usage }));
}
}();
_pool.ptc().ctx.debug.set_name(*texture.image, _name);
_pool.ptc().ctx.debug.set_name(texture.view->payload, nameAppend(_name, "main"));
return Texture2DMS{
.name = _name,
.handle = &texture };
}
auto Texture2DMS::resource(vuk::Access _access) const -> vuk::Resource {
return vuk::Resource(name, vuk::Resource::Type::eImage, _access);
}
void Texture2DMS::attach(vuk::RenderGraph& _rg, vuk::Access _initial, vuk::Access _final,
vuk::Clear _clear) const {
_rg.attach_image(name, vuk::ImageAttachment::from_texture(*handle, _clear), _initial, _final);
}
}

@ -0,0 +1,50 @@
#pragma once
#include "vuk/RenderGraph.hpp"
#include "vuk/Image.hpp"
#include "vuk/Name.hpp"
#include "base/types.hpp"
#include "base/math.hpp"
#include "gfx/resources/pool.hpp"
namespace minote::gfx {
using namespace base;
struct Texture2DMS {
vuk::Name name;
vuk::Texture* handle = nullptr;
// Construct a texture inside a pool. If the pool already contained a texture under the same
// name, the existing one is retrieved instead.
static auto make(Pool&, vuk::Name, uvec2 size, vuk::Format,
vuk::ImageUsageFlags, vuk::SampleCountFlagBits) -> Texture2DMS;
// Return the size of the texture.
[[nodiscard]]
auto size() const -> uvec2 { return uvec2{handle->extent.width, handle->extent.height}; }
// Return the count of samples per pixel.
[[nodiscard]]
auto samples() const -> u32 { return u32(handle->sample_count.count); }
// Return the surface format.
[[nodiscard]]
auto format() const -> vuk::Format { return handle->format; }
// Declare as a vuk::Resource.
[[nodiscard]]
auto resource(vuk::Access) const -> vuk::Resource;
// Attach texture to rendergraph
void attach(vuk::RenderGraph&, vuk::Access initial, vuk::Access final, vuk::Clear = {}) const;
operator bool() const { return handle != nullptr; }
// Convertible to vuk::ImageView
operator vuk::ImageView() const { return *handle->view; }
};
}

@ -0,0 +1,50 @@
#include "gfx/resources/texture3d.hpp"
#include "gfx/util.hpp"
namespace minote::gfx {
auto Texture3D::make(Pool& _pool, vuk::Name _name, uvec3 _size, vuk::Format _format,
vuk::ImageUsageFlags _usage) -> Texture3D {
auto& texture = [&_pool, _name, _format, _size, _usage]() -> vuk::Texture& {
if (_pool.contains(_name)) {
return _pool.get<vuk::Texture>(_name);
} else {
return _pool.insert<vuk::Texture>(_name,
_pool.ptc().allocate_texture(vuk::ImageCreateInfo{
.format = _format,
.extent = {_size.x(), _size.y(), _size.z()},
.usage = _usage }));
}
}();
_pool.ptc().ctx.debug.set_name(*texture.image, _name);
_pool.ptc().ctx.debug.set_name(texture.view->payload, nameAppend(_name, "main"));
return Texture3D{
.name = _name,
.handle = &texture };
}
auto Texture3D::resource(vuk::Access _access) const -> vuk::Resource {
return vuk::Resource(name, vuk::Resource::Type::eImage, _access);
}
void Texture3D::attach(vuk::RenderGraph& _rg, vuk::Access _initial, vuk::Access _final,
vuk::Clear _clear) const {
_rg.attach_image(name, vuk::ImageAttachment::from_texture(*handle, _clear), _initial, _final);
}
}

@ -0,0 +1,45 @@
#pragma once
#include "vuk/RenderGraph.hpp"
#include "vuk/Image.hpp"
#include "vuk/Name.hpp"
#include "base/types.hpp"
#include "base/math.hpp"
#include "gfx/resources/pool.hpp"
namespace minote::gfx {
using namespace base;
struct Texture3D {
vuk::Name name;
vuk::Texture* handle = nullptr;
// Construct a texture inside a pool. If the pool already contained a texture under the same
// name, the existing one is retrieved instead.
static auto make(Pool&, vuk::Name, uvec3 size, vuk::Format, vuk::ImageUsageFlags) -> Texture3D;
// Return the size of the texture.
[[nodiscard]]
auto size() const -> uvec3 { return uvec3{handle->extent.width, handle->extent.height, handle->extent.depth}; }
// Return the surface format.
[[nodiscard]]
auto format() const -> vuk::Format { return handle->format; }
// Declare as a vuk::Resource.
[[nodiscard]]
auto resource(vuk::Access) const -> vuk::Resource;
// Attach texture to rendergraph
void attach(vuk::RenderGraph&, vuk::Access initial, vuk::Access final, vuk::Clear = {}) const;
operator bool() const { return handle != nullptr; }
// Convertible to vuk::ImageView
operator vuk::ImageView() const { return *handle->view; }
};
}

@ -0,0 +1,46 @@
#pragma once
#include "vuk/Image.hpp"
namespace minote::gfx {
// Commonly used sampler presets.
constexpr auto NearestClamp = vuk::SamplerCreateInfo{
.magFilter = vuk::Filter::eNearest,
.minFilter = vuk::Filter::eNearest,
.addressModeU = vuk::SamplerAddressMode::eClampToEdge,
.addressModeV = vuk::SamplerAddressMode::eClampToEdge };
constexpr auto LinearClamp = vuk::SamplerCreateInfo{
.magFilter = vuk::Filter::eLinear,
.minFilter = vuk::Filter::eLinear,
.addressModeU = vuk::SamplerAddressMode::eClampToEdge,
.addressModeV = vuk::SamplerAddressMode::eClampToEdge };
constexpr auto TrilinearClamp = vuk::SamplerCreateInfo{
.magFilter = vuk::Filter::eLinear,
.minFilter = vuk::Filter::eLinear,
.mipmapMode = vuk::SamplerMipmapMode::eLinear,
.addressModeU = vuk::SamplerAddressMode::eClampToEdge,
.addressModeV = vuk::SamplerAddressMode::eClampToEdge };
constexpr auto TrilinearRepeat = vuk::SamplerCreateInfo{
.magFilter = vuk::Filter::eLinear,
.minFilter = vuk::Filter::eLinear,
.mipmapMode = vuk::SamplerMipmapMode::eLinear,
.addressModeU = vuk::SamplerAddressMode::eRepeat,
.addressModeV = vuk::SamplerAddressMode::eRepeat };
inline auto const MinClampRMCI = VkSamplerReductionModeCreateInfo{
.sType = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO,
.reductionMode = VK_SAMPLER_REDUCTION_MODE_MIN };
inline auto const MinClamp = vuk::SamplerCreateInfo{
.pNext = &MinClampRMCI,
.magFilter = vuk::Filter::eLinear,
.minFilter = vuk::Filter::eLinear,
.addressModeU = vuk::SamplerAddressMode::eClampToEdge,
.addressModeV = vuk::SamplerAddressMode::eClampToEdge };
}

@ -0,0 +1,85 @@
#pragma once
#include "vuk/CommandBuffer.hpp"
#include "vuk/Types.hpp"
#include "vuk/Name.hpp"
#include "base/containers/string.hpp"
#include "base/concepts.hpp"
#include "base/types.hpp"
#include "base/math.hpp"
namespace minote::gfx {
using namespace base;
struct AABB {
vec3 min;
vec3 max;
};
// Return the number a mipmaps that a square texture of the given size would have.
constexpr auto mipmapCount(u32 size) {
return u32(floor(log2(size))) + 1;
}
// Rounded up division
template<usize N, integral T>
constexpr auto divRoundUp(vec<N, T> _v, vec<N, T> _div) -> vec<N, T> {
return (_v - T(1)) / _div + T(1);
}
template<usize N, integral T>
constexpr auto divRoundUp(vec<N, T> _v, T _div) -> vec<N, T> {
return (_v - vec<N, T>(1)) / _div + vec<N, T>(1);
}
template<integral T>
constexpr auto divRoundUp(T _val, T _div) -> T {
return (_val - T(1)) / _div + T(1);
}
// Create a new vuk Name by appending a provided suffix.
inline auto nameAppend(vuk::Name name, string_view suffix) -> vuk::Name {
auto str = string();
str.reserve(name.to_sv().size() + 1 + suffix.size() + 1);
str.append(name.to_sv());
str.push_back(' ');
str.append(suffix);
return vuk::Name(str);
}
// Conversion from vec[n] to vuk::Extent[n]D
template<arithmetic T>
constexpr auto vukExtent(vec<2, T> v) -> vuk::Extent2D { return {v[0], v[1]}; }
template<arithmetic T>
constexpr auto vukExtent(vec<3, T> v) -> vuk::Extent3D { return {v[0], v[1], v[2]}; }
// Pack two values in a u32. x is in lower bits, y in upper
constexpr auto u32Fromu16(uvec2 _v) -> u32 {
return (_v.y() << 16) | _v.x();
}
// Shorthand for setting rendering area
inline void cmdSetViewportScissor(vuk::CommandBuffer cmd, uvec2 area) {
cmd.set_viewport(0, vuk::Rect2D{ .extent = vukExtent(area) })
.set_scissor(0, vuk::Rect2D{ .extent = vukExtent(area) });
}
}

@ -0,0 +1,12 @@
#include "gfx/world.hpp"
namespace minote::gfx {
auto World::upload(Pool& _pool, vuk::Name _name) const -> Buffer<World> {
return Buffer<World>::make(_pool, _name,
vuk::BufferUsageFlagBits::eUniformBuffer, std::span(this, 1));
}
}

@ -0,0 +1,37 @@
#pragma once
#include "vuk/Context.hpp"
#include "vuk/Name.hpp"
#include "gfx/resources/buffer.hpp"
#include "gfx/resources/pool.hpp"
#include "base/types.hpp"
#include "base/math.hpp"
namespace minote::gfx {
using namespace base;
// Struct of global data, commonly used by shaders.
struct World {
mat4 view;
mat4 projection;
mat4 viewProjection;
mat4 viewProjectionInverse;
mat4 prevViewProjection;
uvec2 viewportSize;
f32 nearPlane;
f32 pad0;
vec3 cameraPos;
u32 frameCounter;
vec3 sunDirection;
f32 pad1;
vec3 sunIlluminance;
// Upload the world data to the GPU, to be used as a uniform.
auto upload(Pool&, vuk::Name) const -> Buffer<World>;
};
}

@ -0,0 +1,73 @@
#version 460
#pragma shader_stage(compute)
layout(local_size_x = 16, local_size_y = 16) in;
#include "../util.glsl"
layout(binding = 0) uniform sampler2D s_source;
layout(binding = 1) restrict writeonly uniform image2D i_target;
layout(constant_id = 0) const uint SourceSizePacked = 0;
layout(constant_id = 1) const uint TargetSizePacked = 0;
const uvec2 SourceSize = U16FROMU32(SourceSizePacked);
const uvec2 TargetSize = U16FROMU32(TargetSizePacked);
shared vec4 sh_outer[gl_WorkGroupSize.x + 2][gl_WorkGroupSize.y + 2];
shared vec4 sh_inner[gl_WorkGroupSize.x + 1][gl_WorkGroupSize.y + 1];
void main() {
uvec2 gid = gl_GlobalInvocationID.xy;
uvec2 lid = gl_LocalInvocationID.xy;
// Fill up the filter tap cache
vec2 uv = (vec2(gid) + vec2(0.5)) / vec2(TargetSize);
vec2 texel = vec2(1.0) / vec2(SourceSize);
vec2 texel2 = texel * 2.0;
if (lid.x <= 1)
sh_outer[lid.x ][lid.y+2] = textureLod(s_source, uv + vec2(-texel2.x, texel2.y), 0.0);
if (lid.y <= 1)
sh_outer[lid.x+2][lid.y ] = textureLod(s_source, uv + vec2(texel2.x, -texel2.y), 0.0);
if (lid.x <= 1 && lid.y <= 1)
sh_outer[lid.x ][lid.y ] = textureLod(s_source, uv - texel2, 0.0);
sh_outer[lid.x+2][lid.y+2] = textureLod(s_source, uv + texel2, 0.0);
if (lid.x == 0)
sh_inner[lid.x ][lid.y+1] = textureLod(s_source, uv + vec2(-texel.x, texel.y), 0.0);
if (lid.y == 0)
sh_inner[lid.x+1][lid.y ] = textureLod(s_source, uv + vec2(texel.x, -texel.y), 0.0);
if (lid.x == 0 && lid.y == 0)
sh_inner[lid.x ][lid.y ] = textureLod(s_source, uv - texel, 0.0);
sh_inner[lid.x+1][lid.y+1] = textureLod(s_source, uv + texel, 0.0);
barrier();
if (any(greaterThanEqual(gid, TargetSize)))
return; // We can't return before all barriers are passed
// Convolve source texture
vec4 resultInner =
sh_inner[lid.x ][lid.y ] * (4.0 / 32.0) +
sh_inner[lid.x+1][lid.y ] * (4.0 / 32.0) +
sh_inner[lid.x ][lid.y+1] * (4.0 / 32.0) +
sh_inner[lid.x+1][lid.y+1] * (4.0 / 32.0);
vec4 resultOuter =
sh_outer[lid.x ][lid.y ] * (1.0 / 32.0) +
sh_outer[lid.x+1][lid.y ] * (2.0 / 32.0) +
sh_outer[lid.x+2][lid.y ] * (1.0 / 32.0) +
sh_outer[lid.x ][lid.y+1] * (2.0 / 32.0) +
sh_outer[lid.x+1][lid.y+1] * (4.0 / 32.0) +
sh_outer[lid.x+2][lid.y+1] * (2.0 / 32.0) +
sh_outer[lid.x ][lid.y+2] * (1.0 / 32.0) +
sh_outer[lid.x+1][lid.y+2] * (2.0 / 32.0) +
sh_outer[lid.x+2][lid.y+2] * (1.0 / 32.0);
imageStore(i_target, ivec2(gid), resultInner + resultOuter);
}

@ -0,0 +1,91 @@
#version 460
#pragma shader_stage(compute)
layout(local_size_x = 16, local_size_y = 16) in;
#include "../util.glsl"
layout(binding = 0) uniform sampler2D s_source;
layout(binding = 1) restrict writeonly uniform image2D i_target;
layout(constant_id = 0) const uint SourceSizePacked = 0;
layout(constant_id = 1) const uint TargetSizePacked = 0;
const uvec2 SourceSize = U16FROMU32(SourceSizePacked);
const uvec2 TargetSize = U16FROMU32(TargetSizePacked);
shared vec4 sh_outer[gl_WorkGroupSize.x + 2][gl_WorkGroupSize.y + 2];
shared vec4 sh_inner[gl_WorkGroupSize.x + 1][gl_WorkGroupSize.y + 1];
void main() {
uvec2 gid = gl_GlobalInvocationID.xy;
uvec2 lid = gl_LocalInvocationID.xy;
// Fill up the filter tap cache
vec2 uv = (vec2(gid) + vec2(0.5)) / vec2(TargetSize);
vec2 texel = vec2(1.0) / vec2(SourceSize);
vec2 texel2 = texel * 2.0;
if (lid.x <= 1)
sh_outer[lid.x ][lid.y+2] = textureLod(s_source, uv + vec2(-texel2.x, texel2.y), 0.0);
if (lid.y <= 1)
sh_outer[lid.x+2][lid.y ] = textureLod(s_source, uv + vec2(texel2.x, -texel2.y), 0.0);
if (lid.x <= 1 && lid.y <= 1)
sh_outer[lid.x ][lid.y ] = textureLod(s_source, uv - texel2, 0.0);
sh_outer[lid.x+2][lid.y+2] = textureLod(s_source, uv + texel2, 0.0);
if (lid.x == 0)
sh_inner[lid.x ][lid.y+1] = textureLod(s_source, uv + vec2(-texel.x, texel.y), 0.0);
if (lid.y == 0)
sh_inner[lid.x+1][lid.y ] = textureLod(s_source, uv + vec2(texel.x, -texel.y), 0.0);
if (lid.x == 0 && lid.y == 0)
sh_inner[lid.x ][lid.y ] = textureLod(s_source, uv - texel, 0.0);
sh_inner[lid.x+1][lid.y+1] = textureLod(s_source, uv + texel, 0.0);
barrier();
if (any(greaterThanEqual(gid, TargetSize)))
return; // We can't return before all barriers are passed
// Convolve source texture
vec4 resultInner = karisAverage(
sh_inner[lid.x ][lid.y ],
sh_inner[lid.x+1][lid.y ],
sh_inner[lid.x ][lid.y+1],
sh_inner[lid.x+1][lid.y+1]);
vec4 resultOuter1 = karisAverage(
sh_outer[lid.x ][lid.y ],
sh_outer[lid.x+1][lid.y ],
sh_outer[lid.x ][lid.y+1],
sh_outer[lid.x+1][lid.y+1]);
vec4 resultOuter2 = karisAverage(
sh_outer[lid.x+1][lid.y ],
sh_outer[lid.x+2][lid.y ],
sh_outer[lid.x+1][lid.y+1],
sh_outer[lid.x+2][lid.y+1]);
vec4 resultOuter3 = karisAverage(
sh_outer[lid.x ][lid.y+1],
sh_outer[lid.x+1][lid.y+1],
sh_outer[lid.x ][lid.y+2],
sh_outer[lid.x+1][lid.y+2]);
vec4 resultOuter4 = karisAverage(
sh_outer[lid.x+1][lid.y+1],
sh_outer[lid.x+2][lid.y+1],
sh_outer[lid.x+1][lid.y+2],
sh_outer[lid.x+2][lid.y+2]);
vec4 result =
resultInner * 0.5 +
resultOuter1 * 0.125 +
resultOuter2 * 0.125 +
resultOuter3 * 0.125 +
resultOuter4 * 0.125;
imageStore(i_target, ivec2(gid), result);
}

@ -0,0 +1,46 @@
#version 460
#pragma shader_stage(compute)
layout(local_size_x = 8, local_size_y = 8) in;
#include "../util.glsl"
layout(binding = 0) uniform sampler2D s_source;
layout(binding = 1) uniform sampler2D s_targetRead;
layout(binding = 2) writeonly uniform image2D i_target;
layout(constant_id = 0) const uint SourceSizePacked = 0;
layout(constant_id = 1) const uint TargetSizePacked = 0;
const uvec2 SourceSize = U16FROMU32(SourceSizePacked);
const uvec2 TargetSize = U16FROMU32(TargetSizePacked);
layout(push_constant) uniform Constants {
float u_power;
};
void main() {
const uvec2 gid = gl_GlobalInvocationID.xy;
const uvec2 lid = gl_LocalInvocationID.xy;
if (any(greaterThanEqual(gid, TargetSize)))
return;
vec2 uv = (vec2(gid) + vec2(0.5)) / vec2(TargetSize);
vec2 texel = vec2(1.0) / vec2(SourceSize);
// Upscale fetches can't be cached in the same way as downscale
vec4 current = texelFetch(s_targetRead, ivec2(gid), 0);
vec4 result = textureLod(s_source, uv, 0.0) * (4.0 / 16.0);
result += textureLod(s_source, uv + vec2(-texel.x, 0.0), 0.0) * (2.0 / 16.0);
result += textureLod(s_source, uv + vec2( texel.x, 0.0), 0.0) * (2.0 / 16.0);
result += textureLod(s_source, uv + vec2( 0.0, -texel.y), 0.0) * (2.0 / 16.0);
result += textureLod(s_source, uv + vec2( 0.0, texel.y), 0.0) * (2.0 / 16.0);
result += textureLod(s_source, uv + vec2(-texel.x, -texel.y), 0.0) * (1.0 / 16.0);
result += textureLod(s_source, uv + vec2( texel.x, -texel.y), 0.0) * (1.0 / 16.0);
result += textureLod(s_source, uv + vec2(-texel.x, texel.y), 0.0) * (1.0 / 16.0);
result += textureLod(s_source, uv + vec2( texel.x, texel.y), 0.0) * (1.0 / 16.0);
imageStore(i_target, ivec2(gid), current + result * u_power);
}

@ -0,0 +1,10 @@
#version 460
#pragma shader_stage(fragment)
layout(location = 0) out vec4 out_color;
void main() {
out_color = vec4(1.0, 1.0, 0.0, 1.0);
}

@ -0,0 +1,79 @@
#version 460
#pragma shader_stage(vertex)
#include "../types.glsl"
layout(binding = 0) uniform WorldConstants {
World u_world;
};
layout(binding = 1, std430) restrict readonly buffer AABBs {
float b_AABBs[];
};
layout(binding = 2, std430) restrict readonly buffer Instances {
Instance b_instances[];
};
layout(binding = 3, std430) restrict readonly buffer Transforms {
mat3x4 b_transforms[];
};
void main() {
Instance instance = b_instances[gl_InstanceIndex];
uint aabbOffset = instance.meshletIdx * 6;
vec3 aabbMin = {
b_AABBs[aabbOffset + 0],
b_AABBs[aabbOffset + 1],
b_AABBs[aabbOffset + 2]};
vec3 aabbMax = {
b_AABBs[aabbOffset + 3],
b_AABBs[aabbOffset + 4],
b_AABBs[aabbOffset + 5]};
vec3 vertex;
switch (gl_VertexIndex) {
case 0: vertex = vec3(aabbMin.x, aabbMin.y, aabbMin.z); break;
case 1: vertex = vec3(aabbMax.x, aabbMin.y, aabbMin.z); break;
case 2: vertex = vec3(aabbMin.x, aabbMax.y, aabbMin.z); break;
case 3: vertex = vec3(aabbMax.x, aabbMax.y, aabbMin.z); break;
case 4: vertex = vec3(aabbMin.x, aabbMin.y, aabbMin.z); break;
case 5: vertex = vec3(aabbMin.x, aabbMax.y, aabbMin.z); break;
case 6: vertex = vec3(aabbMax.x, aabbMin.y, aabbMin.z); break;
case 7: vertex = vec3(aabbMax.x, aabbMax.y, aabbMin.z); break;
case 8: vertex = vec3(aabbMin.x, aabbMin.y, aabbMax.z); break;
case 9: vertex = vec3(aabbMax.x, aabbMin.y, aabbMax.z); break;
case 10: vertex = vec3(aabbMin.x, aabbMax.y, aabbMax.z); break;
case 11: vertex = vec3(aabbMax.x, aabbMax.y, aabbMax.z); break;
case 12: vertex = vec3(aabbMin.x, aabbMin.y, aabbMax.z); break;
case 13: vertex = vec3(aabbMin.x, aabbMax.y, aabbMax.z); break;
case 14: vertex = vec3(aabbMax.x, aabbMin.y, aabbMax.z); break;
case 15: vertex = vec3(aabbMax.x, aabbMax.y, aabbMax.z); break;
case 16: vertex = vec3(aabbMin.x, aabbMin.y, aabbMin.z); break;
case 17: vertex = vec3(aabbMin.x, aabbMin.y, aabbMax.z); break;
case 18: vertex = vec3(aabbMax.x, aabbMin.y, aabbMin.z); break;
case 19: vertex = vec3(aabbMax.x, aabbMin.y, aabbMax.z); break;
case 20: vertex = vec3(aabbMin.x, aabbMax.y, aabbMin.z); break;
case 21: vertex = vec3(aabbMin.x, aabbMax.y, aabbMax.z); break;
case 22: vertex = vec3(aabbMax.x, aabbMax.y, aabbMin.z); break;
case 23: vertex = vec3(aabbMax.x, aabbMax.y, aabbMax.z); break;
}
uint transformIdx = instance.objectIdx;
mat4 transform = getTransform(b_transforms[transformIdx]);
gl_Position = u_world.viewProjection * transform * vec4(vertex, 1.0);
}

@ -0,0 +1,6 @@
#ifndef CONSTANTS_GLSL
#define CONSTANTS_GLSL
#define PI 3.1415926535897932384626433832795
#endif //CONSTANTS_GLSL

@ -0,0 +1,313 @@
// Copyright 2016 Activision Publishing, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#version 460
#pragma shader_stage(compute)
layout(local_size_x = 64) in;
#define NUM_TAPS 32
#define BASE_RESOLUTION 128
layout(binding = 0) uniform samplerCube s_source;
layout(binding = 1) restrict writeonly uniform image2DArray i_target0;
layout(binding = 2) restrict writeonly uniform image2DArray i_target1;
layout(binding = 3) restrict writeonly uniform image2DArray i_target2;
layout(binding = 4) restrict writeonly uniform image2DArray i_target3;
layout(binding = 5) restrict writeonly uniform image2DArray i_target4;
layout(binding = 6) restrict writeonly uniform image2DArray i_target5;
layout(binding = 7) restrict writeonly uniform image2DArray i_target6;
layout(binding = 8) uniform Coeffs {
vec4 u_coeffs[7][5][3][24];
};
void get_dir(out vec3 _dir, vec2 _uv, uint _face) {
switch (_face) {
case 0:
_dir[0] = 1;
_dir[1] = _uv[1];
_dir[2] = -_uv[0];
break;
case 1:
_dir[0] = -1;
_dir[1] = _uv[1];
_dir[2] = _uv[0];
break;
case 2:
_dir[0] = _uv[0];
_dir[1] = 1;
_dir[2] = -_uv[1];
break;
case 3:
_dir[0] = _uv[0];
_dir[1] = -1;
_dir[2] = _uv[1];
break;
case 4:
_dir[0] = _uv[0];
_dir[1] = _uv[1];
_dir[2] = 1;
break;
default:
_dir[0] = -_uv[0];
_dir[1] = _uv[1];
_dir[2] = -1;
break;
}
}
void main() {
// INPUT:
// gid.x = the linear address of the texel (ignoring face) - max 21,840
// gid.y = the face
// -> use to index output texture
// gid.x = texel x
// gid.y = texel y
// gid.z = face
uvec3 gid = gl_GlobalInvocationID;
// determine which texel this is
int level;
if (gid.x < 128 * 128) {
level = 0;
} else if (gid.x < 128 * 128 + 64 * 64) {
level = 1;
gid.x -= ( 128 * 128 );
} else if (gid.x < 128 * 128 + 64 * 64 + 32 * 32) {
level = 2;
gid.x -= ( 128 * 128 + 64 * 64 );
} else if (gid.x < 128 * 128 + 64 * 64 + 32 * 32 + 16 * 16) {
level = 3;
gid.x -= ( 128 * 128 + 64 * 64 + 32 * 32 );
} else if (gid.x < 128 * 128 + 64 * 64 + 32 * 32 + 16 * 16 + 8 * 8) {
level = 4;
gid.x -= ( 128 * 128 + 64 * 64 + 32 * 32 + 16 * 16 );
} else if (gid.x < 128 * 128 + 64 * 64 + 32 * 32 + 16 * 16 + 8 * 8 + 4 * 4) {
level = 5;
gid.x -= ( 128 * 128 + 64 * 64 + 32 * 32 + 16 * 16 + 8 * 8 );
} else if (gid.x < 128 * 128 + 64 * 64 + 32 * 32 + 16 * 16 + 8 * 8 + 4 * 4 + 2 * 2) {
level = 6;
gid.x -= ( 128 * 128 + 64 * 64 + 32 * 32 + 16 * 16 + 8 * 8 + 4 * 4 );
} else {
return;
}
// determine dir / pos for the texel
vec3 dir, adir, frameZ;
{
gid.z = gid.y;
int res = BASE_RESOLUTION >> level;
gid.y = gid.x / res;
gid.x -= gid.y * res;
vec2 uv = {
(float(gid.x) * 2.0 + 1.0) / float(res) - 1.0,
-(float(gid.y) * 2.0 + 1.0) / float(res) + 1.0};
get_dir(dir, uv, gid.z);
frameZ = normalize(dir);
adir = abs(dir);
}
// GGX gather colors
vec4 color = vec4(0.0);
for (uint axis = 0; axis < 3; axis += 1) {
uint otherAxis0 = 1 - (axis & 1u) - (axis >> 1);
uint otherAxis1 = 2 - (axis >> 1);
float frameweight = (max(adir[otherAxis0], adir[otherAxis1]) - 0.75) / 0.25;
if (frameweight > 0) {
// determine frame
vec3 upVector;
switch (axis) {
case 0:
upVector = vec3(1.0, 0.0, 0.0);
break;
case 1:
upVector = vec3(0.0, 1.0, 0.0);
break;
default:
upVector = vec3(0.0, 0.0, 1.0);
break;
}
vec3 frameX = normalize(cross(upVector, frameZ));
vec3 frameY = cross(frameZ, frameX);
// calculate parametrization for polynomial
float nx = dir[otherAxis0];
float ny = dir[otherAxis1];
float nz = adir[axis];
float nMaxXY = max(abs(ny), abs(nx));
nx /= nMaxXY;
ny /= nMaxXY;
float theta;
if (ny < nx) {
if (ny <= -0.999)
theta = nx;
else
theta = ny;
} else {
if (ny >= 0.999)
theta = -nx;
else
theta = -ny;
}
float phi;
if (nz <= -0.999)
phi = -nMaxXY;
else if (nz >= 0.999)
phi = nMaxXY;
else
phi = nz;
float theta2 = theta*theta;
float phi2 = phi*phi;
// sample
for (uint iSuperTap = 0; iSuperTap < NUM_TAPS / 4; iSuperTap += 1) {
uint index = (NUM_TAPS / 4) * axis + iSuperTap;
vec4 coeffsDir0[3];
vec4 coeffsDir1[3];
vec4 coeffsDir2[3];
vec4 coeffsLevel[3];
vec4 coeffsWeight[3];
for (uint iCoeff = 0; iCoeff < 3; iCoeff += 1) {
coeffsDir0[iCoeff] = u_coeffs[level][0][iCoeff][index];
coeffsDir1[iCoeff] = u_coeffs[level][1][iCoeff][index];
coeffsDir2[iCoeff] = u_coeffs[level][2][iCoeff][index];
coeffsLevel[iCoeff] = u_coeffs[level][3][iCoeff][index];
coeffsWeight[iCoeff] = u_coeffs[level][4][iCoeff][index];
}
for (uint iSubTap = 0; iSubTap < 4; iSubTap += 1) {
// determine sample attributes (dir, weight, level)
vec3 sampleDir =
frameX * (coeffsDir0[0][iSubTap] + coeffsDir0[1][iSubTap] * theta2 + coeffsDir0[2][iSubTap] * phi2) +
frameY * (coeffsDir1[0][iSubTap] + coeffsDir1[1][iSubTap] * theta2 + coeffsDir1[2][iSubTap] * phi2) +
frameZ * (coeffsDir2[0][iSubTap] + coeffsDir2[1][iSubTap] * theta2 + coeffsDir2[2][iSubTap] * phi2);
float sampleLevel = coeffsLevel[0][iSubTap] + coeffsLevel[1][iSubTap] * theta2 + coeffsLevel[2][iSubTap] * phi2;
float sampleWeight = coeffsWeight[0][iSubTap] + coeffsWeight[1][iSubTap] * theta2 + coeffsWeight[2][iSubTap] * phi2;
sampleWeight *= frameweight;
// adjust for jacobian
sampleDir /= max(abs(sampleDir[0]), max(abs(sampleDir[1]), abs(sampleDir[2])));
sampleLevel += 0.75 * log2(dot(sampleDir, sampleDir));
// sample cubemap
color.xyz += textureLod(s_source, sampleDir, sampleLevel + 1.0).xyz * sampleWeight;
color.w += sampleWeight;
}
}
}
}
color /= color.w;
// write color
color.xyz = max(vec3(0.0), color.xyz);
color.w = 1;
switch (level) {
case 0:
imageStore(i_target0, ivec3(gid), color);
break;
case 1:
imageStore(i_target1, ivec3(gid), color);
break;
case 2:
imageStore(i_target2, ivec3(gid), color);
break;
case 3:
imageStore(i_target3, ivec3(gid), color);
break;
case 4:
imageStore(i_target4, ivec3(gid), color);
break;
case 5:
imageStore(i_target5, ivec3(gid), color);
break;
default:
imageStore(i_target6, ivec3(gid), color);
break;
}
}

@ -0,0 +1,201 @@
// Copyright 2016 Activision Publishing, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#version 460
#pragma shader_stage(compute)
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
#include "../util.glsl"
layout(binding = 0) uniform samplerCube s_source;
layout(binding = 1) restrict writeonly uniform image2DArray i_target;
layout(constant_id = 0) const uint TargetSize = 0;
void get_dir_0(out vec3 _dir, float _u, float _v) {
_dir[0] = 1;
_dir[1] = _v;
_dir[2] = -_u;
}
void get_dir_1(out vec3 _dir, in float _u, in float _v) {
_dir[0] = -1;
_dir[1] = _v;
_dir[2] = _u;
}
void get_dir_2(out vec3 _dir, float _u, float _v) {
_dir[0] = _u;
_dir[1] = 1;
_dir[2] = -_v;
}
void get_dir_3(out vec3 _dir, float _u, float _v) {
_dir[0] = _u;
_dir[1] = -1;
_dir[2] = _v;
}
void get_dir_4(out vec3 _dir, float _u, float _v) {
_dir[0] = _u;
_dir[1] = _v;
_dir[2] = 1;
}
void get_dir_5(out vec3 _dir, float _u, float _v) {
_dir[0] = -_u;
_dir[1] = _v;
_dir[2] = -1;
}
float calcWeight(float _u, float _v) {
float val = _u*_u + _v*_v + 1;
return val * sqrt(val);
}
void main() {
uvec3 gid = gl_GlobalInvocationID;
float inv_res_lo = 1.0 / float(TargetSize);
float u0 = (float(gid.x) * 2.0 + 1.0 - 0.75) * inv_res_lo - 1.0;
float u1 = (float(gid.x) * 2.0 + 1.0 + 0.75) * inv_res_lo - 1.0;
float v0 = (float(gid.y) * 2.0 + 1.0 - 0.75) * -inv_res_lo + 1.0;
float v1 = (float(gid.y) * 2.0 + 1.0 + 0.75) * -inv_res_lo + 1.0;
float weights[4] = {
calcWeight(u0, v0),
calcWeight(u1, v0),
calcWeight(u0, v1),
calcWeight(u1, v1)};
float wsum = 0.5 / (weights[0] + weights[1] + weights[2] + weights[3]);
for (uint i = 0; i < 4; i += 1)
weights[i] = weights[i] * wsum + 0.125;
vec3 dir;
vec4 color;
switch (gid.z) {
case 0:
get_dir_0(dir, u0, v0);
color = textureLod(s_source, dir, 0) * weights[0];
get_dir_0(dir, u1, v0);
color += textureLod(s_source, dir, 0) * weights[1];
get_dir_0(dir, u0, v1);
color += textureLod(s_source, dir, 0) * weights[2];
get_dir_0(dir, u1, v1);
color += textureLod(s_source, dir, 0) * weights[3];
break;
case 1:
get_dir_1(dir, u0, v0);
color = textureLod(s_source, dir, 0) * weights[0];
get_dir_1(dir, u1, v0);
color += textureLod(s_source, dir, 0) * weights[1];
get_dir_1(dir, u0, v1);
color += textureLod(s_source, dir, 0) * weights[2];
get_dir_1(dir, u1, v1);
color += textureLod(s_source, dir, 0) * weights[3];
break;
case 2:
get_dir_2(dir, u0, v0);
color = textureLod(s_source, dir, 0) * weights[0];
get_dir_2(dir, u1, v0);
color += textureLod(s_source, dir, 0) * weights[1];
get_dir_2(dir, u0, v1);
color += textureLod(s_source, dir, 0) * weights[2];
get_dir_2(dir, u1, v1);
color += textureLod(s_source, dir, 0) * weights[3];
break;
case 3:
get_dir_3(dir, u0, v0);
color = textureLod(s_source, dir, 0) * weights[0];
get_dir_3(dir, u1, v0);
color += textureLod(s_source, dir, 0) * weights[1];
get_dir_3(dir, u0, v1);
color += textureLod(s_source, dir, 0) * weights[2];
get_dir_3(dir, u1, v1);
color += textureLod(s_source, dir, 0) * weights[3];
break;
case 4:
get_dir_4(dir, u0, v0);
color = textureLod(s_source, dir, 0) * weights[0];
get_dir_4(dir, u1, v0);
color += textureLod(s_source, dir, 0) * weights[1];
get_dir_4(dir, u0, v1);
color += textureLod(s_source, dir, 0) * weights[2];
get_dir_4(dir, u1, v1);
color += textureLod(s_source, dir, 0) * weights[3];
break;
default:
get_dir_5(dir, u0, v0);
color = textureLod(s_source, dir, 0) * weights[0];
get_dir_5(dir, u1, v0);
color += textureLod(s_source, dir, 0) * weights[1];
get_dir_5(dir, u0, v1);
color += textureLod(s_source, dir, 0) * weights[2];
get_dir_5(dir, u1, v1);
color += textureLod(s_source, dir, 0) * weights[3];
break;
}
imageStore(i_target, ivec3(gid), color);
}

@ -0,0 +1,179 @@
#version 460
#pragma shader_stage(compute)
#extension GL_KHR_shader_subgroup_basic: enable
#extension GL_KHR_shader_subgroup_clustered: enable
layout(local_size_x = 32, local_size_y = 32) in;
#include "../util.glsl"
layout(binding = 0) uniform sampler2DMS s_depth;
layout(binding = 1) restrict writeonly uniform image2D i_hiz0;
layout(binding = 2) restrict writeonly uniform image2D i_hiz1;
layout(binding = 3) restrict writeonly uniform image2D i_hiz2;
layout(binding = 4) restrict writeonly uniform image2D i_hiz3;
layout(binding = 5) restrict writeonly uniform image2D i_hiz4;
layout(binding = 6) restrict writeonly uniform image2D i_hiz5;
layout(constant_id = 0) const uint DepthSizePacked = 0;
const uvec2 DepthSize = uvec2(U16FROMU32(DepthSizePacked));
layout(constant_id = 1) const uint HiZSizePacked = 0;
const uvec2 HiZSize = uvec2(U16FROMU32(HiZSizePacked));
layout(constant_id = 2) const uint MipCount = 0;
// Tile buffer for cross-thread communication of results
shared float sh_temp[gl_WorkGroupSize.x * gl_WorkGroupSize.y];
void storeTemp(uvec2 pos, float val) {
sh_temp[pos.x + pos.y * gl_WorkGroupSize.x] = val;
}
float loadTemp(uvec2 pos) {
return sh_temp[pos.x + pos.y * gl_WorkGroupSize.x];
}
void main() {
uvec2 lid = mortonOrder(gl_LocalInvocationID.x + gl_LocalInvocationID.y * gl_WorkGroupSize.x);
uvec2 tileOffset = gl_WorkGroupID.xy * gl_WorkGroupSize.xy;
uvec2 gid = tileOffset + lid;
uvec2 depthAligned = (DepthSize + gl_WorkGroupSize.xy - 1) & ~(gl_WorkGroupSize.xy - 1);
ivec2 depthOffset = (ivec2(DepthSize) - ivec2(depthAligned)) / 2;
uvec2 hizOffset = (HiZSize - depthAligned) / 2;
float minSample = 1.0;
for (uint i = 0; i < 8; i += 1) {
float newSample = 1.0;
ivec2 samplePos = ivec2(gid) + depthOffset;
if (all(greaterThanEqual(samplePos, ivec2(0))) && all(lessThan(samplePos, DepthSize)))
newSample = min(minSample, texelFetch(s_depth, ivec2(gid) + depthOffset, int(i)).x);
minSample = min(minSample, newSample);
}
// Write resolved multisampled value
uvec2 writePos = gid + hizOffset;
imageStore(i_hiz0, ivec2(writePos), vec4(minSample, 0.0, 0.0, 0.0));
// Use subgroup ops as much as possible
if (MipCount < 2)
return;
if (gl_SubgroupSize >= 4) {
minSample = subgroupClusteredMin(minSample, 4);
if (gl_SubgroupInvocationID % 4 == 0)
imageStore(i_hiz1, ivec2(writePos / 2), vec4(minSample, 0.0, 0.0, 0.0));
} else {
storeTemp(lid, minSample);
barrier();
if (all(equal(lid % 2, uvec2(0)))) {
minSample = min(minSample, loadTemp(lid + uvec2(1, 0)));
minSample = min(minSample, loadTemp(lid + uvec2(0, 1)));
minSample = min(minSample, loadTemp(lid + uvec2(1, 1)));
imageStore(i_hiz1, ivec2(writePos / 2), vec4(minSample, 0.0, 0.0, 0.0));
}
}
if (MipCount < 3)
return;
if (gl_SubgroupSize >= 16) {
minSample = subgroupClusteredMin(minSample, 16);
if (gl_SubgroupInvocationID % 16 == 0)
imageStore(i_hiz2, ivec2(writePos / 4), vec4(minSample, 0.0, 0.0, 0.0));
} else {
if (all(equal(lid % 2, uvec2(0))))
storeTemp(lid, minSample);
barrier();
if (all(equal(lid % 4, uvec2(0)))) {
minSample = min(minSample, loadTemp(lid + uvec2(2, 0)));
minSample = min(minSample, loadTemp(lid + uvec2(0, 2)));
minSample = min(minSample, loadTemp(lid + uvec2(2, 2)));
imageStore(i_hiz2, ivec2(writePos / 4), vec4(minSample, 0.0, 0.0, 0.0));
}
}
if (MipCount < 4)
return;
if (gl_SubgroupSize >= 64) {
minSample = subgroupClusteredMin(minSample, 64);
if (gl_SubgroupInvocationID % 64 == 0)
imageStore(i_hiz3, ivec2(writePos / 8), vec4(minSample, 0.0, 0.0, 0.0));
} else {
if (all(equal(lid % 4, uvec2(0))))
storeTemp(lid, minSample);
barrier();
if (all(equal(lid % 8, uvec2(0)))) {
minSample = min(minSample, loadTemp(lid + uvec2(4, 0)));
minSample = min(minSample, loadTemp(lid + uvec2(0, 4)));
minSample = min(minSample, loadTemp(lid + uvec2(4, 4)));
imageStore(i_hiz3, ivec2(writePos / 8), vec4(minSample, 0.0, 0.0, 0.0));
}
}
// Workgroup-only pass
if (MipCount < 5)
return;
if (all(equal(lid % 8, uvec2(0))))
storeTemp(lid, minSample);
barrier();
if (all(equal(lid % 16, uvec2(0)))) {
minSample = min(minSample, loadTemp(lid + uvec2(8, 0)));
minSample = min(minSample, loadTemp(lid + uvec2(0, 8)));
minSample = min(minSample, loadTemp(lid + uvec2(8, 8)));
imageStore(i_hiz4, ivec2(writePos / 16), vec4(minSample, 0.0, 0.0, 0.0));
}
// Workgroup-only pass
if (MipCount < 6)
return;
if (all(equal(lid % 16, uvec2(0))))
storeTemp(lid, minSample);
barrier();
if (all(equal(lid /* % 32 */, uvec2(0)))) {
minSample = min(minSample, loadTemp(lid + uvec2(16, 0)));
minSample = min(minSample, loadTemp(lid + uvec2(0, 16)));
minSample = min(minSample, loadTemp(lid + uvec2(16, 16)));
imageStore(i_hiz5, ivec2(writePos / 32), vec4(minSample, 0.0, 0.0, 0.0));
}
}

@ -0,0 +1,184 @@
#version 460
#pragma shader_stage(compute)
#extension GL_KHR_shader_subgroup_basic: enable
#extension GL_KHR_shader_subgroup_clustered: enable
layout(local_size_x = 32, local_size_y = 32) in;
#include "../util.glsl"
layout(binding = 0) uniform sampler2D s_hizSrc;
layout(binding = 1) restrict writeonly uniform image2D i_hiz0;
layout(binding = 2) restrict writeonly uniform image2D i_hiz1;
layout(binding = 3) restrict writeonly uniform image2D i_hiz2;
layout(binding = 4) restrict writeonly uniform image2D i_hiz3;
layout(binding = 5) restrict writeonly uniform image2D i_hiz4;
layout(binding = 6) restrict writeonly uniform image2D i_hiz5;
layout(binding = 7) restrict writeonly uniform image2D i_hiz6;
layout(constant_id = 0) const uint HiZSizePacked = 0;
const uvec2 HiZSize = uvec2(U16FROMU32(HiZSizePacked));
layout(constant_id = 1) const uint MipCount = 0;
// Tile buffer for cross-thread communication of results
shared float sh_temp[gl_WorkGroupSize.x * gl_WorkGroupSize.y];
void storeTemp(uvec2 pos, float val) {
sh_temp[pos.x + pos.y * gl_WorkGroupSize.x] = val;
}
float loadTemp(uvec2 pos) {
return sh_temp[pos.x + pos.y * gl_WorkGroupSize.x];
}
void main() {
uvec2 lid = mortonOrder(gl_LocalInvocationID.x + gl_LocalInvocationID.y * gl_WorkGroupSize.x);
uvec2 tileOffset = gl_WorkGroupID.xy * gl_WorkGroupSize.xy;
uvec2 gid = tileOffset + lid;
// Handle first two levels with a min sampler
uvec2 sourcePos = gid * 4;
vec2 sourcePitch = rcp(vec2(HiZSize));
float minSamples[4];
minSamples[0] = textureLod(s_hizSrc, (vec2(sourcePos) + vec2(1.0, 1.0)) * sourcePitch, 0.0).x;
minSamples[1] = textureLod(s_hizSrc, (vec2(sourcePos) + vec2(3.0, 1.0)) * sourcePitch, 0.0).x;
minSamples[2] = textureLod(s_hizSrc, (vec2(sourcePos) + vec2(1.0, 3.0)) * sourcePitch, 0.0).x;
minSamples[3] = textureLod(s_hizSrc, (vec2(sourcePos) + vec2(3.0, 3.0)) * sourcePitch, 0.0).x;
imageStore(i_hiz0, ivec2(gid * 2 + uvec2(0, 0)), vec4(minSamples[0], 0.0, 0.0, 0.0));
imageStore(i_hiz0, ivec2(gid * 2 + uvec2(1, 0)), vec4(minSamples[1], 0.0, 0.0, 0.0));
imageStore(i_hiz0, ivec2(gid * 2 + uvec2(0, 1)), vec4(minSamples[2], 0.0, 0.0, 0.0));
imageStore(i_hiz0, ivec2(gid * 2 + uvec2(1, 1)), vec4(minSamples[3], 0.0, 0.0, 0.0));
if (MipCount < 2)
return;
float minSample = minSamples[0];
minSample = min(minSample, minSamples[1]);
minSample = min(minSample, minSamples[2]);
minSample = min(minSample, minSamples[3]);
imageStore(i_hiz1, ivec2(gid), vec4(minSample, 0.0, 0.0, 0.0));
// Use subgroup ops as much as possible
if (MipCount < 3)
return;
if (gl_SubgroupSize >= 4) {
minSample = subgroupClusteredMin(minSample, 4);
if (gl_SubgroupInvocationID % 4 == 0)
imageStore(i_hiz2, ivec2(gid / 2), vec4(minSample, 0.0, 0.0, 0.0));
} else {
storeTemp(lid, minSample);
barrier();
if (all(equal(lid % 2, uvec2(0)))) {
minSample = min(minSample, loadTemp(lid + uvec2(1, 0)));
minSample = min(minSample, loadTemp(lid + uvec2(0, 1)));
minSample = min(minSample, loadTemp(lid + uvec2(1, 1)));
imageStore(i_hiz2, ivec2(gid / 2), vec4(minSample, 0.0, 0.0, 0.0));
}
}
if (MipCount < 4)
return;
if (gl_SubgroupSize >= 16) {
minSample = subgroupClusteredMin(minSample, 16);
if (gl_SubgroupInvocationID % 16 == 0)
imageStore(i_hiz3, ivec2(gid / 4), vec4(minSample, 0.0, 0.0, 0.0));
} else {
if (all(equal(lid % 2, uvec2(0))))
storeTemp(lid, minSample);
barrier();
if (all(equal(lid % 4, uvec2(0)))) {
minSample = min(minSample, loadTemp(lid + uvec2(2, 0)));
minSample = min(minSample, loadTemp(lid + uvec2(0, 2)));
minSample = min(minSample, loadTemp(lid + uvec2(2, 2)));
imageStore(i_hiz3, ivec2(gid / 4), vec4(minSample, 0.0, 0.0, 0.0));
}
}
if (MipCount < 5)
return;
if (gl_SubgroupSize >= 64) {
minSample = subgroupClusteredMin(minSample, 64);
if (gl_SubgroupInvocationID % 64 == 0)
imageStore(i_hiz4, ivec2(gid / 8), vec4(minSample, 0.0, 0.0, 0.0));
} else {
if (all(equal(lid % 4, uvec2(0))))
storeTemp(lid, minSample);
barrier();
if (all(equal(lid % 8, uvec2(0)))) {
minSample = min(minSample, loadTemp(lid + uvec2(4, 0)));
minSample = min(minSample, loadTemp(lid + uvec2(0, 4)));
minSample = min(minSample, loadTemp(lid + uvec2(4, 4)));
imageStore(i_hiz4, ivec2(gid / 8), vec4(minSample, 0.0, 0.0, 0.0));
}
}
// Workgroup-only pass
if (MipCount < 6)
return;
if (all(equal(lid % 8, uvec2(0))))
storeTemp(lid, minSample);
barrier();
if (all(equal(lid % 16, uvec2(0)))) {
minSample = min(minSample, loadTemp(lid + uvec2(8, 0)));
minSample = min(minSample, loadTemp(lid + uvec2(0, 8)));
minSample = min(minSample, loadTemp(lid + uvec2(8, 8)));
imageStore(i_hiz5, ivec2(gid / 16), vec4(minSample, 0.0, 0.0, 0.0));
}
// Workgroup-only pass
if (MipCount < 7)
return;
if (all(equal(lid % 16, uvec2(0))))
storeTemp(lid, minSample);
barrier();
if (all(equal(lid /* % 32 */, uvec2(0)))) {
minSample = min(minSample, loadTemp(lid + uvec2(16, 0)));
minSample = min(minSample, loadTemp(lid + uvec2(0, 16)));
minSample = min(minSample, loadTemp(lid + uvec2(16, 16)));
imageStore(i_hiz6, ivec2(gid / 32), vec4(minSample, 0.0, 0.0, 0.0));
}
}

@ -0,0 +1,15 @@
#version 460
#pragma shader_stage(fragment)
layout(location = 0) in vec4 f_color;
layout(location = 1) in vec2 f_uv;
layout(location = 0) out vec4 out_color;
layout(binding = 0) uniform sampler2D s_texture;
void main() {
out_color = f_color * texture(s_texture, f_uv.st);
}

@ -0,0 +1,22 @@
#version 460
#pragma shader_stage(vertex)
layout(location = 0) in vec2 v_pos;
layout(location = 1) in vec2 v_uv;
layout(location = 2) in vec4 v_color;
layout(location = 0) out vec4 f_color;
layout(location = 1) out vec2 f_uv;
layout(push_constant) uniform Constants {
vec2 u_scale;
vec2 u_translate;
};
void main() {
f_color = v_color;
f_uv = v_uv;
gl_Position = vec4(v_pos * u_scale + u_translate, 0.0, 1.0);
}

@ -0,0 +1,155 @@
#version 460
#pragma shader_stage(compute)
layout(local_size_x = 64) in;
#include "../types.glsl"
#include "../util.glsl"
layout(binding = 0) uniform CullingData {
mat4 u_view;
vec4 u_frustum;
float u_P00;
float u_P11;
};
layout(binding = 1, std430) restrict readonly buffer Meshlets {
Meshlet b_meshlets[];
};
layout(binding = 2, std430) restrict readonly buffer Instances {
Instance b_instances[];
};
layout(binding = 3, std430) restrict readonly buffer Transforms {
mat3x4 b_transforms[];
};
layout(binding = 4) uniform sampler2D s_hiz;
layout(binding = 5, std430) restrict buffer OutInstanceCount {
uvec4 b_outInstanceCount;
};
layout(binding = 6, std430) restrict writeonly buffer OutInstances {
Instance b_outInstances[];
};
layout(binding = 7, std430) restrict buffer GroupCounter {
uint b_groupCounter;
};
layout(push_constant) uniform Constants {
uint u_instanceCount;
};
layout(constant_id = 0) const uint MaxTrisPerMeshlet = 0;
layout(constant_id = 1) const float ZNear = 0.0;
layout(constant_id = 2) const uint HiZSizePacked = 0;
const uvec2 HiZSize = uvec2(U16FROMU32(HiZSizePacked));
layout(constant_id = 3) const uint HiZInnerPacked = 0;
const uvec2 HiZInner = uvec2(U16FROMU32(HiZInnerPacked));
// 2D Polyhedral Bounds of a Clipped, Perspective-Projected 3D Sphere. Michael Mara, Morgan McGuire. 2013
bool projectSphere(vec3 C, float r, float znear, float P00, float P11, out vec4 aabb) {
if (C.z < r + znear)
return false;
vec2 cx = -C.xz;
vec2 vx = vec2(sqrt(dot(cx, cx) - r * r), r);
vec2 minx = mat2(vx.x, vx.y, -vx.y, vx.x) * cx;
vec2 maxx = mat2(vx.x, -vx.y, vx.y, vx.x) * cx;
vec2 cy = -C.yz;
vec2 vy = vec2(sqrt(dot(cy, cy) - r * r), r);
vec2 miny = mat2(vy.x, vy.y, -vy.y, vy.x) * cy;
vec2 maxy = mat2(vy.x, -vy.y, vy.y, vy.x) * cy;
aabb = vec4(minx.x / minx.y * P00, miny.x / miny.y * P11, maxx.x / maxx.y * P00, maxy.x / maxy.y * P11);
aabb = aabb.xwzy * vec4(0.5f, -0.5f, 0.5f, -0.5f) + vec4(0.5f); // clip space -> uv space
return true;
}
void main() {
uint gid = gl_GlobalInvocationID.x;
uint lid = gl_LocalInvocationID.x;
if (gid < u_instanceCount) {
// Retrieve meshlet data
Instance instance = b_instances[gid];
Meshlet meshlet = b_meshlets[instance.meshletIdx];
uint transformIdx = instance.objectIdx;
mat4 transform = getTransform(b_transforms[transformIdx]);
// Transform meshlet data
float scale = max(
length(transform[0].xyz), max(
length(transform[1].xyz),
length(transform[2].xyz)));
vec3 boundingSphereCenter = vec3(transform * vec4(meshlet.boundingSphereCenter, 1.0));
float boundingSphereRadius = meshlet.boundingSphereRadius * scale;
bool visible = true;
// Frustum culling
vec3 viewCenter = vec3(u_view * vec4(boundingSphereCenter, 1.0));
visible = visible && (
(viewCenter.z * u_frustum[1] - abs(viewCenter.x) * u_frustum[0] > -boundingSphereRadius) &&
(viewCenter.z * u_frustum[3] - abs(viewCenter.y) * u_frustum[2] > -boundingSphereRadius));
// Occlusion culling
if (visible) { // Avoid a texture lookup if not needed
viewCenter.y *= -1;
vec4 aabb;
if (projectSphere(viewCenter, boundingSphereRadius, ZNear, u_P00, u_P11, aabb))
{
float width = (aabb.z - aabb.x) * HiZInner.x;
float height = (aabb.w - aabb.y) * HiZInner.y;
float level = floor(log2(max(width, height)));
vec2 offset = vec2(HiZSize - HiZInner) / 2.0 / vec2(HiZSize);
vec2 scale = vec2(HiZInner) / vec2(HiZSize);
// Sampler is set up to do min reduction, so this computes the minimum depth of a 2x2 texel quad
vec2 depthUv = saturate((aabb.xy + aabb.zw) * 0.5) * scale + offset;
float depth = textureLod(s_hiz, depthUv, level).x;
float depthSphere = ZNear / (viewCenter.z - boundingSphereRadius);
visible = (depthSphere > depth);
}
}
// Write the instance
if (visible) {
uint outIdx = atomicAdd(b_outInstanceCount.w, 1);
b_outInstances[outIdx] = instance;
}
}
// Update group counter
barrier();
if (lid == gl_WorkGroupSize.x - 1) {
uint groupCount = atomicAdd(b_groupCounter, 1);
if (groupCount == gl_NumWorkGroups.x - 1) {
// We're the last workgroup, write the instance groups
groupMemoryBarrier();
b_outInstanceCount.x = divRoundUp(b_outInstanceCount.w * MaxTrisPerMeshlet, 4 * 256);
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save